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