Commit lockfiles and wire pip-audit / npm audit into CI #72

Closed
opened 2026-07-28 05:57:15 +00:00 by claude-bot · 2 comments

Severity: HIGH (supply chain / reproducibility)

The problem

Neither project has a committed lockfile.

  • backend/pyproject.toml uses >= lower bounds only. No requirements.txt, no uv.lock, no
    poetry.lock — confirmed absent.
  • frontend/package.json uses ^ ranges, and no package-lock.json is tracked in git —
    confirmed via git ls-files.

Why it matters

Builds are not reproducible: two installs a week apart produce different dependency trees. You
cannot audit what is actually deployed, or answer "am I affected by advisory X," without SSHing
to the host. A compromised or yanked upstream is picked up silently.

For an application holding irreplaceable data, this is a more durable problem than any single
CVE — and Renovate PR #1 can only be useful once lockfiles exist for it to update.

Installed versions at audit time

Read from the dev server, so accurate for that host only: Authlib 1.6.10, Pillow 12.2.0,
FastAPI 0.121.3, Starlette 0.49.4, SQLAlchemy 2.0.45, Pydantic 2.12.4, uvicorn 0.38.0,
itsdangerous 2.2.0, python-multipart 0.0.20, Vite 7.3.2, esbuild 0.27.7, React 19.2.0,
react-router-dom 7.9.6, TanStack Query 5.90.5, TypeScript 5.9.3. All recent releases.

The CVE question is unresolved

The audit's CVE section was model recall, explicitly unverified. It must be confirmed by
running the real tools. Priority order for review once output exists:

  • Authlib — performs all ID token validation; the JWS/JWT algorithm-confusion class matters most
  • Pillow — parses attacker-supplied uploads
  • Vite / esbuild — dev-server advisories, relevant because the dev server runs on the LAN
  • react-router, python-multipart

