Skip to content

fix(security): harden dependency-audit Cargo/Python coverage and add audit gate - #1420

Merged
don-petry merged 4 commits into
mainfrom
fix/sec-dependency-audit-reusable
Oct 28, 2026
Merged

don-petry merged 4 commits into
mainfrom
fix/sec-dependency-audit-reusable

Conversation

@don-petry

@don-petry don-petry commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

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-cargo and audit-pip check the audited repo out under repo/. 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.
  • A new aggregate job, Audit gate, needs detect and every audit job.
  • The caller stub (standards/workflows/dependency-audit.yml) stays compatible. No inputs or secrets were added, and the Detect ecosystems job name and the dependency-audit / Detect ecosystems required context are unchanged.

Fixes #1393: Cargo workspace dedup used directory ancestry

Confirmed: at dependency-audit-reusable.yml:194-208 on main, any non-[workspace] crate under a workspace root's directory was treated as covered. I ran main's Audit Cargo dependencies step against a fixture whose workspace has exclude = ["tools/excluded"], with cargo stubbed. It audited only ./repo and never tools/excluded.

Fix: each Cargo.toml is resolved with cargo 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-duplicated workspace_root values. 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:212 on main, the step ran cargo generate-lockfile 2>/dev/null; cargo audit in every directory. In the same fixture, a committed lockfile with a vulnerable pin was overwritten and the step exited 0.

Fix: cargo audit runs against the committed Cargo.lock unchanged. cargo generate-lockfile runs only when no Cargo.lock exists, 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-259 on main, a requirements.txt in a directory named svc<LF>api was split into ./repo/svc and api. Both -f guards were false, so no pip-audit ran and the step exited 0.

Fix: discovery uses find -print0, NUL-safe dirname, sort -zu and read -d ''. If a discovered project has no readable pyproject.toml or requirements.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:30 requires dependency-audit / Detect ecosystems. That job only runs find and succeeds no matter what the scanners report.

Fix (additive): a new Audit gate job in the reusable:

  • It has needs: [detect, audit-npm, audit-pnpm, audit-go, audit-cargo, audit-pip] and if: always().
  • It uses always(), not !cancelled(), so a cancelled run reports a failure here instead of a skip.
  • It passes only when detect succeeded 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.
  • This follows the same pattern as the CodeQL aggregate job in codeql.yml.
  • In consumers it reports as dependency-audit / Audit gate.
  • A test pins that the gate needs every other job, so a future ecosystem job can't bypass it.

Not changed (decision needed): standards/rulesets/code-quality.json still requires dependency-audit / Detect ecosystems. Repointing it to dependency-audit / Audit gate changes the required-check contract for the whole fleet. Consumers pin dependency-audit/v2-stable, which has no Audit gate job. 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 uses Part of #1406 and not Fixes. Suggested sequence once this ships to dependency-audit/v2-stable:

  1. Add dependency-audit / Audit gate to code-quality.json, either in addition to or in place of Detect ecosystems.
  2. Update the PROVIDERS / REUSABLE_JOB_NAMES entries in test/workflows/required-checks/merge-group.bats.
  3. Update the comments and docs that name the context: scripts/apply-rulesets.sh, the stub headers, standards/github-settings.md, and the renames map in compliance-audit.sh.
  4. Run apply-rulesets.sh.

standards/github-settings.md now documents the gate and the pending decision.

A no-ruleset-change alternative for reviewers: rename the old detect job's display name and give the aggregate job the name Detect 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)

Behaviour changes for consumers (after channel promotion)

  • More crates may be audited: excluded and non-member crates under a workspace directory.
  • A Cargo.toml that Cargo can't resolve now fails cargo audit. Before, it was skipped if it sat under a workspace directory. Fixture crates inside a workspace should be listed in the workspace's exclude or carry their own [workspace] table.
  • A vulnerable committed Cargo.lock now fails. Before, the runner regenerated it and could mask the vulnerable pin.
  • A new, non-required dependency-audit / Audit gate check 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:

  • the lib functions, with cargo and pip-audit stubs that record their calls and fail on a VULN marker;
  • the reusable's own run: steps, extracted from the YAML with yq and run with bash -e against fixtures;
  • the gate's wiring, including needs completeness, 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.3
  • bats test/workflows/dependency-hardening.bats test/workflows/required-checks/: all pass
  • bats test/scripts/standards-templates/ test/scripts/compliance-audit/{centralized-stub-pins,required-workflows-ingress}.bats test/scripts/apply-rulesets/: all pass
  • deploy-standard-workflows/dry-run.bats and lib/standards-deploy.bats fail identically on an untouched origin/main tree. That is the macOS base64 environment, not this change.
  • shellcheck -x on the lib, helpers and bats file: clean
  • actionlint with the CI ignore set: clean
  • gitleaks dir .: no leaks

