Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/public-codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ on:
type: number
default: 14
runner:
description: 'Runner label.'
description: 'Runner label for ordinary trusted events.'
type: string
default: 'ubuntu-latest'
dependabot_runner:
description: 'Isolated runner label for pull requests authored by dependabot[bot].'
type: string
default: 'ubuntu-latest'
egress_policy:
Expand All @@ -63,7 +67,7 @@ permissions: {}
jobs:
codeql:
name: CodeQL (${{ matrix.language }})
runs-on: ${{ inputs.runner }}
runs-on: ${{ github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]' && inputs.dependabot_runner || inputs.runner }}
timeout-minutes: 20
permissions:
actions: read # upload-sarif reads this workflow run
Expand All @@ -79,6 +83,15 @@ jobs:
with:
egress-policy: ${{ inputs.egress_policy }}

- name: Record CodeQL trust boundary
shell: bash
env:
TRUST_BOUNDARY: ${{ github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]' && 'dependabot-untrusted' || 'ordinary' }}
SELECTED_RUNNER: ${{ github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]' && inputs.dependabot_runner || inputs.runner }}
run: |
printf "CodeQL trust boundary: \`%s\`\n\nRunner: \`%s\`\n" \
"$TRUST_BOUNDARY" "$SELECTED_RUNNER" >> "$GITHUB_STEP_SUMMARY"

- name: Isolate global Git config
shell: bash
run: |
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The project follows Semantic Versioning.

## [Unreleased]

- Added explicit Dependabot-safe advanced CodeQL routing. Ordinary private
analysis and Dependabot pull requests can use separate reviewed runner
classes while preserving stable language check identities and a secretless
`pull_request` trust boundary.
- Added validated opt-in dependency cache inputs to `python-ci.yml` pip callers
and `node-ci.yml` npm/pnpm/Yarn callers. Cache backends must match the package
manager and dependency paths cannot be supplied without enabling a cache.
Expand Down
6 changes: 5 additions & 1 deletion catalog/capabilities.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ capabilities:
required_settings:
- "Code scanning enabled"
- "GitHub Advanced Security enabled (required on private repos)"
- "Dependabot pull requests use the reviewed dependabot_runner trust boundary"
risks:
- "Paid via GHAS on private repositories; keep upload disabled there"
- "Language autobuild can fail on compiled languages without a build spec"
- "Dependabot pull_request events have read-only tokens and no Actions secrets; use pull_request, never pull_request_target, and keep checkout credentials disabled"
deprecations: null
last_verified: "2026-07-04"
last_verified: "2026-08-23"
sources:
- "https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql"
- "https://github.com/github/codeql-action"
- "https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-on-actions"
- "https://docs.github.com/en/code-security/reference/code-scanning/troubleshoot-analysis-errors/resource-not-accessible"

- id: github-code-quality
name: GitHub Code Quality
Expand Down
7 changes: 6 additions & 1 deletion docs/01-public-oss-free.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ jobs:
with:
languages: '["python","actions"]'
queries: security-and-quality
runner: ubuntu-latest
dependabot_runner: ubuntu-latest
```

Inputs: `languages` (JSON array), `queries` (suite, default
`security-and-quality`), `runner`, `egress_policy`. See
`security-and-quality`), `runner`, `dependabot_runner`, `egress_policy`. Public
callers keep both on `ubuntu-latest`; private GHAS callers may route ordinary
analysis to a trusted private class and Dependabot pull requests to an isolated
untrusted class without changing the `CodeQL (<language>)` check identity. See
[06 Security scanning](06-security-scanning.md#codeql) for language coverage and
Copilot Autofix.

Expand Down
2 changes: 2 additions & 0 deletions docs/03-private-paid-ghas.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ jobs:
uses: NDDev-OpenNetwork/ci-workflows/.github/workflows/public-codeql.yml@<full-sha>
with:
languages: '["python","actions"]'
runner: nddev-linux-standard
dependabot_runner: nddev-linux-untrusted
zizmor:
permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/capability-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@
| SLSA build provenance (`slsa-build-provenance`) | supply-chain | ga | free | paid | conditional | `.github/workflows/release-supply-chain.yml` | `-` |

---
Source data verified through: 2026-08-22
Source data verified through: 2026-08-23
2 changes: 1 addition & 1 deletion docs/generated/workflow-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@
| `.github/workflows/zizmor-sarif.yml` | `zizmor` | ga |

---
Source data verified through: 2026-08-22
Source data verified through: 2026-08-23
1 change: 1 addition & 0 deletions examples/nddev/security-private-selfhosted.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
with:
languages: '["python","actions"]'
runner: nddev-linux-standard
dependabot_runner: nddev-linux-untrusted
security-bundle:
permissions: { contents: read }
uses: NDDev-OpenNetwork/ci-workflows/.github/workflows/nddev-security-bundle.yml@<sha>
Expand Down
1 change: 1 addition & 0 deletions examples/nddev/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
uses: NDDev-OpenNetwork/ci-workflows/.github/workflows/public-codeql.yml@<sha>
with:
runner: ubuntu-latest
dependabot_runner: ubuntu-latest
languages: '["python","actions"]'
secret-scan:
permissions: { contents: read }
Expand Down
1 change: 1 addition & 0 deletions examples/private-paid-ghas/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
uses: NDDev-OpenNetwork/ci-workflows/.github/workflows/public-codeql.yml@<sha>
with:
runner: ubuntu-latest
dependabot_runner: ubuntu-latest
languages: '["python","actions"]'
secret-scan:
permissions: { contents: read }
Expand Down
1 change: 1 addition & 0 deletions examples/public-oss/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
uses: NDDev-OpenNetwork/ci-workflows/.github/workflows/public-codeql.yml@<sha>
with:
runner: ubuntu-latest
dependabot_runner: ubuntu-latest
languages: '["python","actions"]'
secret-scan:
permissions: { contents: read }
Expand Down
29 changes: 29 additions & 0 deletions scripts/check_workflow_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,35 @@ def check() -> list[str]:
"""
workflow_root = workflow_files()[0].parent

codeql = load_yaml(workflow_root / "public-codeql.yml")
codeql_on = get_on(codeql)
codeql_call = codeql_on.get("workflow_call", {}) if isinstance(codeql_on, dict) else {}
codeql_inputs = codeql_call.get("inputs", {}) if isinstance(codeql_call, dict) else {}
dependabot_runner = codeql_inputs.get("dependabot_runner", {}) if isinstance(codeql_inputs, dict) else {}
if not isinstance(dependabot_runner, dict) or dependabot_runner.get("type") != "string" or dependabot_runner.get("default") != "ubuntu-latest":
problems.append(
"public-codeql.yml: dependabot_runner must remain a string with the "
"public-safe ubuntu-latest default"
)
codeql_job = (codeql.get("jobs", {}) or {}).get("codeql", {})
expected_codeql_runner = "${{ github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]' && inputs.dependabot_runner || inputs.runner }}"
if codeql_job.get("runs-on") != expected_codeql_runner:
problems.append(
"public-codeql.yml: runner selection must explicitly isolate "
"Dependabot-authored pull requests"
)
boundary_steps = [
step for step in (codeql_job.get("steps", []) or [])
if isinstance(step, dict) and step.get("name") == "Record CodeQL trust boundary"
]
if len(boundary_steps) != 1 or (boundary_steps[0].get("env") or {}).get("SELECTED_RUNNER") != expected_codeql_runner:
problems.append(
"public-codeql.yml: selected Dependabot/ordinary runner boundary must "
"be emitted as job evidence"
)
if "pull_request_target" in (workflow_root / "public-codeql.yml").read_text(encoding="utf-8"):
problems.append("public-codeql.yml: Dependabot analysis must never use pull_request_target")

# The consolidated scanner requires four output paths before it can create
# empty fail-safe evidence. Changing the shared script without wiring every
# caller broke the first public-product consumer ring at shell expansion,
Expand Down
Loading