---
name: "dependency_audit"
description: "Run a full dependency audit: CVE triage with reachability, license compliance checks (copyleft and unknown licenses), abandoned-package signals, and the quarterly prune of deps you no longer need. Triggers when a project needs a security or license review of its dependency tree, when new advisories appear, or on a scheduled audit cadence."
---

# Dependency Audit

## Purpose

Answer three questions about the code you did not write: is it *dangerous*, is it *legal*, and is it *maintained*. A proper audit covers the resolved transitive tree (not just the manifest), triages every advisory for reachability instead of panic-listing, enforces the project's license policy, flags abandoned packages before they become incidents, and ends with a pruning list of what to cut.

## Workflow

1. **Inventory the tree.** Work from the lockfile, never the manifest. Run `bin/dep-audit.py [path]`: it detects ecosystems, lists direct vs transitive counts per ecosystem, and runs whatever scanners are installed. If no lockfile exists, stop and say so — auditing a manifest without a lockfile audits versions that may never have shipped. (Pair with `dependency_pinning` to fix that first.)
2. **Scan for vulnerabilities.** Run the ecosystem scanners listed in `references/scanner-commands.md` over the full resolved graph. Record the raw findings, then triage each advisory:
   - Severity (CVSS), plus EPSS score and CISA KEV listing when available. CVSS alone mis-ranks.
   - Direct or transitive? A direct dep is your decision; a transitive one needs a remediation path (overrides, resolutions, constraints, parent bump).
   - Reachable? Is the vulnerable code path actually called by your code? A CVE in an unused code path is a different beast from one in your request handler. `govulncheck` answers this properly; other tools do not. When you cannot determine reachability, write "not verified", never presume.
   - Dev or prod? Dev-only findings get a different threat model and their own section, not a mix-in.
3. **Check licenses.** Extract licenses for the full transitive *production* tree (`--production` / `--omit=dev`; build tooling is not distributed, exclude it). Compare against the project's allowlist (see `references/license-policy.md`). Flag: forbidden copyleft (GPL/AGPL in proprietary code, SSPL anywhere), unknown or missing licenses, and license *changes* between versions of a dep. A license switch upstream is a new risk.
4. **Score maintenance health.** For each direct dep (spot-check transitives): last release date, repo archived/deprecated status, commit activity, open-issue responsiveness. Release age alone does not prove abandonment, a stable finished library is fine. You need age *plus* silence: 4+ years since last release and no commits, or a stated deprecation/archival. Single-maintainer packages deep in the tree are a bus-factor risk; flag them.
5. **Find the prune list.** Candidates to cut: deps only used in dead code paths, multiple packages solving the same problem (two HTTP clients, three date libs), deps replaceable by stdlib builtins, packages with 50+ transitive deps doing a simple job, direct git/tarball references the audit tools cannot see. `npm ls` deprecation warnings and `npm outdated` / `pip list --outdated` / `cargo outdated` seed this list.
6. **Report.** Produce the audit report per the Output Contract below. Group findings into action tracks, not one panic list. Then remediate what the policy says to remediate, re-run the scanners, and confirm the findings cleared.

## Output Contract

A finished audit report contains:

- **Tree summary:** ecosystems found, direct count, transitive count, total unique packages, lockfile status.
- **Triage table** (one row per advisory): Package | Installed | Severity | CVE/GHSA id | Fixed in | Direct or transitive | Reachable in our code (yes/no/not verified) | Action.
- **License table:** license type | package count | status against policy; violations listed individually with remediation (replace, isolate, accept with sign-off).
- **Abandoned/deprecated table:** package | last release | signal (stated or inferred) | replacement if known.
- **Prune list:** package | why it can go (dead usage, duplicate, stdlib covers it) | effort estimate.
- **Accepted risks:** advisory or package, why it is accepted, owner, review date. An unrecorded acceptance is a lie of omission.
- **Tracks:** fix now (reachable High/Critical, license violations) | fix soon (unreachable/medium, unmaintained with replacement) | routine batch (patch/minor bumps) | monitor (slightly outdated, healthy).

## Operating Rules

1. **Lockfile or it did not happen.** The audit is only as real as the resolved tree. No committed lockfile means step one is `dependency_pinning`, not triage.
2. **Severity is not priority.** Rank by reachability and exploitability, not by the color of the badge. A reachable medium outranks an unreachable critical.
3. **Report reachability explicitly.** A report of 200 unreviewed advisories teaches everyone to ignore the scanner. Triaged and honest, or do not ship the report.
4. **Prod tree for licenses, full tree for vulns.** License policy judges what you distribute; vulnerability scanning covers everything the build touches.
5. **Unknown means investigate, never presume.** Unknown license, unknown reachability, unknown maintainer: flag it and verify. A silent presumption of safety is how incidents are born.
6. **Every accepted risk gets an owner and a date.** If nobody owns it and it never gets re-reviewed, it is not an acceptance, it is neglect.
7. **Install scripts are code.** `postinstall`/`preinstall` in an unfamiliar package gets read during the audit, not skimmed over.
8. **Watch the names.** Typosquats (one transposition off a popular package), sudden ownership transfers, and version jumps like 1.0.4 to 99.0.0 are supply-chain attacks, not CVEs. Scanners will not catch them. You do.
9. **Schedule it.** New advisories appear for old versions every day. Wire the scanner into CI to fail on new High/Critical, enable Dependabot/Renovate, and run this audit on a real cadence (monthly for active projects, quarterly at minimum).
