Files
atomic-design-poc/docs/project/refactor-backlog-setup/refactor-backlog/implementation/rb-14.md
T
ehoandClaude Opus 5 adfaa32a42 ci: gate on known advisories in the .NET dependency tree (RB-14)
npm audit --omit=dev gates the shipped frontend bundle; nothing equivalent
existed for the backend, so the entire .NET dependency tree — direct and
transitive — was unscanned (BIO-016 lists it first under "Absent").

The ticket's literal wording would not have worked. `dotnet list package
--vulnerable` is a reporting command: it prints the advisory table and exits
0 regardless. Verified with a throwaway project on System.Net.Http 4.3.0 —
severity High, GHSA-7jgj-8wvc-jh57, exit code 0. A bare `- run: dotnet list
package --vulnerable` would have added a line that reads like coverage in a
compliance review and enforces nothing, which is worse than leaving the gap
visible.

scripts/dotnet-audit.sh runs the scan and matches "has the following
vulnerable packages" — the exact sentence dotnet prints per project on a hit.
One script, two callers (ci.yml and ci-local.sh), so the workflow and the
local gate cannot drift apart.

No severity threshold and no suppression list: picking either before a real
advisory forces the question would be guessing at a policy nobody needs yet.
Secret scanning, BIO-016's other named absence, stays on the checklist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 16:24:16 +02:00

2.9 KiB

RB-14 — scan the .NET dependency tree for known advisories

Status: implemented · 2026-08-27 · Source findings: 07-bio2-compliance.md BIO-016 · 99-backlog.md RB-14

What was wrong

npm audit --omit=dev gates the shipped frontend bundle. Nothing equivalent existed for the backend, so the entire .NET dependency tree — direct and transitive — was unscanned. BIO-016 lists it first under "Absent".

The trap the ticket walked into

The backlog row says: dotnet list package --vulnerable --include-transitive as a failing step. Implemented literally, that step cannot fail. dotnet list package --vulnerable is a reporting command: it prints the advisory table and exits 0 regardless.

Verified rather than assumed — a throwaway project with System.Net.Http 4.3.0:

Project `vulntest` has the following vulnerable packages
   > System.Net.Http   4.3.0   4.3.0   High   https://github.com/advisories/GHSA-7jgj-8wvc-jh57
EXITCODE=0

A High severity advisory, exit code 0. A bare - run: dotnet list package --vulnerable would have added a line to ci.yml that reads like coverage in a compliance review and enforces nothing — which is worse than leaving the gap visible.

What changed

File Change
scripts/dotnet-audit.sh new — runs the scan, matches its output, exits 1 on a hit
.github/workflows/ci.yml new backend step calling the script (same changes.outputs.backend guard)
scripts/ci-local.sh new backend dependency audit step calling the same script

One script, two callers, rather than the same four lines pasted into a workflow and a shell script that would then drift. The guard matches has the following vulnerable packages — the exact sentence dotnet list prints per project on a hit; the clean case prints has no vulnerable packages given the current sources instead.

Verification

  • Against the real solution: passes, both projects clean (exit 0).
  • Against the marker sentence dotnet list actually emits: the guard fires and exits 1.
  • The exit-0-on-High behaviour that motivates the whole script is reproduced above.

Residual

--include-transitive means a vulnerable package pulled in by a dependency turns CI red with no direct upgrade available. The fix in that case is a direct PackageReference pinning a patched version; the script's failure message says so. There is deliberately no severity threshold and no suppression list — adding one before a real advisory forces the question would be guessing at a policy nobody has needed yet.

Secret scanning (gitleaks/trufflehog), BIO-016's other named absence, is not in this ticket and remains on the pre-production checklist.