Dependency audit runbook¶
Two checks guard the revel-backend dependency graph:
pip-audit— queries the PyPI Advisory Database and fails on any known CVE in the resolved tree.licensecheck— walkspyproject.toml+uv.lockand fails on copyleft / source-available licenses (GPL, AGPL, SSPL, BUSL, Commons Clause) entering the tree.
Both run in two contexts:
- PR / push to
main—.github/workflows/deps.yamlruns both on any change that touchespyproject.toml,uv.lock,Makefile, or the workflow itself. - Nightly,
03:17 UTC—.github/workflows/nightly-audit.ymlre-runs both againstmainand opens-or-bumps a GitHub issue (labels:security,dependencies) on failure, so newly-disclosed advisories surface within ~24h without a PR.
Both PR jobs (licensecheck, pip-audit) are required status checks on main. Because deps.yaml is path-filtered, PRs that don't touch the dep graph get their passing status from the no-op twin .github/workflows/deps-noop.yaml instead (same workflow name, same job ids, inverse paths-ignore: filter — GitHub's documented pattern for skipped-but- required checks). Keep the two path lists exact complements when adding or removing a watched file, or non-dep PRs will hang on "Expected — Waiting for status to be reported".
Local reproduction¶
Neither target is part of make check — make check is kept fast and offline; dependency checks hit the network and live outside that loop.
Triage: nightly issue filed¶
pip-audit¶
- Open the linked Actions run from the issue body to see the advisory ID (
GHSA-xxxx-xxxx-xxxx/PYSEC-…) and affected package/version. - Read the advisory. Decide:
- Bump — preferred when a fixed version exists.
uv lock --upgrade-package <pkg>(oruv add <pkg>@<version>for a direct dep), verifymake test, open a PR. - Suppress — only if the advisory is withdrawn, not actually exploitable in our context, or there is no fix yet. Add
--ignore-vuln <ID>to theaudittarget in the Makefile with a comment explaining the rationale. The workflow invocations callmake audit, so the suppression flows through to CI automatically. Re-review suppressions quarterly. - Close the tracker issue when the PR lands (or link the suppressed-vuln PR).
Current grandfathered suppressions live at the top of the audit target in Makefile alongside their rationale.
Gotcha: -r pyproject.toml¶
The licensecheck target passes -r pyproject.toml explicitly. Do not drop that flag. When licensecheck runs without it and stdin is not a tty (i.e. make, CI, any pipeline), it treats __stdin__ as the requirements source and silently falls back to a partial graph. With the flag, the uv resolver walks [project.dependencies] plus the configured [tool.licensecheck].groups and you get the full 280+ package tree.
Gotcha: pip-audit --disable-pip in CI¶
The audit target passes --disable-pip alongside --no-deps. pip-audit -r always spawns a fresh venv and runs python -m ensurepip inside it; uv's managed Python doesn't ship ensurepip, so the step aborts with exit 127 on CI runners that use setup-uv. --disable-pip skips the bootstrap entirely (safe because with --no-deps we don't need pip to resolve). Locally this may mask if your system Python has ensurepip, but CI always needs it.
licensecheck¶
- Open the Actions run and identify which package triggered the fail (the tool prints the package, its license, and why it matched
fail_licenses). - Decide:
- Replace / bump — preferred. Swap for a permissively-licensed alternative or upgrade to a version that has been relicensed.
- Grandfather — if the package is load-bearing and no alternative exists, add it to
[tool.licensecheck].ignore_packagesinpyproject.tomlwith an inline comment explaining why the license is acceptable in our deployment model (e.g.# <pkg>: AGPL, but used only as a CLI-separate process, not linked). - Close the tracker issue when the PR lands.
Label taxonomy¶
The nightly workflow applies these labels when opening or bumping an issue:
security— advisory / compliance impactdependencies— third-party code
Both labels are required to exist in the repo for the issue-creation step to succeed.
Policy summary¶
- Fail list is copyleft-only. Permissive unknowns (PSF-2.0, ISC variants, BSD-3-Clause-Clear, etc.) warn but don't fail (
zero = truewith noonly_licensescatch-all). This is deliberate — maintaining an explicit allow-list of every permissive SPDX ID generates churn without safety gain. - Grandfathering is explicit. Every entry in
ignore_packages/--ignore-vulncarries a rationale comment. Drive-by additions without a comment fail review. - Frontend is out of scope.
revel-frontend/dep scanning is handled (or not) separately; this runbook coversrevel-backend/only.