Repository navigation
fix(security): harden dependency-audit Cargo/Python coverage and add audit gate - #1420
Conversation
…audit gate (#1393, #1394, #1404, #1406) - Cargo: audit one root per workspace as reported by `cargo metadata --no-deps` (`workspace_root`), so a crate the parent workspace excludes, or is not a member of, is audited on its own instead of being dropped because it sits under the workspace directory (#1393). - Cargo: audit a committed Cargo.lock as committed; generate one only when none exists (#1394). - Python: NUL-delimited discovery; a discovered project that cannot be audited fails the job instead of being skipped (#1404). Paths are escaped in log lines so a directory name cannot emit a workflow command. - Add an aggregate `Audit gate` job (needs detect + every audit job, `if: always()`) that fails when any of them failed or was cancelled. `Detect ecosystems` and the code-quality ruleset are unchanged; switching the required context is left as a decision (#1406). - Move the logic into .github/scripts/dependency-audit/lib/dependency-audit.sh, sourced from the reusable's own commit (job.workflow_sha); audited repo is checked out under repo/ so the tooling is never scanned. pip-audit now installs from the tooling checkout's hash-locked requirements file, which consumer repos do not have. - bats suite (lib + the reusable's own run steps against fixtures with stubbed cargo/pip-audit) and a Dependency Audit Tests workflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Dev-Lead — review-changes (no-changes)No changes were needed for this PR. |
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-28T04:25:23Z. |
|
No description provided. |
|
No description provided. |
|
This is an operational notice that the Codex review failed to complete, not a code finding. All CI checks are passing with no blockers, and no specific code issues are reported. No code changes needed. |
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
No description provided. |
This comment has been minimized.
This comment has been minimized.
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
No description provided. |
|
Quality Gate passed with no security hotspots. The 13 reported code quality issues are non-critical and don't block the Quality Gate. Without specific file/line references in the comment, they cannot be addressed individually in this reply. |
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
No description provided. |
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-28T12:47:44Z. |
This comment has been minimized.
This comment has been minimized.
|
SonarCloud Quality Gate assessment: Quality Gate passed with 0 Security Hotspots. The PR changes include new dependency audit script and workflow hardening — reviewed the code for common security patterns (curl|bash, exec, hardcoded credentials, insecure downloads) and found none. All CI checks including SonarCloud Code Analysis passed successfully. |
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
No description provided. |
|
This comment reports a rate-limit notice — the code review was throttled due to usage limits. No actionable code findings are present. |
|
The Quality Gate passed with 0 Security Hotspots and 0% duplication on new code. The 13 reported code quality issues are non-critical findings flagged by SonarCloud but do not block the Quality Gate. All CI checks including SonarCloud Code Analysis passed successfully. Reviewed the changed files for common security patterns (script injection, hardcoded credentials, insecure downloads, dynamic code execution) and found none. |
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: MEDIUM
Reviewed commit: d8e47e3cad68ff711c27271f09dd8983b8a58e4c
Review mode: triage-approved (single reviewer)
Summary
Hardens the fleet-wide dependency-audit reusable. Cargo/Python discovery and a new aggregate gate verdict move into a sourceable, bats-tested lib (.github/scripts/dependency-audit/lib/dependency-audit.sh). Cargo coverage now comes from cargo metadata workspace_root (#1393). A committed Cargo.lock is audited as committed (#1394). Python discovery is NUL-safe (#1404). An additive Audit gate job aggregates detect plus every audit job (part of #1406). Two jobs are now thin glue: they check the audited repo out under repo/ and the reusable's own commit (job.workflow_sha) under .dependency-audit-tooling/. A new Dependency Audit Tests workflow runs shellcheck and 27 bats tests. I rated it MEDIUM, not HIGH: it is non-trivial logic that changes behaviour for consumers, but it adds no new security-sensitive surface. Triage assessment confirmed.
Linked issue analysis
- #1393 (Cargo excluded crates dropped by directory-ancestry dedup): addressed. Each manifest is resolved with
cargo metadata --no-depsand audit roots are deduplicated byworkspace_root. A manifest that can't be resolved fails loudly instead of being skipped. Pinned by tests for the lib and for the extracted workflow step. - #1394 (committed Cargo.lock regenerated before audit): addressed.
generate-lockfileruns only when no Cargo.lock exists, and a failed generation now fails the job. A test asserts the committed lock is left unchanged. - #1404 (newline-split Python discovery): addressed. Discovery uses
find -print0,sort -zuandread -d ''. A discovered project with no readable manifest, or a failed discovery walk, now fails the job. CR/LF in paths are escaped before they reach::group::/::error::lines, which closes a workflow-command injection vector. Tested. - #1406 (required check is discovery, not scanner outcome): correctly marked Part of, not Fixes. The
Audit gatejob is additive and fails closed on failure, cancelled, missing or malformed input. A wiring test pinsneedscompleteness andalways(). Repointing the ruleset is deferred untilv2-stableships the job, which is the right call: doing it earlier would leave consumer PRs waiting on a check that never reports.
Findings
No blocking findings.
Security review: no regressions, net hardening.
- Permissions stay
contents: readat the top level. Both checkouts usepersist-credentials: false. - Actions are pinned by SHA. The new test workflow uses
permissions: {}plus a job-level read. toJSON(needs)reaches the script throughenv:, not interpolated intorun:.- The tooling checkout is a sibling of the scanned tree, so the audited repo can't shadow the lib.
combsmith/.githubis public, so the cross-repo checkout works with consumers' tokens.cargo install cargo-auditnow runs outside the audited repo, so its.cargo/config.toml/rust-toolchain.tomlcan't affect the scanner install.- The pip-audit requirements path bug (installing from the audited repo, which consumers don't have) is fixed.
Non-blocking notes (for the canary / channel promotion):
- Rust layout breakage risk. Cargo itself errors ("believes it's in a workspace when it's not") on a
Cargo.tomlunder a workspace directory that is neither a member nor excluded, for examplecargo vendoroutput invendor/or test-fixture crates. With this change those failcargo audit, where before they were silently skipped. The PR documents this as intended. Check the Rust consumers in the canary before promoting tov2-stable. - Not live-tested yet. The tooling checkout and new install path in
audit-cargo/audit-pipdidn't run in this repo's dogfood run (both jobs were skipped because those ecosystems are absent). The bats step-extraction tests cover the logic; the first real run will be the canary. - Known gap, tracked as a follow-up in the PR body. The npm, pnpm and Go loops still use newline-delimited
find ... -exec dirname, the same pattern #1404 fixed for Python.
Errexit check: the runner uses bash -e, and every fallible call in the lib sits in an if ! or || status=1 context, so failures are counted rather than aborting the step part-way.
CI status
All checks green:
- Lint and bats (Dependency Audit Tests), bats, ShellCheck, Lint
- CodeQL / Analyze (actions, python), SonarCloud (Quality Gate passed, 0 hotspots)
- gitleaks, AgentShield, Agent Security Scan, duplicate-decl-gate
dependency-audit / Detect ecosystems,npm auditand the newdependency-audit / Audit gate, all SUCCESS
pnpm, govulncheck, cargo and pip were skipped because those ecosystems are absent here. CodeRabbit is NEUTRAL (rate-limited, no findings). There are no review threads, and the bot notices have all been dispositioned.
Reviewed automatically by the PR-review agent (single-reviewer mode: opus 5.5 [opus 4.8, opus 4.7]). Reply if you need a human review.
|
pr-review approved on PARTIAL advisory evidence: 1/5 required advisory bots reported before the gate's head-age-timeout fallback proceeded. Recorded for the miss-rate metric (#1596). |
|
…t ecosystems (#1406) The code-quality ruleset required `dependency-audit / Detect ecosystems`, which passes once discovery finishes whatever the scanners find, so a vulnerable dependency never blocked a merge. Require the aggregate `Audit gate` job added in #1420, bound to the GitHub Actions app (integration_id 15368). Every channel a consumer pins (v2-stable, v2-ring1, v2-next, local ref) ships the job. Update the standards docs, caller-stub comments, compliance-audit stale-name remediation and the tests that pin the required context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t ecosystems (#1406) (#1470) * fix(security): require dependency-audit / Audit gate instead of Detect ecosystems (#1406) The code-quality ruleset required `dependency-audit / Detect ecosystems`, which passes once discovery finishes whatever the scanners find, so a vulnerable dependency never blocked a merge. Require the aggregate `Audit gate` job added in #1420, bound to the GitHub Actions app (integration_id 15368). Every channel a consumer pins (v2-stable, v2-ring1, v2-next, local ref) ships the job. Update the standards docs, caller-stub comments, compliance-audit stale-name remediation and the tests that pin the required context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: correct skipped-check and detection wording for the audit gate (#1406) GitHub treats a job skipped by its `if:` as passing, so a required-but-skipped check satisfies the merge gate rather than blocking it; that is why the per-ecosystem scanners must not be required. Also note that Go, Cargo and Python are detected by manifest, not lockfile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>



Summary
Fixes four findings in
.github/workflows/dependency-audit-reusable.yml. The Cargo/Python discovery and the new gate verdict now live in a sourceable, bats-tested library,.github/scripts/dependency-audit/lib/dependency-audit.sh, per AGENTS.md "Decision Logic Lives in a Pure, Tested Script". The workflow steps are now thin glue:audit-cargoandaudit-pipcheck the audited repo out underrepo/. They check this repo out at the reusable's own commit (job.workflow_sha, the same pattern as fix(feature-ideation): source tooling from the reusable's own commit (#839) #1368) into.dependency-audit-tooling/, outside the scanned tree, and source the lib from there.Audit gate, needsdetectand every audit job.standards/workflows/dependency-audit.yml) stays compatible. No inputs or secrets were added, and theDetect ecosystemsjob name and thedependency-audit / Detect ecosystemsrequired context are unchanged.Fixes #1393: Cargo workspace dedup used directory ancestry
Confirmed: at
dependency-audit-reusable.yml:194-208on main, any non-[workspace]crate under a workspace root's directory was treated as covered. I ran main'sAudit Cargo dependenciesstep against a fixture whose workspace hasexclude = ["tools/excluded"], withcargostubbed. It audited only./repoand nevertools/excluded.Fix: each
Cargo.tomlis resolved withcargo metadata --no-deps --format-version 1. That command doesn't need the network and doesn't write a lockfile. The audit set is the de-duplicatedworkspace_rootvalues. Cargo reports the parent workspace for a member and the crate's own directory for an excluded or standalone crate. If Cargo can't resolve a manifest, the job fails with::error::and does not skip it. Every root that does resolve is still audited.Fixes #1394: committed Cargo.lock regenerated before audit
Confirmed: at
dependency-audit-reusable.yml:212on main, the step rancargo generate-lockfile 2>/dev/null; cargo auditin every directory. In the same fixture, a committed lockfile with a vulnerable pin was overwritten and the step exited 0.Fix:
cargo auditruns against the committedCargo.lockunchanged.cargo generate-lockfileruns only when noCargo.lockexists, which is typical for libraries. A failed generation now fails the job; before, it was ignored.Fixes #1404: newline-delimited Python discovery
Confirmed: at
dependency-audit-reusable.yml:241-259on main, arequirements.txtin a directory namedsvc<LF>apiwas split into./repo/svcandapi. Both-fguards were false, so nopip-auditran and the step exited 0.Fix: discovery uses
find -print0, NUL-safedirname,sort -zuandread -d ''. If a discovered project has no readablepyproject.tomlorrequirements.txt(for example a dangling symlink), the job fails. If the discovery walk itself fails, the job fails too. Directory names are shown with CR/LF escaped in::group::and::error::lines, so a crafted name can't start a new workflow-command line.Part of #1406: required check is ecosystem discovery
Confirmed:
standards/rulesets/code-quality.json:30requiresdependency-audit / Detect ecosystems. That job only runsfindand succeeds no matter what the scanners report.Fix (additive): a new
Audit gatejob in the reusable:needs: [detect, audit-npm, audit-pnpm, audit-go, audit-cargo, audit-pip]andif: always().always(), not!cancelled(), so a cancelled run reports a failure here instead of a skip.detectsucceeded and every audit job either succeeded or was skipped. A job is skipped when its ecosystem is absent.failure,cancelled, a missing result or malformed input all fail it.CodeQLaggregate job incodeql.yml.dependency-audit / Audit gate.Not changed (decision needed):
standards/rulesets/code-quality.jsonstill requiresdependency-audit / Detect ecosystems. Repointing it todependency-audit / Audit gatechanges the required-check contract for the whole fleet. Consumers pindependency-audit/v2-stable, which has noAudit gatejob. If the ruleset were applied before that channel ships the job, every consumer PR would wait forever on a check that never reports. That's why this PR usesPart of #1406and notFixes. Suggested sequence once this ships todependency-audit/v2-stable:dependency-audit / Audit gatetocode-quality.json, either in addition to or in place ofDetect ecosystems.PROVIDERS/REUSABLE_JOB_NAMESentries intest/workflows/required-checks/merge-group.bats.scripts/apply-rulesets.sh, the stub headers,standards/github-settings.md, and therenamesmap incompliance-audit.sh.apply-rulesets.sh.standards/github-settings.mdnow documents the gate and the pending decision.A no-ruleset-change alternative for reviewers: rename the old
detectjob's display name and give the aggregate job the nameDetect ecosystems. The existing required context would then gate on the scanners as soon as the channel is promoted. The cost is a confusingly named job, so I haven't done it here.Also fixed in the same jobs (please review)
audit-pipinstalled fromscripts/pip-audit-requirements.txtin the audited repo. Consumer repos don't have that file (the same class of bug as feature-ideation v1-next: pip step fails — tooling_ref default 'v1' skews from reusable (blocks .github-private, will break fleet on promotion) #839), so the job failed at "Install pip-audit". It now installs from the tooling checkout's copy. This matters for dependency-audit-reusable: the required check is ecosystem discovery, not the scanners' outcome #1406: a required gate would otherwise fail on every Python consumer.test/workflows/dependency-hardening.batsnow maps the.dependency-audit-tooling/prefix back to the repo root, just as it already does for.feature-ideation-tooling/.cargo install cargo-auditruns outside the audited repo (in the workspace root, notrepo/). An audited repo's.cargo/config.tomlorrust-toolchain.tomlno longer affects how the scanner is installed.Behaviour changes for consumers (after channel promotion)
Cargo.tomlthat Cargo can't resolve now failscargo audit. Before, it was skipped if it sat under a workspace directory. Fixture crates inside a workspace should be listed in the workspace'sexcludeor carry their own[workspace]table.Cargo.locknow fails. Before, the runner regenerated it and could mask the vulnerable pin.dependency-audit / Audit gatecheck appears.Tests
New suite:
test/workflows/dependency-audit/dependency-audit.bats(27 tests), run by a new Dependency Audit Tests workflow (.github/workflows/dependency-audit-tests.yml) on any change to the reusable, the lib, the tests or the stub template. It covers:cargoandpip-auditstubs that record their calls and fail on aVULNmarker;run:steps, extracted from the YAML withyqand run withbash -eagainst fixtures;needscompleteness,always(), and the tooling checkout ref and path.Regression check: running the same suite against main's workflow, the step-level tests for #1393/#1394 (Cargo), #1404 (Python) and #1406 (gate step), plus the wiring tests, fail. With this branch, all 27 pass.
Commands run locally:
bats test/workflows/dependency-audit/: 27/27 pass under bash 3.2 and bash 5.3bats test/workflows/dependency-hardening.bats test/workflows/required-checks/: all passbats test/scripts/standards-templates/ test/scripts/compliance-audit/{centralized-stub-pins,required-workflows-ingress}.bats test/scripts/apply-rulesets/: all passdeploy-standard-workflows/dry-run.batsandlib/standards-deploy.batsfail identically on an untouchedorigin/maintree. That is the macOSbase64environment, not this change.shellcheck -xon the lib, helpers and bats file: cleanactionlintwith the CI ignore set: cleangitleaks dir .: no leaksNo
cargois available locally. The realcargo metadata/cargo auditbehaviour will be exercised by this repo's dogfood run and by the next canary.Release notes / follow-ups
dependency-auditrelease cut and channel promotion (v2-next→v2-stable). No tags are moved here.find … -exec dirname, the same pattern dependency-audit-reusable: newline-delimited Python project discovery silently skips directories whose names contain newlines #1404 fixed for Python.Cargo.lockthat is stale relative to itsCargo.tomlis audited as committed. A--lockedfreshness check would close that gap, but it needs network access and could break repos whose libraries don't keep their lockfiles in sync.🤖 Generated with Claude Code
Problem
See the linked issue.
Risk
Medium — changes GitHub Actions workflow behavior, which is exercised only post-merge; verify via the affected workflow runs.
Test plan
Tests added/updated:
test/workflows/dependency-audit/dependency-audit.bats,test/workflows/dependency-hardening.bats. Verification:bash scripts/dev-lead-lint.sh(shellcheck --severity=warning) ran pre-commit; the bats suite runs in CI.Rollback
Revert this PR. No non-revertible side effects (no tags, migrations, or external state).
Monitoring
Watch the affected workflow run(s) in the Actions tab and this PR's Lint check for regressions.