From e91a7cde7a0e14e94bd9b058b937ed9e5ad532d2 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 26 Jul 2026 02:21:11 +0200 Subject: [PATCH] fix(deps): patch transitive runtime CVEs via pnpm overrides (#1258) Closes 13 runtime-scope Dependabot advisories that Dependabot cannot fix itself: every vulnerable package here is transitive, so the bot has no lever until each parent publishes a release widening its own pin. Overrides added (pinned-source form, matching existing convention): - @xmldom/xmldom 0.8.11 -> 0.8.13 (5 high) via video.js -> mpd-parser - fast-uri 3.1.0 -> 3.1.4 (4 high) via electron-conf -> ajv - js-yaml 4.1.1 -> 4.3.0 (2) via electron-updater - form-data 4.0.5 -> 4.0.6 (1 high) via axios - ajv 8.17.1 -> 8.18.0 (1) via electron-conf Every target stays inside its parent's declared semver range. For xmldom, fast-uri and js-yaml the newest published version is outside that range (0.9.x / 4.x / 5.x), so "latest" would have broken them; the new doc records that constraint. Deliberately excluded: axios and uuid are direct deps already covered by open Dependabot PRs (#1251, #1252). undici is labelled runtime scope but every path to it is build tooling (electron -> @electron/get, @angular/build, @module-federation/dts-plugin) and it is not in the packaged app. Reachability: xmldom arrives via video.js -> VHS -> mpd-parser, but the app routes every .mpd to Shaka, which uses its own DASH parser, so that one is defence in depth. The genuinely reachable one is js-yaml, which electron-updater uses to parse latest.yml from releases. Adds docs/architecture/dependency-security-overrides.md and a .changes note. Co-Authored-By: Claude Opus 5 --- .changes/deps-transitive-cve-overrides.md | 8 ++ .../dependency-security-overrides.md | 74 +++++++++++++ package.json | 5 + pnpm-lock.yaml | 102 +++++++++--------- 4 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 .changes/deps-transitive-cve-overrides.md create mode 100644 docs/architecture/dependency-security-overrides.md diff --git a/.changes/deps-transitive-cve-overrides.md b/.changes/deps-transitive-cve-overrides.md new file mode 100644 index 000000000..8be3e46d5 --- /dev/null +++ b/.changes/deps-transitive-cve-overrides.md @@ -0,0 +1,8 @@ +--- +type: internal +area: deps +--- + +Patched five vulnerable transitive dependencies that ship with the app — +including the YAML parser `electron-updater` uses to read update manifests, and +the HTTP form encoder behind portal requests. No behaviour change. diff --git a/docs/architecture/dependency-security-overrides.md b/docs/architecture/dependency-security-overrides.md new file mode 100644 index 000000000..be525ed63 --- /dev/null +++ b/docs/architecture/dependency-security-overrides.md @@ -0,0 +1,74 @@ +# Dependency Security Overrides + +How transitive CVEs are patched in this repo, and the constraint that makes +"just bump to latest" the wrong move. + +## Why overrides exist + +Dependabot can only bump packages we declare ourselves. When the vulnerable +package is transitive — pulled in by `video.js`, `electron-updater`, +`electron-conf`, `axios` — the bot has no lever: it would have to wait for the +parent to publish a release that widens its own pin. Until then the alert stays +open regardless of how many bot PRs land. + +`pnpm.overrides` in the root `package.json` is that lever. Every entry uses the +pinned-source form so an override only rewrites the exact resolution it was +written for, and goes stale visibly instead of silently re-targeting a future +version: + +```json +"@xmldom/xmldom@0.8.11": "0.8.13" +``` + +## The semver ceiling + +**An override must stay inside the range its parent declares.** pnpm applies +overrides without re-checking the parent's range, so an out-of-range target +installs cleanly and then fails at runtime or under load, not at install time. + +For three of the five current security overrides, the newest published version +is _outside_ the parent's range. Taking "latest" would break them: + +| Override | Pinned to | Parent range | Latest on npm | +| ---------------- | --------- | --------------------------------------- | ------------- | +| `@xmldom/xmldom` | 0.8.13 | `mpd-parser` `^0.8.3`, `plist` `^0.8.8` | 0.9.x ❌ | +| `fast-uri` | 3.1.4 | `ajv` `^3.0.1` | 4.x ❌ | +| `js-yaml` | 4.3.0 | `electron-updater` `^4.1.0` | 5.x ❌ | +| `form-data` | 4.0.6 | `axios` `^4.0.5` | 4.0.6 ✅ | +| `ajv` | 8.18.0 | `electron-conf` `^8.13.0` | 8.20.0 ✅ | + +Before changing any of these, check the parent's declared range first: + +```bash +npm view @ dependencies --json +``` + +## Verifying an override actually applied + +Grepping `pnpm-lock.yaml` for the old version still finds it, but that hit is +not a leftover package block — pnpm removes those once nothing resolves to them. +It is the override's own selector key, echoed in the `overrides:` block at the +top of the lockfile: + +```yaml +overrides: + '@xmldom/xmldom@0.8.11': 0.8.13 +``` + +So a bare grep proves only that the override is declared, never that it took +effect. Resolve the real path on disk instead: + +```bash +node -e "console.log(require('./node_modules/.pnpm/mpd-parser@1.3.1/node_modules/@xmldom/xmldom/package.json').version)" +``` + +## What is deliberately not overridden + +`undici` carries open alerts flagged `runtime` scope, but every path to it is +build tooling — `electron` → `@electron/get`, `@angular/build`, and +`@module-federation/dts-plugin`. It is not in the packaged app. The `runtime` +label is a Dependabot classification artifact, not a shipped-code claim. Bumping +it inside the Angular/Nx toolchain risks the build for no runtime benefit. + +When triaging, confirm scope from the dependency graph rather than trusting the +alert's `scope` field. diff --git a/package.json b/package.json index 7f4879c18..45b2170bc 100644 --- a/package.json +++ b/package.json @@ -218,16 +218,21 @@ "pnpm": { "overrides": { "@hono/node-server@1.19.9": "1.19.14", + "@xmldom/xmldom@0.8.11": "0.8.13", "ajv@6.12.6": "6.14.0", + "ajv@8.17.1": "8.18.0", "brace-expansion@1.1.12": "1.1.13", "defu@6.1.4": "6.1.6", "devalue@5.6.2": "5.6.4", "express-rate-limit@8.2.1": "8.3.0", + "fast-uri@3.1.0": "3.1.4", "flatted@3.3.3": "3.4.2", "follow-redirects@1.15.11": "1.16.0", + "form-data@4.0.5": "4.0.6", "h3@1.15.5": "1.15.10", "hono@4.12.0": "4.12.14", "immutable@5.1.4": "5.1.5", + "js-yaml@4.1.1": "4.3.0", "lodash-es@4.17.22": "4.18.1", "node-abi@3.85.0": "3.92.0", "node-forge@1.3.3": "1.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5659b2ab..06aa48366 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,16 +6,21 @@ settings: overrides: '@hono/node-server@1.19.9': 1.19.14 + '@xmldom/xmldom@0.8.11': 0.8.13 ajv@6.12.6: 6.14.0 + ajv@8.17.1: 8.18.0 brace-expansion@1.1.12: 1.1.13 defu@6.1.4: 6.1.6 devalue@5.6.2: 5.6.4 express-rate-limit@8.2.1: 8.3.0 + fast-uri@3.1.0: 3.1.4 flatted@3.3.3: 3.4.2 follow-redirects@1.15.11: 1.16.0 + form-data@4.0.5: 4.0.6 h3@1.15.5: 1.15.10 hono@4.12.0: 4.12.14 immutable@5.1.4: 5.1.5 + js-yaml@4.1.1: 4.3.0 lodash-es@4.17.22: 4.18.1 node-abi@3.85.0: 3.92.0 node-forge@1.3.3: 1.4.0 @@ -4712,10 +4717,9 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@xmldom/xmldom@0.8.11': - resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + '@xmldom/xmldom@0.8.13': + resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} - deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -4806,7 +4810,7 @@ packages: ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: - ajv: ^8.0.0 + ajv: 8.18.0 peerDependenciesMeta: ajv: optional: true @@ -4814,7 +4818,7 @@ packages: ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: - ajv: ^8.0.0 + ajv: 8.18.0 peerDependenciesMeta: ajv: optional: true @@ -4827,14 +4831,11 @@ packages: ajv-keywords@5.1.0: resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} peerDependencies: - ajv: ^8.8.2 + ajv: 8.18.0 ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -6498,8 +6499,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.4: + resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -6620,8 +6621,8 @@ packages: typescript: '>3.6.0' webpack: ^5.11.0 - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} forwarded@0.2.0: @@ -6871,6 +6872,10 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hast-util-from-html@2.0.3: resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} @@ -7608,8 +7613,8 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true jsdom@26.1.0: @@ -11278,8 +11283,8 @@ snapshots: '@angular-devkit/core@21.1.4(chokidar@5.0.0)': dependencies: - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) jsonc-parser: 3.3.1 picomatch: 4.0.3 rxjs: 7.8.2 @@ -11595,7 +11600,7 @@ snapshots: hast-util-from-html: 2.0.3 hast-util-to-text: 4.0.2 import-meta-resolve: 4.2.0 - js-yaml: 4.1.1 + js-yaml: 4.3.0 mdast-util-definitions: 6.0.0 rehype-raw: 7.0.0 rehype-stringify: 10.0.1 @@ -11621,7 +11626,7 @@ snapshots: hast-util-from-html: 2.0.3 hast-util-to-text: 4.0.2 import-meta-resolve: 4.2.0 - js-yaml: 4.1.1 + js-yaml: 4.3.0 mdast-util-definitions: 6.0.0 rehype-raw: 7.0.0 rehype-stringify: 10.0.1 @@ -13272,7 +13277,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -16104,7 +16109,7 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@xmldom/xmldom@0.8.11': {} + '@xmldom/xmldom@0.8.13': {} '@xtuc/ieee754@1.2.0': {} @@ -16190,10 +16195,6 @@ snapshots: optionalDependencies: ajv: 8.18.0 - ajv-formats@3.0.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -16214,17 +16215,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.4 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -16335,7 +16329,7 @@ snapshots: hosted-git-info: 4.1.0 is-ci: 3.0.1 isbinaryfile: 5.0.7 - js-yaml: 4.1.1 + js-yaml: 4.3.0 json5: 2.2.3 lazy-val: 1.0.5 minimatch: 10.1.1 @@ -16460,7 +16454,7 @@ snapshots: html-escaper: 3.0.3 http-cache-semantics: 4.2.0 import-meta-resolve: 4.2.0 - js-yaml: 4.1.1 + js-yaml: 4.3.0 magic-string: 0.30.21 magicast: 0.5.2 mrmime: 2.0.1 @@ -16555,7 +16549,7 @@ snapshots: axios@1.15.0: dependencies: follow-redirects: 1.16.0(debug@4.4.3) - form-data: 4.0.5 + form-data: 4.0.6 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -16563,7 +16557,7 @@ snapshots: axios@1.16.0: dependencies: follow-redirects: 1.16.0(debug@4.4.3) - form-data: 4.0.5 + form-data: 4.0.6 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -16894,7 +16888,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-ci: 3.0.1 - js-yaml: 4.1.1 + js-yaml: 4.3.0 sanitize-filename: 1.6.3 source-map-support: 0.5.21 stat-mode: 1.0.0 @@ -17269,7 +17263,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -17279,7 +17273,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.0 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -17581,7 +17575,7 @@ snapshots: builder-util-runtime: 9.3.1 fs-extra: 10.1.0 iconv-lite: 0.6.3 - js-yaml: 4.1.1 + js-yaml: 4.3.0 optionalDependencies: dmg-license: 1.0.11 transitivePeerDependencies: @@ -17702,7 +17696,7 @@ snapshots: electron-conf@1.3.0(electron@41.7.2): dependencies: - ajv: 8.17.1 + ajv: 8.18.0 electron: 41.7.2 electron-playwright-helpers@1.8.2: @@ -17715,7 +17709,7 @@ snapshots: builder-util: 26.0.11 builder-util-runtime: 9.3.1 chalk: 4.1.2 - form-data: 4.0.5 + form-data: 4.0.6 fs-extra: 10.1.0 lazy-val: 1.0.5 mime: 2.6.0 @@ -17730,7 +17724,7 @@ snapshots: dependencies: builder-util-runtime: 9.7.0 fs-extra: 10.1.0 - js-yaml: 4.1.1 + js-yaml: 4.3.0 lazy-val: 1.0.5 lodash.escaperegexp: 4.1.2 lodash.isequal: 4.5.0 @@ -18379,7 +18373,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.0: {} + fast-uri@3.1.4: {} fastq@1.20.1: dependencies: @@ -18520,12 +18514,12 @@ snapshots: typescript: 5.9.3 webpack: 5.104.1(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.28.1) - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 forwarded@0.2.0: {} @@ -18806,6 +18800,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hast-util-from-html@2.0.3: dependencies: '@types/hast': 3.0.4 @@ -19832,7 +19830,7 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.1: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 @@ -20786,14 +20784,14 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 '@videojs/vhs-utils': 3.0.5 - '@xmldom/xmldom': 0.8.11 + '@xmldom/xmldom': 0.8.13 global: 4.4.0 mpd-parser@1.3.1: dependencies: '@babel/runtime': 7.28.4 '@videojs/vhs-utils': 4.1.1 - '@xmldom/xmldom': 0.8.11 + '@xmldom/xmldom': 0.8.13 global: 4.4.0 mpegts.js@1.8.0: @@ -21098,7 +21096,7 @@ snapshots: figures: 3.2.0 flat: 5.0.2 follow-redirects: 1.16.0(debug@4.4.3) - form-data: 4.0.5 + form-data: 4.0.6 fs-constants: 1.0.0 function-bind: 1.1.2 get-caller-file: 2.0.5 @@ -21552,7 +21550,7 @@ snapshots: plist@3.1.0: dependencies: - '@xmldom/xmldom': 0.8.11 + '@xmldom/xmldom': 0.8.13 base64-js: 1.5.1 xmlbuilder: 15.1.1