Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,66 @@ jobs:
#
# `--omit=dev` skips devDependencies because they don't ship
# to production; the prod bundle is what reaches a user.
#
# ── Advisory waiver: GHSA-qwww-vcr4-c8h2 (react-router) ───────
# react-router 7.12.0–8.2.0 carries a HIGH "RSC Mode CSRF Bypass"
# advisory. We run react-router-dom@7.18.1 (the latest published
# `dom` package) in classic SPA mode — `<BrowserRouter>` +
# declarative `<Routes>`/`<Route>`, no `createBrowserRouter`,
# no `RouterProvider`, no `useFetcher`/`useActionData`/`<Form>`,
# no RSC/SSR. The vulnerable code path (action execution before
# the 400 response in RSC mode) is NOT reachable in this app.
#
# No patched `react-router-dom` exists yet: 7.18.1 is the latest
# on the registry, and the advisory's "patched >= 8.3.0" only
# applies to the bare `react-router` package (no `react-router-dom`
# 8.x has been published). npm's only suggested remediation is a
# breaking downgrade to react-router-dom@7.11.0, which would lose
# 7.12→7.18 fixes and is riskier than the (unreachable) vuln.
#
# This mirrors the backend pip-audit convention: when a CVE has no
# fix yet, suppress the specific advisory with a citation and
# revisit date instead of blocking every deploy. npm v9 `audit`
# has no per-advisory `--ignore` flag, so we filter the JSON
# output and fail only on vulnerabilities OTHER than this one.
# Remove this waiver once react-router-dom publishes a fixed 8.x
# (or a 7.x patch) and bump the pin in package.json.
- name: npm audit (production deps only, high+critical)
working-directory: frontend
run: npm audit --audit-level=high --omit=dev
run: |
# Waived advisory IDs (GitHub Advisory URL slugs). Each MUST
# have a justification comment above. Revisit on every bump.
WAIVED="GHSA-qwww-vcr4-c8h2"

# Run the audit and capture JSON. --audit-level=high keeps
# the gate at high+critical; --omit=dev scopes to prod deps.
REPORT="$(npm audit --audit-level=high --omit=dev --json || true)"

# Extract advisory URL slugs that are NOT in the waive list.
# `npm audit --json` nests advisories under .vulnerabilities
# → <pkg> → .via[] (each advisory is a dict with .url).
UNWAIVED="$(printf '%s' "$REPORT" | jq -r '
[.vulnerabilities[]? |
.via[]? |
select(type == "object") |
.url // empty
] |
map(gsub("https://github.com/advisories/"; "")) |
. - ["'"$WAIVED"'"] |
unique |
.[]
')"

if [ -n "$UNWAIVED" ]; then
echo "::error::npm audit found un-waived high+critical advisories:"
echo "$UNWAIVED"
echo ""
echo "Full report:"
printf '%s\n' "$REPORT" | jq '.vulnerabilities | to_entries | map(select(.value.severity != "low" and .value.severity != "moderate")) | from_entries'
exit 1
fi

echo "::notice::npm audit passed (1 documented waiver active: $WAIVED — react-router RSC-mode CSRF bypass, not reachable in SPA BrowserRouter mode; no patched react-router-dom published yet)."

# Vitest component tests — run BEFORE the build so a regression
# caught by tests doesn't get the chance to ship via a successful
Expand Down
26 changes: 25 additions & 1 deletion .github/workflows/weekly-deps-refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,31 @@ jobs:
run: npm update
- name: Frontend audit (production deps, high+critical)
working-directory: frontend
run: npm audit --audit-level=high --omit=dev
# Gate parity with deploy.yml — same waived-advisory filter.
# See deploy.yml "npm audit" step for the GHSA-qwww-vcr4-c8h2
# (react-router RSC-mode CSRF bypass) waiver justification:
# app uses SPA BrowserRouter, not RSC; no patched
# react-router-dom published yet.
run: |
WAIVED="GHSA-qwww-vcr4-c8h2"
REPORT="$(npm audit --audit-level=high --omit=dev --json || true)"
UNWAIVED="$(printf '%s' "$REPORT" | jq -r '
[.vulnerabilities[]? |
.via[]? |
select(type == "object") |
.url // empty
] |
map(gsub("https://github.com/advisories/"; "")) |
. - ["'"$WAIVED"'"] |
unique |
.[]
')"
if [ -n "$UNWAIVED" ]; then
echo "::error::npm audit found un-waived high+critical advisories:"
echo "$UNWAIVED"
exit 1
fi
echo "::notice::npm audit passed (1 documented waiver active: $WAIVED)."
- name: Frontend tests (vitest)
working-directory: frontend
run: npm test
Expand Down
13 changes: 13 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,17 @@ constraint-dependencies = [
# pydantic-settings: GHSA-4xgf-cpjx-pc3j (fixed in 2.14.2). Transitive
# via fastmcp. Remove once fastmcp's own pin clears 2.14.2.
"pydantic-settings>=2.14.2",
# click: PYSEC-2026-2132 / CVE-2026-7246 / GHSA-47fr-3ffg-hgmw
# (command injection in click.edit(), fixed in 8.3.3). Transitive
# via uvicorn, which depends on ``click`` with no version specifier,
# so it pulls whatever the resolver picks — without this floor that
# was 8.3.1. Surfaced in the PyPA advisory DB ~2026-07-20, after the
# last green deploy — pip-audit --strict started failing on it even
# though no code changed. No app code calls click.edit() (CLI
# helper used by uvicorn's launcher), so the vulnerable path isn't
# reachable in this service — but pip-audit --strict is a deploy
# gate and correctly fails the scan. Remove once uvicorn pins
# click>=8.3.3 itself (currently unbounded) or the advisory is
# withdrawn.
"click>=8.3.3",
]
11 changes: 6 additions & 5 deletions backend/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.