No cargo is available locally. The real cargo metadata / cargo audit behaviour will be exercised by this repo's dogfood run and by the next canary.

Release notes / follow-ups

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

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.

…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>
@don-petry
don-petry requested a review from a team as a code owner October 28, 2026 03:22
@chatgpt-codex-connector

This comment has been minimized.

@coderabbitai

This comment has been minimized.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — review-changes (no-changes)

No changes were needed for this PR.

@don-petry
don-petry enabled auto-merge (squash) October 28, 2026 03:25
@donpetry-bot

Copy link
Copy Markdown
Contributor

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.

@donpetry-bot

Copy link
Copy Markdown
Contributor

No description provided.

@don-petry
don-petry disabled auto-merge October 28, 2026 05:27
@donpetry-bot

donpetry-bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

No description provided.

@don-petry

Copy link
Copy Markdown
Contributor Author

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.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — fix-bot-comment (no-changes)

Agent reasoning
## Summary
**Bot:** chatgpt-codex-connector[bot]
**Issues addressed:** 1 operational notice
- **Failed review attempt (operational notice):** Codex reported "We couldn't complete this review" with no specific code findings. All CI checks pass with no blockers. Dispositioned as `informational`.
**Files changed:** None
**Disposition:** Posted on PR #1420 — the harness will verify and minimize the bot's original comment as RESOLVED.

@don-petry
don-petry enabled auto-merge (squash) October 28, 2026 05:28
@don-petry

don-petry commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

No description provided.

@don-petry
don-petry disabled auto-merge October 28, 2026 08:40
@don-petry

This comment has been minimized.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — fix-bot-comment (no-changes)

Agent reasoning
## Summary
**Bot:** coderabbitai[bot]  
**Issues addressed:** 0 (no actionable findings)  
**Comment disposition:** informational
The CodeRabbit comment is a **pure rate-limit operational notice** — it reports that the free OSS review quota was exhausted, not that there are code defects. The comment contains:
- A rate-limit block explaining the quota was used
- Configuration metadata  
- No security findings, code issues, or actionable defects
Per the non-actionable bot notices guidance, I posted a disposition reply (`informational`) on the original PR issue comment. The harness will verify and minimize it as RESOLVED, clearing the maintainer-comment gate.
**No code changes required.**

@don-petry
don-petry enabled auto-merge (squash) October 28, 2026 08:41
@donpetry-bot

Copy link
Copy Markdown
Contributor

No description provided.

@don-petry
don-petry disabled auto-merge October 28, 2026 11:32
@don-petry

Copy link
Copy Markdown
Contributor Author

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.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — fix-bot-comment (no-changes)

Agent reasoning
**Comment disposition:** ✅ Posted `answered` disposition on PR #1420
**Rationale:** 
The SonarCloud comment reported "Quality Gate passed" with 0 Security Hotspots, but listed 13 new code quality issues without specific file/line references. This doesn't match the fully-clean pattern (would require "[0 New issues]") but the issues are not actionable in this context:
- Quality Gate passed (no blocker)
- No security hotspots (0)
- Comment provides no specific files or line numbers to address
- PR's changed files contain no known security hotspot patterns (curl|bash, hardcoded credentials, eval/exec, insecure downloads)
- All CI checks passed successfully
**Files changed:** None (no code changes required)
**Skipped:** 0

@don-petry
don-petry enabled auto-merge (squash) October 28, 2026 11:34
@don-petry

Copy link
Copy Markdown
Contributor Author

No description provided.

@donpetry-bot

Copy link
Copy Markdown
Contributor

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.

@don-petry
don-petry disabled auto-merge October 28, 2026 11:48
@don-petry

This comment has been minimized.

@don-petry

Copy link
Copy Markdown
Contributor Author

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.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — fix-bot-comment (no-changes)

