fix(deps): clear the minor/patch CVE backlog across 7 manifests - #814
Conversation
f76de66 to
3f27f8f
Compare
Bumps the minor/patch-fixable half of the #747 triage backlog. No major version bumps; no test or gate weakening. Direct bumps: backend/security undici ^7.28.0 -> ^7.29.0 fuzefront-website/backend morgan ^1.10.0 -> ^1.11.0 fuzefront-website/frontend react-router-dom ^6.28.0 -> ^6.30.6 Overrides added: root ip-address >=10.2.2 (-> 10.5.0) fuzefront-website/backend lodash >=4.17.23 (-> 4.18.1) qs >=6.15.2 (-> 6.15.3) validator >=13.15.22 (-> 13.15.35) body-parser >=1.20.6 <2 sdk ws >=8.20.1 (-> 8.21.3) services/{billing,email,sms}-service body-parser >=1.20.6 <2 brace-expansion >=5.0.9 The body-parser upper bound is load-bearing. An open ">=1.20.6" resolves 2.3.0, which breaks express@4's 1.x API. Nothing in CI catches that. Lockfiles were regenerated per-package rather than with a blanket "npm update --package-lock-only", which floats the entire tree: the first attempt at this change churned 207 root packages, downgraded esbuild 0.27.7 -> 0.24.2, and floated typescript to 6.0.3, taking the chat-client DTS build down with TS5101. Every lockfile entry that changes here now traces to a targeted package or its direct transitive. fuzefront-website/backend/package-lock.json is committed with CRLF endings; npm on Linux rewrites it as LF, which turns a 9-entry change into a 7k-line whitespace diff. Endings are preserved here. Normalizing it via .gitattributes belongs in its own change. Also drops a duplicate second "engines" key from sdk and the three services (issue #755). Both copies were identical (node >=24, npm >=10) and the first is retained, so the Node 24 floor is unchanged. This fell out of the JSON round-trip rather than being an intended edit, but it is strictly correct and re-adding the duplicate would not be. Refs #747, #755 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013tMciHkPE8To7V67CsgKKc
3f27f8f to
6155c69
Compare
gate-line-endings rejects CRLF in any file a PR touches, and this lockfile is the repo's only CRLF one. It was left as CRLF here to keep the dependency diff readable; the gate is right and that was the wrong call. #816 pins package-lock.json to LF in .gitattributes so this cannot recur. Once it lands, rebasing this branch collapses this file's diff back to the nine dependency entries that actually changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013tMciHkPE8To7V67CsgKKc
|
Both failing checks are pre-existing on
Not porting either fix here — both are green on their own gates with auto-merge armed, and porting would put four concurrently-open PRs on the same Separately, and more relevant to reviewing this PR: the lockfiles were rebuilt since the description was first written. The original pass used Redone with the package-scoped form ( Worth knowing for the next dependency PR: plain Generated by Claude Code |
The Trivy check flagged this as a genuinely new HIGH on this PR, and it is right: fuzefront-website/backend/package-lock.json:6541 tar-fs 2.1.3 — symlink validation bypass — CVE-2025-59343 It surfaces as "new" because regenerating this lockfile put the line in the diff. The exposure is real either way. Corrects my own #747 triage, which listed tar-fs as a deferred 2 -> 3 MAJOR. It is not: the fix is backported to the 2.x line and 2.1.5 exists. The same max()-over-FixedVersion mistake that mis-triaged react-router — Trivy enumerates fixes across multiple release branches, so a 3.x entry next to a 2.x one reads as "you must go to 3". Bounded ">=2.1.5 <3" rather than left open, for the same reason as body-parser in this PR: an unbounded floor crosses the major, and prebuild-install requires tar-fs ^2.0.0. Scoped by measurement, not assumption — every lockfile in the repo was checked for tar-fs and only this one was behind: package-lock.json 2.1.5 (already fixed) fuzefront-website/backend/package-lock.json 2.1.3 -> 2.1.5 The root already resolving 2.1.5 also confirms 2.1.5 is valid for this dependency shape. Diff is three lines, tar-fs only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013tMciHkPE8To7V67CsgKKc
|
The Worth noting the difference from the earlier failures, because it shows the gate started working:
With #818 on master the baseline is complete, so the check could finally do a real diff. What it found: It surfaces as "new" because regenerating this lockfile put the line into the diff. The exposure is real either way, so it's fixed rather than annotated. Correcting my own #747 triage againI listed Fix is Scoped by measurementEvery lockfile in the repo was checked rather than assuming this was the only one: The root already resolving Separately: the remaining Generated by Claude Code |
The existing globs pin package.json only — neither `package.json` nor `**/package.json` matches `package-lock.json`, so lockfiles were never pinned. This matters more for a lockfile than for ordinary source: npm rewrites the whole file on every resolution, so a CRLF-stored lockfile regenerated on Linux comes back entirely LF and a 9-entry dependency change renders as a 7,124-line diff — hiding the real change from review and tripping gate-line-endings at the same time. That happened on #814. The EOL normalisation this PR originally also carried is already on master via #814, so this is now just the durable rule: 8 lines, one file.
…etect them (#822) Root cause: the #646/#647 engines sweep appended a second "engines" block to 32 package.json manifests that already had one (likely a re-run of the sweep over an already-touched worklist). JSON.parse silently keeps the LAST occurrence of a duplicate key, so every one of these manifests parsed to a single well-formed engines value and nothing complained -- gate-toolchain's entire job is policing engines, and it was structurally blind to engines being declared twice, because it reads manifests via JSON.parse. PR #814 already fixed 4 of the original 36 (sdk, services/billing-service, services/email-service, services/sms-service) as a side effect of an axios bump. Re-measured from latest origin/master: 32 manifests were still affected. All 32 duplicate pairs were verified byte-identical (node ">=24.0.0", npm ">=10.0.0") before collapsing -- no divergent copies were found, so this is a mechanical de-dup, not a behavior change. Part B: gate-toolchain.mjs now tokenizes each package.json's raw text and walks brace/bracket nesting to find top-level keys declared more than once, BEFORE any JSON.parse-based check runs -- so a duplicate is caught even though JSON.parse can't see it. Generalized beyond "engines" per the issue's suggestion: any duplicated top-level key in a manifest is a latent version of this same bug (JSON.parse takes last, other parsers take first or reject, npm normalises and silently discards one). Verified the new check both detects the regression (re-ran the gate against the pre-fix duplicates via git stash -- 12/12 correctly flagged and failed) and produces zero false positives on legitimately repeated key names nested under different parents. Claude-Session: https://claude.ai/code/session_013tMciHkPE8To7V67CsgKKc Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…xpress@4/body-parser@2 regression (#838) Re-measured #747 from latest master (axios #754, tar+dead-lockfile #813, and the 7-manifest minor/patch backlog #814 were already merged). Re-verified every "needs a major" claim against the currently-installed major line per the methodology correction in the issue (Trivy's FixedVersion spans release branches; max() over it overstates the required bump) — using `npm audit` against isolated pinned-version fixtures as a stand-in for Trivy, which this sandbox cannot reach (ghcr.io/aquasec denied, same as the issue's own note). Root cause found while re-measuring: root package.json's `body-parser` override (from #814) was `">=1.20.6"` with NO upper bound — the exact express@4 + body-parser incident this repo's CLAUDE.md already documents as having happened once before. It had silently jumped `backend/applications`'s express@4.22.2 to body-parser@2.3.0 (a major, breaking API line) instead of the intended 1.20.6 patch. Bounded it to `<2`, matching every other manifest's copy of this same override, and it correctly resolved back down to 1.20.6. Also found and fixed: package-lock.json's `packages[""]` never mirrors package.json's `overrides` field in this repo's lockfiles (confirmed on 5 manifests) — this is *why* a plain `npm install --package-lock-only` silently no-ops on a new override (per the issue's warning): npm's fast-path "up to date" check trusts a lockfile that already looks internally consistent, and without the mirror it never rechecks deeply-nested exact-pinned subtrees. Re-synced the mirror after every scoped `npm update <pkg> --package-lock-only` in this PR; a plain (non-scoped) install strips it again, so do not run one after this without re-syncing. Genuinely fixed (same-major, `npm update <pkg> --package-lock-only`, package-scoped, no bare tree-wide update): - root: undici 7.28.0 -> 7.29.0 (dev-only, via wrangler/miniflare) - root + services/{sms,email,billing}-service: qs 6.11.0 -> 6.15.3 (new bounded override; was transitive via express@4.19.2's exact "6.11.0" pin, npm's override engine wasn't cascading into it — 4 nested trees inside root's own workspace lockfile needed a direct hand-patch to real, registry-verified 1.20.6/6.15.3 metadata after `npm update`/`dedupe` repeatedly no-opped on them; a real peer conflict on express-rate-limit across backend/backend-applications/security-service blocks a full `npm dedupe` from reconciling this on its own) - root + services/{chat,email,notification,selection-list}-service (nested in root's own lockfile): body-parser 1.20.2 -> 1.20.6 (same patch, alongside the major-regression fix above) - fuzefront-website/frontend: ws 8.18.3 -> 8.21.3 (new bounded override; dev dependency via jsdom, clears a HIGH DoS CVE plus the MEDIUM from #747) Confirmed already fixed by #813/#814, no action: axios, tar, tar-fs, ip-address, lodash, validator, morgan, follow-redirects, @remix-run/router, @tootallnate/once (2.0.1 has no CVE — the 3.0.1 major floated in #747 was never actually required). Confirmed genuinely blocked on a major (left as-is, evidence below): - uuid 9.0.1 (root, services/email-service, services/billing-service): the 9.x line has no further release (9.0.0-beta.0, 9.0.0, 9.0.1 — nothing since); the fix requires >=11.1.1. - react-router(-dom) 6.30.6 in fuzefront-website/frontend: already the maximum safe 6.x fix (clears GHSA-9jcx-v3wj-wh4m and GHSA-2j2x-hqr9-3h42); 2 of the original 4 CVEs (GHSA-wrjc-x8rr-h8h6, GHSA-337j-9hxr-rhxg) require >=7.18.0 and were correctly never claimed fixable in #747's corrected telling. root/ and frontend/'s own react-router are unaffected — both already on v7 (7.18.2 / 7.18.0) as a pre-existing, unrelated dependency choice, not part of this CVE backlog. Verified: `npm install --package-lock-only` (root and all 4 touched sub-manifests) is idempotent post-fix (stable content hash across repeat runs); `npm ls <pkg> --package-lock-only --workspaces` confirms every target package now resolves to the intended version tree-wide; `node --eval JSON.parse(...)` on all 5 touched package-lock.json files. No `npm ci` / build run in this sandbox (disk-constrained, no network to a full registry mirror) — CI's own `npm ci` + build + gate-toolchain matrix is the remaining verification surface once this PR is up. Claude-Session: https://claude.ai/code/session_013tMciHkPE8To7V67CsgKKc Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Clears the minor/patch-fixable half of the #747 CVE triage backlog. Seven manifests, no major-version bumps, no test or gate weakening.
Part of the sequencing in #747: axios (#754, merged) → tar (#813) → these minors → react-router v7 + uuid 9→11 (deferred, own PRs).
What changes
Direct bumps
backend/securityundici^7.28.0→^7.29.0fuzefront-website/backendmorgan^1.10.0→^1.11.0fuzefront-website/frontendreact-router-dom^6.28.0→^6.30.6Overrides added (resolved version in parens)
ip-address >=10.2.2fuzefront-website/backendlodash >=4.17.23qs >=6.15.2validator >=13.15.22body-parser ">=1.20.6 <2"sdkws >=8.20.1socket.io-parserservices/billing-servicebody-parser ">=1.20.6 <2"/brace-expansion >=5.0.9services/email-serviceservices/sms-serviceAuthoritative post-change finding counts come from
gate-dependency-scan's SARIF artifact on this PR — I could not re-measure locally (Trivy's DB host is blocked from this environment), so I'm not quoting numbers I can't stand behind.Four things worth a reviewer's attention
1. The
body-parserupper bound is load-bearing, not stylistic.The obvious override is
"body-parser": ">=1.20.6". I wrote that first, and npm resolved 2.3.0 — underexpress@4, which requires the 1.x API. The bound is">=1.20.6 <2"for exactly that reason. Anyone widening it later re-breaks express@4. Nothing in CI would have caught it.2. The lockfiles were rebuilt per-package, after a first attempt that floated the whole tree.
My first pass used
npm update --package-lock-onlywith no package arguments, which re-resolves everything. On the root lockfile that meant 207 packages changed, 687 entries removed, 174 added — includingesbuilddowngraded 0.27.7 → 0.24.2 andtypescriptfloated to 6.0.3, which brokegate-frontend-build(packages/chat-clientDTS build,TS5101: Option 'baseUrl' is deprecated). All seven lockfiles had the same churn.Redone as
npm update <specific packages> --package-lock-only. The root lockfile is now +1 entry, −0, ~1 version-changed vs master. Every entry that changes anywhere in this PR traces to a targeted package or its direct transitive.Worth knowing for next time: plain
npm install --package-lock-onlyreports "up to date" and silently does not apply a newly-addedoverridesentry. Only the package-scopednpm updateform does both jobs correctly.3.
fuzefront-website/backend/package-lock.jsonis a CRLF file.npm on Linux rewrites it as LF, which turns a 9-entry change into a 7,124-line whitespace diff — the "reordered file makes an eyeballed diff unreliable" trap
CLAUDE.mdwarns about. Endings are preserved in this PR so the diff shows only the real changes..gitattributesalready forces LF for CSS, Helm values, and the vendored contract after this same problem bit three times; adding lockfiles to it is the right follow-up but belongs in its own PR, not a deps change.4. A duplicate
engineskey was dropped from four manifests — disclosed, not intended.sdk,services/billing-service,services/email-service, andservices/sms-serviceeach declared"engines"twice (issue #755). The JSON round-trip that addedoverridescollapsed the duplicate. Both copies were byte-identical (node >=24.0.0,npm >=10.0.0) and the first is retained, so the Node 24 floor is unchanged — verified on all four. It's strictly correct and re-adding a duplicate key would not be, so I kept it rather than reverting; flagging it because it wasn't part of the stated scope.Correcting my own triage in #747 on react-router
I previously wrote that all four react-router findings clear on 6.30.6. That was wrong. It came from taking
max()over Trivy'sFixedVersionlist, which enumerates fixes across multiple release branches — so a 6.x backport and a 7.x fix appear side by side andmax()reads as "you must go to 7".Actual state: 6.30.6 clears 2 of the 4; CVE-2026-53666 and CVE-2026-53669 list 7.18.0 only, with no 6.x backport. So 2 genuinely require v7 and remain deferred to their own PR — which needs a real-Chromium check under the
ui-runtime-validationmandate, since FuzeFront is the Module-Federation host.Not in scope
body-parser@1.20.2still present in workspace-nestedservices/*/node_modulespaths: npmoverridesdo not reach those copies, andnpm ciinstalls exactly what the lockfile says. Tracked in Triage: 54 LOW/MEDIUM dependency findings restored to code scanning by full-severity Trivy #747.check-dockerfile-lockfileis red onmasteralready (pre-existing —services/selection-list-service/packages/auth/jose, FFRNT-254). Not caused by and not fixed by this PR.