Fix

  • Commit lockfiles for both projects (uv lock or pip-compile; npm install committing
    package-lock.json).
  • Add pip-audit and npm audit to CI (#13), failing on high severity.
  • Run both against the production host and triage the real output.
  • Then merge Renovate PR #1.

Done when

  • Lockfiles are committed for backend and frontend
  • pip-audit and npm audit run in CI and gate the build
  • Their output has been triaged and any real advisories are filed as issues
  • Renovate PR #1 is merged and updating locked versions

References

  • backend/pyproject.toml, frontend/package.json
  • Open PR #1 (Renovate onboarding)

Related: #13 (CI pipeline).

## Severity: HIGH (supply chain / reproducibility) ## The problem Neither project has a committed lockfile. - `backend/pyproject.toml` uses `>=` lower bounds only. No `requirements.txt`, no `uv.lock`, no `poetry.lock` — confirmed absent. - `frontend/package.json` uses `^` ranges, and no `package-lock.json` is tracked in git — confirmed via `git ls-files`. ## Why it matters Builds are not reproducible: two installs a week apart produce different dependency trees. You cannot audit what is actually deployed, or answer "am I affected by advisory X," without SSHing to the host. A compromised or yanked upstream is picked up silently. For an application holding irreplaceable data, this is a more durable problem than any single CVE — and Renovate PR #1 can only be useful once lockfiles exist for it to update. ## Installed versions at audit time Read from the dev server, so accurate for that host only: Authlib 1.6.10, Pillow 12.2.0, FastAPI 0.121.3, Starlette 0.49.4, SQLAlchemy 2.0.45, Pydantic 2.12.4, uvicorn 0.38.0, itsdangerous 2.2.0, python-multipart 0.0.20, Vite 7.3.2, esbuild 0.27.7, React 19.2.0, react-router-dom 7.9.6, TanStack Query 5.90.5, TypeScript 5.9.3. All recent releases. ## The CVE question is unresolved The audit's CVE section was model recall, explicitly **unverified**. It must be confirmed by running the real tools. Priority order for review once output exists: - **Authlib** — performs all ID token validation; the JWS/JWT algorithm-confusion class matters most - **Pillow** — parses attacker-supplied uploads - **Vite / esbuild** — dev-server advisories, relevant because the dev server runs on the LAN - **react-router**, **python-multipart** ## Fix - Commit lockfiles for both projects (`uv lock` or `pip-compile`; `npm install` committing `package-lock.json`). - Add `pip-audit` and `npm audit` to CI (#13), failing on high severity. - Run both against the production host and triage the real output. - Then merge Renovate PR #1. ## Done when - [ ] Lockfiles are committed for backend and frontend - [ ] `pip-audit` and `npm audit` run in CI and gate the build - [ ] Their output has been triaged and any real advisories are filed as issues - [ ] Renovate PR #1 is merged and updating locked versions ## References - `backend/pyproject.toml`, `frontend/package.json` - Open PR #1 (Renovate onboarding) Related: #13 (CI pipeline).
claude-bot added this to the v0.1.1 milestone 2026-07-28 05:57:15 +00:00
Author

Lockfiles done in 8a28bbb. The audit tooling is still outstanding, so leaving this open.

Committed:

  • frontend/package-lock.json (1796 lines)
  • backend/requirements.txt and backend/requirements-dev.txt, generated by pip-compile from
    pyproject.toml with --strip-extras

CI now installs from these rather than resolving ranges, so builds are reproducible and npm ci
works. Renovate can manage pinned versions rather than just bumping ranges in a manifest.

Note the workflow: edit pyproject.toml, then regenerate, never edit requirements*.txt by
hand. pip-compile writes a header recording the command, and Renovate's pip-compile manager
relies on it.

Still to do for this issue:

  • pip-audit step in the backend CI job
  • npm audit step in the frontend CI job
  • Triage the real output and file anything genuine
  • Merge Renovate PR #1 (now useful, since lockfiles exist for it to update)

The CVE list in the original audit was explicitly unverified model recall — treat the tool output as
authoritative over it.

**Lockfiles done** in 8a28bbb. The audit tooling is still outstanding, so leaving this open. Committed: - `frontend/package-lock.json` (1796 lines) - `backend/requirements.txt` and `backend/requirements-dev.txt`, generated by `pip-compile` from `pyproject.toml` with `--strip-extras` CI now installs from these rather than resolving ranges, so builds are reproducible and `npm ci` works. Renovate can manage pinned versions rather than just bumping ranges in a manifest. Note the workflow: **edit `pyproject.toml`, then regenerate**, never edit `requirements*.txt` by hand. `pip-compile` writes a header recording the command, and Renovate's `pip-compile` manager relies on it. Still to do for this issue: - [ ] `pip-audit` step in the backend CI job - [ ] `npm audit` step in the frontend CI job - [ ] Triage the real output and file anything genuine - [ ] Merge Renovate PR #1 (now useful, since lockfiles exist for it to update) The CVE list in the original audit was explicitly unverified model recall — treat the tool output as authoritative over it.
Author

Done in 709b983. Lockfiles were already committed; this closes the audit half and, importantly, actually ran the tools — the audit's CVE section was explicitly unverified recall, so the first job was finding out what is true.

Real output

pip-audit: clean on both requirements.txt and requirements-dev.txt. No advisories at all. So the Authlib / python-multipart concerns in the issue body were not borne out — good news, but now verified rather than assumed.

npm audit: six findings, four high. Five are real and fixed by a lockfile refresh within existing semver ranges — no code changes; typecheck and build verified:

Package Was → Now Advisories
react-router 7.14.1 → 7.18.2 7 of 8, incl. unauthenticated RCE via vendored turbo-stream deserialization, open redirect, XSS, DoS
vite 7.3.2 → 7.3.6 server.fs.deny bypass on Windows alternate paths; launch-editor NTLMv2 hash disclosure via UNC handling
postcss 8.5.10 → 8.5.25 arbitrary file read via attacker-controlled sourceMappingURL
esbuild 0.27.7 → 0.28.1 dev-server arbitrary file read on Windows
@babel/core (transitive) sourceMappingURL arbitrary file read

Your prioritisation was right about where to look — Vite/esbuild dev-server advisories matter because the dev server runs on the LAN — but the biggest one was react-router, which the original list did not flag.

The sixth needs judgement, not a bump. GHSA-qwww-vcr4-c8h2, high, CSRF bypass in React Router's RSC mode. That requires a React Router server runtime handling server actions; Circa's frontend is a static Vite build with a FastAPI backend — BrowserRouter, declarative <Routes>, no SSR, no framework mode, no data router, no actions. Unreachable. The CSRF control here is server-side anyway (CSRFOriginMiddleware, #63). The fix exists only in react-router 8.3.0, a major that renovate.json deliberately holds for review. Filed as #132 (v0.2.0) rather than forced through — pushing a framework major under a CVE that does not apply to us is how working software breaks.

The CI gate

This raised a real question: how does CI stay honest with a known-inapplicable high outstanding? npm audit has no ignore mechanism, so the usual answers are lowering --audit-level (blinds the build to the next real finding at that severity) or || true (blinds it to everything). Both trade the control for a green tick.

frontend/scripts/audit.mjs filters npm audit --json by advisory id against frontend/audit-allowlist.json, and is deliberately hostile to its own allowlist:

  • an entry past its reviewBy date fails the build — this one expires 2026-10-31 and CI will start refusing merges until someone re-decides
  • an entry matching nothing in the current report fails the build — an exception must not outlive the advisory it was written for and silently cover a future one

I tested all three failure modes by hand before committing, including the empty-allowlist case proving the gate genuinely blocks the react-router finding.

pip-audit is pinned via requirements-dev.txt rather than pip installed in the workflow — fetching a security tool unpinned at build time is its own supply-chain risk. Both audits run against the locked set, not the installed set, so the result describes what a deployment would actually get. I replayed both CI jobs end to end in the exact images (node:22-bookworm-slim + python3, node:22-slim) before pushing.

"Done when"

  • Lockfiles committed for backend and frontend
  • pip-audit and npm audit run in CI and gate the build
  • Output triaged; the one non-actionable advisory is documented with an expiry and filed as #132
  • Renovate PR #1 merged — over to you, I have not touched it

That last box is the only thing outstanding on this issue, and it needs you rather than me. Closing on the basis that the tooling and triage are done; reopen if you would rather it stay open until #1 lands.

Done in 709b983. Lockfiles were already committed; this closes the audit half and, importantly, **actually ran the tools** — the audit's CVE section was explicitly unverified recall, so the first job was finding out what is true. ## Real output **`pip-audit`: clean** on both `requirements.txt` and `requirements-dev.txt`. No advisories at all. So the Authlib / python-multipart concerns in the issue body were not borne out — good news, but now verified rather than assumed. **`npm audit`: six findings, four high.** Five are real and fixed by a lockfile refresh **within existing semver ranges** — no code changes; typecheck and build verified: | Package | Was → Now | Advisories | |---|---|---| | `react-router` | 7.14.1 → 7.18.2 | 7 of 8, incl. **unauthenticated RCE** via vendored turbo-stream deserialization, open redirect, XSS, DoS | | `vite` | 7.3.2 → 7.3.6 | `server.fs.deny` bypass on Windows alternate paths; launch-editor NTLMv2 hash disclosure via UNC handling | | `postcss` | 8.5.10 → 8.5.25 | arbitrary file read via attacker-controlled `sourceMappingURL` | | `esbuild` | 0.27.7 → 0.28.1 | dev-server arbitrary file read on Windows | | `@babel/core` | (transitive) | `sourceMappingURL` arbitrary file read | Your prioritisation was right about **where** to look — Vite/esbuild dev-server advisories matter because the dev server runs on the LAN — but the biggest one was react-router, which the original list did not flag. **The sixth needs judgement, not a bump.** [GHSA-qwww-vcr4-c8h2](https://github.com/advisories/GHSA-qwww-vcr4-c8h2), high, CSRF bypass in React Router's **RSC mode**. That requires a React Router server runtime handling server actions; Circa's frontend is a static Vite build with a FastAPI backend — `BrowserRouter`, declarative `<Routes>`, no SSR, no framework mode, no data router, no actions. Unreachable. The CSRF control here is server-side anyway (`CSRFOriginMiddleware`, #63). The fix exists only in react-router **8.3.0**, a major that `renovate.json` deliberately holds for review. Filed as **#132** (v0.2.0) rather than forced through — pushing a framework major under a CVE that does not apply to us is how working software breaks. ## The CI gate This raised a real question: how does CI stay honest with a known-inapplicable high outstanding? `npm audit` has no ignore mechanism, so the usual answers are lowering `--audit-level` (blinds the build to the next *real* finding at that severity) or `|| true` (blinds it to everything). Both trade the control for a green tick. `frontend/scripts/audit.mjs` filters `npm audit --json` by advisory id against `frontend/audit-allowlist.json`, and is **deliberately hostile to its own allowlist**: - an entry past its `reviewBy` date **fails the build** — this one expires **2026-10-31** and CI will start refusing merges until someone re-decides - an entry matching nothing in the current report **fails the build** — an exception must not outlive the advisory it was written for and silently cover a future one I tested all three failure modes by hand before committing, including the empty-allowlist case proving the gate genuinely blocks the react-router finding. `pip-audit` is pinned via `requirements-dev.txt` rather than `pip install`ed in the workflow — fetching a security tool unpinned at build time is its own supply-chain risk. Both audits run against the **locked** set, not the installed set, so the result describes what a deployment would actually get. I replayed both CI jobs end to end in the exact images (`node:22-bookworm-slim` + python3, `node:22-slim`) before pushing. ## "Done when" - [x] Lockfiles committed for backend and frontend - [x] `pip-audit` and `npm audit` run in CI and gate the build - [x] Output triaged; the one non-actionable advisory is documented with an expiry and filed as #132 - [ ] **Renovate PR #1 merged** — over to you, I have not touched it That last box is the only thing outstanding on this issue, and it needs you rather than me. Closing on the basis that the tooling and triage are done; reopen if you would rather it stay open until #1 lands.
Sign in to join this conversation.
No description provided.