Agent reasoning
   - PR contains dependency audit hardening with new shell script and workflow changes
   - Reviewed changes for security hotspot patterns (curl|bash, dynamic code execution, hardcoded credentials)
   - No security issues found; all CI checks passed
   - Disposition: `answered` ✓
**Files reviewed**: 
- `.github/scripts/dependency-audit/lib/dependency-audit.sh` (new script for Cargo/Python discovery)
- `.github/workflows/dependency-audit-reusable.yml` (refactored audit workflow)
- `.github/workflows/dependency-audit-tests.yml` (new test workflow)
- Supporting test and configuration files
**No code changes needed** — Both comments represent either rate-limiting (informational) or a passed quality gate with no actionable security findings in the diff.

@don-petry
don-petry enabled auto-merge (squash) October 28, 2026 11:51
@don-petry

Copy link
Copy Markdown
Contributor Author

No description provided.

@don-petry
don-petry disabled auto-merge October 28, 2026 15:42
@don-petry

Copy link
Copy Markdown
Contributor Author

This comment reports a rate-limit notice — the code review was throttled due to usage limits. No actionable code findings are present.

@don-petry

Copy link
Copy Markdown
Contributor Author

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.

@don-petry

Copy link
Copy Markdown
Contributor Author

Dev-Lead — fix-bot-comment (no-changes)

Agent reasoning
**Issues addressed:**
1. **CodeRabbit stale disposition** (`IC_kwDORyesfc8AAAABgQjyIQ`): The comment was edited after the prior disposition and now shows only a rate-limit notice. Posted fresh `informational` disposition since the comment reports no actionable code findings.
2. **SonarCloud Quality Gate comment** (`IC_kwDORyesfc8AAAABgdXCaQ`): The comment reports "Quality Gate passed" with 13 new code quality issues but **0 Security Hotspots** and **0% duplication on new code**. All CI checks (including "SonarCloud Code Analysis") passed successfully. Posted `answered` disposition explaining that:
   - Quality Gate passed (no blocker)
   - No security hotspots detected
   - The 13 issues are non-critical code quality findings
   - Reviewed changed files for security patterns (script injection, hardcoded credentials, insecure downloads, dynamic code execution) and found none
**Files changed:** None (no code changes required)
**Dispositions posted:** 2 (both verification comments)
The PR is now cleared of blocker bot comments and ready for approval.

@don-petry
don-petry enabled auto-merge (squash) October 28, 2026 15:45

@donpetry-bot donpetry-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-deps and audit roots are deduplicated by workspace_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-lockfile runs 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 -zu and read -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 gate job is additive and fails closed on failure, cancelled, missing or malformed input. A wiring test pins needs completeness and always(). Repointing the ruleset is deferred until v2-stable ships 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: read at the top level. Both checkouts use persist-credentials: false.
  • Actions are pinned by SHA. The new test workflow uses permissions: {} plus a job-level read.
  • toJSON(needs) reaches the script through env:, not interpolated into run:.
  • The tooling checkout is a sibling of the scanned tree, so the audited repo can't shadow the lib.
  • combsmith/.github is public, so the cross-repo checkout works with consumers' tokens.
  • cargo install cargo-audit now runs outside the audited repo, so its .cargo/config.toml / rust-toolchain.toml can'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):

  1. Rust layout breakage risk. Cargo itself errors ("believes it's in a workspace when it's not") on a Cargo.toml under a workspace directory that is neither a member nor excluded, for example cargo vendor output in vendor/ or test-fixture crates. With this change those fail cargo audit, where before they were silently skipped. The PR documents this as intended. Check the Rust consumers in the canary before promoting to v2-stable.
  2. Not live-tested yet. The tooling checkout and new install path in audit-cargo / audit-pip didn'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.
  3. 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 audit and the new dependency-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.

@donpetry-bot

Copy link
Copy Markdown
Contributor

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).

@sonarqubecloud

Copy link
Copy Markdown

@don-petry
don-petry merged commit a5de9a0 into main Oct 28, 2026
27 of 30 checks passed
@don-petry
don-petry deleted the fix/sec-dependency-audit-reusable branch October 28, 2026 17:30
don-petry added a commit that referenced this pull request Nov 4, 2026
…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>
don-petry added a commit that referenced this pull request Nov 5, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants