fix(deps): clear the OSV suppressions that expire on 2026-11-30

Delete the hono, @anthropic-ai/sdk (#2754) and sprintf-js ignores, which the
lockfiles no longer match. Override image-size to 2.0.4 (#2753) and chokidar
to 4.0.3 in lib/diagram-render (drops braces), and delete those ignores.
Re-date katex to 2027-01-31: its fix is outside mermaid 11's range
(mermaid-js/mermaid#8388).

html-to-docx 1.8.0 bundles its own image-size 1.x, which the override cannot
reach and whose ICNS parser loops forever. make-pdf --to docx now hands it
only PNG, JPEG, GIF, WebP or SVG bytes. A golden docx test proves the Word
output is unchanged.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-17 04:20:54 +00:00
1 parent cadbd0af15
commit 54d5763cdf
16 files changed
+326 -114

No files matched your search

+9 -66
View File
@@ -16,73 +16,16 @@
# forces a fresh decision. Tracking issues are filed at ship time and named in
# each reason.
[[IgnoredVulns]]
id = "GHSA-frvp-7c67-39w9"
# @hono/node-server 1.19.x. Reachable only through @modelcontextprotocol/sdk,
# which is an unused transitive dependency (no source file imports it) and never
# starts a Hono HTTP server, so the advisory's request path is not exercised.
# The only fix is @hono/node-server 2.0.5, a major bump the MCP SDK pins against
# (^1.19.9); forcing it via override risks breaking the SDK at runtime for a
# vulnerability we do not expose. Re-evaluate if the MCP SDK becomes a direct,
# server-hosting dependency.
reason = "Unreachable transitive (unused @modelcontextprotocol/sdk); fix requires a risky major override on a pinned peer dep."
ignoreUntil = 2026-11-30T00:00:00Z
[[IgnoredVulns]]
id = "GHSA-5p2g-fcmc-qvqq"
# image-size 1.2.1 via html-to-docx@1.8.0. image-size 2.0.3 (2026-09-14)
# fixes both advisories, but html-to-docx pins image-size ^1.0.0 and no fixed
# 1.x exists, so taking the fix is a cross-major override. Exposure:
# image-size only parses images the user themselves embeds into their own
# generated .docx; no untrusted input path. Upgrade trigger: html-to-docx
# moving off image-size 1.x, or a deliberate override to 2.x after checking
# html-to-docx's call site against 2.x's API with a docx-export smoke test.
# Tracking: #2753.
reason = "Fixed in image-size 2.0.3, but html-to-docx pins image-size ^1.0.0 (no fixed 1.x); local-only input path (user's own docx images). Re-evaluate on expiry."
ignoreUntil = 2026-11-30T00:00:00Z
[[IgnoredVulns]]
id = "GHSA-w3rx-r6r6-pgpr"
# Same package/node as GHSA-5p2g-fcmc-qvqq above; same rationale.
reason = "Fixed in image-size 2.0.3, but html-to-docx pins image-size ^1.0.0 (no fixed 1.x); local-only input path (user's own docx images). Re-evaluate on expiry."
ignoreUntil = 2026-11-30T00:00:00Z
[[IgnoredVulns]]
id = "GHSA-p7fg-763f-g4gf"
# @anthropic-ai/sdk 0.81.0 nested under @anthropic-ai/claude-agent-sdk@0.2.117,
# which is deliberately exact-pinned (eval-harness stability; the v1.77 wave
# pinned the whole harness after repeated CLI-drift breakage) and declares
# ^0.81.0 (0.x caret = 0.81.x only), so the 0.91.1 fix cannot be reached
# without violating the harness pin. 4.8 MEDIUM. Upgrade trigger: the next
# deliberate claude-agent-sdk bump. Tracking: #2754.
reason = "Fix requires breaking the deliberate eval-harness agent-sdk pin; MEDIUM severity accepted until the next harness bump."
ignoreUntil = 2026-11-30T00:00:00Z
[[IgnoredVulns]]
id = "GHSA-vfj7-8cjw-p6xm"
# braces 3.0.3 in lib/diagram-render only: @excalidraw/excalidraw -> sass ->
# chokidar -> braces. chokidar is sass's Node file watcher; the diagram-render
# bundle is built for the browser and contains no chokidar/braces code
# (test/osv-config-wiring.test.ts checks the built page for chokidar). No fixed
# braces release exists. Re-evaluate on expiry or when excalidraw drops sass.
reason = "Build-time only (excalidraw -> sass -> chokidar); not in the shipped browser bundle; no fixed release."
ignoreUntil = 2026-11-30T00:00:00Z
[[IgnoredVulns]]
id = "GHSA-hp3w-g68c-fv3c"
# sprintf-js 1.1.3 (DoS through an unbounded precision specifier in the FORMAT
# string). Reachable only as roarr's logger dependency (global-agent -> roarr),
# which formats its own constant messages; no caller passes user-controlled
# format strings. No fixed release exists.
reason = "No fixed release; only roarr's constant-format logging reaches it."
ignoreUntil = 2026-11-30T00:00:00Z
[[IgnoredVulns]]
id = "GHSA-238p-pmpm-9mq7"
# katex 0.16.47 in lib/diagram-render (mermaid's math renderer). The advisory
# needs prototype pollution to already exist in the page; the render page is
# offline, loads only gstack's own bundle and the user's diagram source. The
# only fix is 0.18.x, outside mermaid 11.16's ^0.16 range. Low severity (2.1).
# Re-evaluate when mermaid accepts katex 0.18.
reason = "Requires pre-existing prototype pollution in an offline single-bundle page; fix is outside mermaid's katex range; low severity."
ignoreUntil = 2026-11-30T00:00:00Z
# offline, loads only gstack's own bundle and the user's diagram source. Low
# severity (2.1). The fix is katex 0.18.2, outside mermaid 11's ^0.16.47 range.
# Mermaid 12 accepts ^0.18, but @excalidraw/mermaid-to-excalidraw 2.2.2 (the
# latest) requires mermaid ^11.12.1, and katex 0.18 renames KaTeX's CSS
# classes, so forcing it means a bundle requalification, not a lockfile bump.
# Upgrade trigger: a mermaid 11.x release that allows katex 0.18
# (https://github.com/mermaid-js/mermaid/issues/8388), then rebuild the bundle.
reason = "Fix (katex 0.18.2) is outside mermaid 11's ^0.16 range; waiting on mermaid-js/mermaid#8388. Needs pre-existing prototype pollution in an offline single-bundle page; low severity."
ignoreUntil = 2027-01-31T00:00:00Z
+5 -6
View File
@@ -41,8 +41,8 @@ The 158 older open issues were re-checked against main 5cb5e1c (v1.91.83.0). Of
- **/cso**
- #2993: tracked under the /cso capacity follow-ups above. **Priority:** P2.
- **Dependency security ledgers** (not code defects; keep open until the trigger)
- #2753: `.osv-scanner.toml` ignores image-size (via html-to-docx) until 2026-11-30. Bump it once a fixed release exists, or re-justify at expiry. **Effort:** S. **Priority:** P3.
- #2754: `.osv-scanner.toml` ignores `@anthropic-ai/sdk` 0.81.0 under the pinned claude-agent-sdk until 2026-11-30. Drop it with the next harness bump. **Effort:** S. **Priority:** P3.
- `.osv-scanner.toml` ignores katex 0.16.47 in lib/diagram-render (GHSA-238p-pmpm-9mq7, low) until 2027-01-31. The fix (0.18.2) is outside mermaid 11's range; when mermaid-js/mermaid#8388 ships, override katex, rebuild the bundle and drop the ignore. **Effort:** S. **Priority:** P3.
- html-to-docx 1.8.0 bundles its own image-size 1.x, which the package.json override cannot reach. `make-pdf --to docx` only hands it PNG, JPEG, GIF, WebP or SVG bytes, but with `--allow-network` html-to-docx still downloads and sizes remote images itself. Drop html-to-docx's bundled sizer (a fork or a replacement converter) or validate remote images before conversion. **Effort:** M. **Priority:** P3.
- **Tests and CI**
- #2756: the periodic lane. Tracked under "Periodic-lane stabilization (#2756)". **Priority:** P2.
- #2967: `test/redact-prepush-scan-range.test.ts` (and its rebase twin) run fixture pushes with the inherited global config, so a developer's `core.hooksPath` pre-push runs inside the fixture. Set `GIT_CONFIG_GLOBAL=/dev/null` and `GIT_CONFIG_NOSYSTEM=1` in the helper. **Effort:** S. **Priority:** P3.
@@ -965,10 +965,9 @@ wave"). Each was explicitly deferred with rationale, not dropped:
- **mermaid 10→11-class major bumps in lib/diagram-render** — the wave's
dependency pass cleared 102 of 105 OSV advisories via in-range bumps +
overrides; the residual ignores (image-size no-fix, @anthropic-ai/sdk under
the harness-pinned agent-sdk) carry `ignoreUntil` expiries (~2026-11-30) and
re-justify themselves on expiry. When the agent-sdk pin next moves, drop the
GHSA-p7fg ignore. Effort S. **Priority:** P3.
overrides; the residual ignores carry `ignoreUntil` expiries and
re-justify themselves on expiry. Only the katex ignore remains (see the
dependency security ledgers above). Effort S. **Priority:** P3.
- **#2750 split absorption** — the record-scanning Codex JSONL parser (real
fix; current Codex streams interleave envelopes so sessions vanish from
/retro global) should be absorbed once the author splits it from the
+2 -3
View File
@@ -34,6 +34,7 @@
"adm-zip": "^0.6.1",
"fast-uri": "3.1.8",
"hono": "4.13.13",
"image-size": "2.0.4",
"ip-address": "10.7.3",
"proxy-addr": "2.0.8",
"sharp": "0.35.5",
@@ -371,7 +372,7 @@
"iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
"image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": { "image-size": "bin/image-size.js" } }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="],
"image-size": ["image-size@2.0.4", "", { "bin": { "image-size": "bin/image-size.js" } }, "sha512-QRUkFFsRV/6fuESxb9Vkq+a0LkSrgKXuc2NEqfikiXxxN/G3tjWt5EVUlMaImRBZRZK/jRBEbYvpPYZL8t08Zw=="],
"image-to-base64": ["image-to-base64@2.2.0", "", { "dependencies": { "node-fetch": "^2.6.0" } }, "sha512-Z+aMwm/91UOQqHhrz7Upre2ytKhWejZlWV/JxUTD1sT7GWWKFDJUEV5scVQKnkzSgPHFuQBUEWcanO+ma0PSVw=="],
@@ -483,8 +484,6 @@
"qs": ["qs@6.16.0", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA=="],
"queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="],
"range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
+3 -28
View File
@@ -14,6 +14,7 @@
},
},
"overrides": {
"chokidar": "4.0.3",
"dompurify": "3.4.16",
"lodash-es": "4.18.1",
"nanoid": "5.1.16",
@@ -178,14 +179,8 @@
"@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browser-fs-access": ["browser-fs-access@0.29.1", "", {}, "sha512-LSvVX5e21LRrXqVMhqtAwj5xPgDb+fXAIH80NsnCQ9xuZPs2xWsOREi24RKgZa1XOiQRbcmVrv87+ulOKsgjxw=="],
"canvas-roundrect-polyfill": ["canvas-roundrect-polyfill@0.0.1", "", {}, "sha512-yWq+R3U3jE+coOeEb3a3GgE2j/0MMiDKM/QpLb6h9ihf5fGY9UXtvK9o4vNqjWXoZz7/3EaSVU3IX53TvFFUOw=="],
@@ -194,7 +189,7 @@
"chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="],
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"clsx": ["clsx@1.1.1", "", {}, "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="],
@@ -292,18 +287,12 @@
"es6-promise-pool": ["es6-promise-pool@2.5.0", "", {}, "sha512-VHErXfzR/6r/+yyzPKeBvO0lgjfC5cbDCQWjWwMZWSb6YU39TGIl51OUmCfWCq4ylMdJSB8zkz2vIuIeIxXApA=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"fractional-indexing": ["fractional-indexing@3.2.0", "", {}, "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"fuzzy": ["fuzzy@0.1.3", "", {}, "sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"glur": ["glur@1.1.2", "", {}, "sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA=="],
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
@@ -320,14 +309,6 @@
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jotai": ["jotai@2.11.0", "", { "peerDependencies": { "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ=="],
@@ -360,8 +341,6 @@
"nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"open-color": ["open-color@1.9.1", "", {}, "sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw=="],
@@ -378,8 +357,6 @@
"pica": ["pica@7.1.1", "", { "dependencies": { "glur": "^1.1.2", "inherits": "^2.0.3", "multimath": "^2.0.0", "object-assign": "^4.1.1", "webworkify": "^1.5.0" } }, "sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ=="],
"picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"png-chunk-text": ["png-chunk-text@1.0.0", "", {}, "sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw=="],
"png-chunks-encode": ["png-chunks-encode@1.0.0", "", { "dependencies": { "crc-32": "^0.3.0", "sliced": "^1.0.1" } }, "sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA=="],
@@ -402,7 +379,7 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
@@ -428,8 +405,6 @@
"tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+1
View File
@@ -14,6 +14,7 @@
"react-dom": "18.3.1"
},
"overrides": {
"chokidar": "4.0.3",
"dompurify": "3.4.16",
"nanoid": "5.1.16",
"source-map-js": "1.2.2",
+12
View File
@@ -21,6 +21,18 @@ export function imageDims(buf: Buffer): ImageDims | null {
return pngDims(buf) ?? jpegDims(buf) ?? gifDims(buf) ?? webpDims(buf) ?? svgDims(buf);
}
/**
* html-to-docx 1.8.0 sizes every embedded image with its own bundled copy of
* image-size 1.x. The package.json override cannot reach that copy, and its
* ICNS parser loops forever on a zero-length entry (GHSA-w3rx-r6r6-pgpr), so
* DOCX conversion only hands it bytes that are recognizably PNG, JPEG, GIF or
* WebP, or text that opens as SVG markup.
*/
export function docxSafeImage(buf: Buffer): boolean {
if (pngDims(buf) ?? jpegDims(buf) ?? gifDims(buf) ?? webpDims(buf)) return true;
return /^\uFEFF?\s*</.test(buf.toString("utf8", 0, 64)) && /<svg\b/i.test(buf.toString("utf8", 0, 4096));
}
function pngDims(b: Buffer): ImageDims | null {
// 8-byte signature, then IHDR chunk: length(4) "IHDR"(4) width(4) height(4)
if (b.length < 24) return null;
+13
View File
@@ -41,6 +41,7 @@ import {
substituteSlots,
} from "./diagram-prepass";
import { applyImagePolicy } from "./image-policy";
import { docxSafeImage } from "./image-size";
/** Default output location (`$P generate letter.md` → /tmp/letter.pdf). */
export const OUTPUT_TMP_DIR = process.platform === "win32" ? os.tmpdir() : "/tmp";
@@ -174,6 +175,18 @@ export async function generate(opts: GenerateOptions): Promise<string> {
progress.end("Rasterizing diagrams for DOCX");
}
finalHtml = convertDiagnosticsForDocx(finalHtml);
finalHtml = finalHtml.replace(/<img\b[^>]*>/gi, (tag) => {
const src = tag.match(/\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i);
const payload = (src?.[1] ?? src?.[2] ?? src?.[3] ?? "").match(/^data:[^;,]+;base64,([\s\S]+)$/)?.[1];
if (payload === undefined) return tag;
let base64 = "";
try {
base64 = decodeURIComponent(payload);
} catch { /* malformed escape: drop it below */ }
if (/^[A-Za-z0-9+/=\s]+$/.test(base64) && docxSafeImage(Buffer.from(base64, "base64"))) return tag;
warn("docx: dropped an image that is not PNG, JPEG, GIF, WebP or SVG");
return "";
});
}
// ─── --to html: write the self-contained document, no print round-trip ──
+90
View File
@@ -0,0 +1,90 @@
/**
* DOCX image sizing (#2753). package.json overrides image-size to 2.x, which
* fixes GHSA-5p2g-fcmc-qvqq and GHSA-w3rx-r6r6-pgpr for the lockfile, but
* html-to-docx 1.8.0 runs a bundled image-size 1.x copy the override cannot
* reach. The golden document.xml was captured before the override, so any
* change to the Word output shows up as a diff here. The orchestrator only
* hands html-to-docx images whose bytes are PNG, JPEG, GIF, WebP or SVG, so
* the bundled ICNS parser's infinite loop never runs.
*/
import { describe, expect, test } from "bun:test";
import { execFileSync } from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { docxSafeImage } from "../src/image-size";
import { generate } from "../src/orchestrator";
const FIXTURES = path.join(import.meta.dir, "fixtures", "docx-images");
const EMU_PER_PX = 9525;
const IMAGES = [
{ file: "wide.png", ext: "png", width: 120, height: 48 },
{ file: "tall.jpg", ext: "jpeg", width: 30, height: 90 },
{ file: "square.gif", ext: "gif", width: 64, height: 64 },
];
/** GHSA-w3rx-r6r6-pgpr: valid ICNS magic and a zero-length entry. */
function icnsLoop(): Buffer {
const buf = Buffer.alloc(24);
buf.write("icns", 0);
buf.writeUInt32BE(24, 4);
buf.write("ic08", 8);
return buf;
}
function unzip(args: string[]): Buffer {
return execFileSync("unzip", args, { timeout: 30_000, maxBuffer: 16 * 1024 * 1024 });
}
describe("make-pdf --to docx image sizing", () => {
test("embeds every image at its pixel size, matching the image-size 1.x output", async () => {
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "make-pdf-docx-images-"));
try {
const out = path.join(workDir, "out.docx");
await generate({ input: path.join(FIXTURES, "doc.md"), output: out, to: "docx", quiet: true });
const xml = unzip(["-p", out, "word/document.xml"]).toString("utf8");
const extents = [...xml.matchAll(/<wp:extent cx="(\d+)" cy="(\d+)"\/>/g)].map(m => [Number(m[1]), Number(m[2])]);
expect(extents).toEqual(IMAGES.map(img => [img.width * EMU_PER_PX, img.height * EMU_PER_PX]));
const media = unzip(["-Z1", out]).toString("utf8").split("\n").filter(name => /^word\/media\/.+/.test(name));
expect(media).toHaveLength(IMAGES.length);
for (const [i, img] of IMAGES.entries()) {
expect(media[i].endsWith(`.${img.ext}`)).toBe(true);
expect(unzip(["-p", out, media[i]]).equals(fs.readFileSync(path.join(FIXTURES, img.file)))).toBe(true);
}
const normalized = xml.replace(/image-[A-Za-z0-9_-]{21}\.(png|jpeg|gif)/g, "image-ID.$1");
expect(normalized.trimEnd()).toBe(fs.readFileSync(path.join(FIXTURES, "document.golden.xml"), "utf8").trimEnd());
} finally {
fs.rmSync(workDir, { recursive: true, force: true });
}
});
test("only PNG, JPEG, GIF, WebP and SVG markup reach html-to-docx's bundled sizer", () => {
for (const img of IMAGES) expect(docxSafeImage(fs.readFileSync(path.join(FIXTURES, img.file)))).toBe(true);
expect(docxSafeImage(Buffer.from('<?xml version="1.0"?>\n<svg xmlns="http://www.w3.org/2000/svg"></svg>'))).toBe(true);
expect(docxSafeImage(icnsLoop())).toBe(false);
expect(docxSafeImage(Buffer.concat([icnsLoop(), Buffer.from('<svg width="1" height="1">')]))).toBe(false);
expect(docxSafeImage(Buffer.from("BM not an image at all"))).toBe(false);
});
test("an ICNS file named .png is dropped with a warning instead of hanging the conversion", () => {
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "make-pdf-docx-icns-"));
try {
fs.writeFileSync(path.join(workDir, "evil.png"), icnsLoop());
fs.writeFileSync(path.join(workDir, "doc.md"), "# ICNS\n\n![evil](evil.png)\n\n![inline](data:image/png;base64," + icnsLoop().toString("base64") + ")\n");
const out = path.join(workDir, "out.docx");
const script = `import { generate } from ${JSON.stringify(path.join(import.meta.dir, "..", "src", "orchestrator.ts"))};
await generate({ input: ${JSON.stringify(path.join(workDir, "doc.md"))}, output: ${JSON.stringify(out)}, to: "docx" });`;
const run = Bun.spawnSync([process.execPath, "-e", script], { timeout: 30_000, stdout: "pipe", stderr: "pipe" });
const stderr = run.stderr.toString();
expect(run.exitCode).toBe(0);
expect(stderr.match(/docx: dropped an image that is not PNG, JPEG, GIF, WebP or SVG/g)).toHaveLength(2);
expect(unzip(["-p", out, "word/document.xml"]).toString("utf8")).not.toContain("<w:drawing>");
} finally {
fs.rmSync(workDir, { recursive: true, force: true });
}
});
});
+11
View File
@@ -0,0 +1,11 @@
# Image sizing smoke
A wide PNG:
![wide](wide.png)
A tall JPEG and a square GIF:
![tall](tall.jpg)
![square](square.gif)
+163
View File
@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:cdr="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
<w:body>
<w:sectPr>
<w:pgSz w:w="12240" w:h="15840" w:orient="portrait"/>
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="720" w:footer="720" w:gutter="0"/>
</w:sectPr>
<w:p>
<w:pPr>
<w:spacing w:lineRule="auto"/>
</w:pPr>
<w:r>
<w:rPr/>
<w:t xml:space="preserve"/>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="Heading1"/>
<w:spacing w:lineRule="auto"/>
</w:pPr>
<w:r>
<w:rPr/>
<w:t xml:space="preserve">Image sizing smoke</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:spacing w:lineRule="auto"/>
</w:pPr>
<w:r>
<w:rPr/>
<w:t xml:space="preserve">A wide PNG:</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:spacing w:lineRule="auto"/>
</w:pPr>
<w:r>
<w:rPr/>
<w:drawing>
<wp:inline distB="0" distL="0" distR="0" distT="0">
<wp:extent cx="1143000" cy="457200"/>
<wp:effectExtent b="0" l="0" r="0" t="0"/>
<wp:docPr id="1" name="image-ID.png"/>
<a:graphic>
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic>
<pic:nvPicPr>
<pic:cNvPr id="1" name="image-ID.png" descr="wide"/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId6" cstate="print"/>
<a:srcRect b="0" l="0" r="0" t="0"/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="1143000" cy="457200"/>
</a:xfrm>
<a:prstGeom prst="rect"/>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:spacing w:lineRule="auto"/>
</w:pPr>
<w:r>
<w:rPr/>
<w:t xml:space="preserve">A tall JPEG and a square GIF:</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:spacing w:lineRule="auto"/>
</w:pPr>
<w:r>
<w:rPr/>
<w:drawing>
<wp:inline distB="0" distL="0" distR="0" distT="0">
<wp:extent cx="285750" cy="857250"/>
<wp:effectExtent b="0" l="0" r="0" t="0"/>
<wp:docPr id="2" name="image-ID.jpeg"/>
<a:graphic>
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic>
<pic:nvPicPr>
<pic:cNvPr id="2" name="image-ID.jpeg" descr="tall"/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId7" cstate="print"/>
<a:srcRect b="0" l="0" r="0" t="0"/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="285750" cy="857250"/>
</a:xfrm>
<a:prstGeom prst="rect"/>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:spacing w:lineRule="auto"/>
</w:pPr>
<w:r>
<w:rPr/>
<w:drawing>
<wp:inline distB="0" distL="0" distR="0" distT="0">
<wp:extent cx="609600" cy="609600"/>
<wp:effectExtent b="0" l="0" r="0" t="0"/>
<wp:docPr id="3" name="image-ID.gif"/>
<a:graphic>
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic>
<pic:nvPicPr>
<pic:cNvPr id="3" name="image-ID.gif" descr="square"/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId8" cstate="print"/>
<a:srcRect b="0" l="0" r="0" t="0"/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="609600" cy="609600"/>
</a:xfrm>
<a:prstGeom prst="rect"/>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
</w:body>
</w:document>
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

+1
View File
@@ -105,6 +105,7 @@
"adm-zip": "^0.6.1",
"fast-uri": "3.1.8",
"hono": "4.13.13",
"image-size": "2.0.4",
"ip-address": "10.7.3",
"proxy-addr": "2.0.8",
"sharp": "0.35.5"
+8
View File
@@ -50,4 +50,12 @@ describe("osv-scanner config wiring", () => {
const page = fs.readFileSync(path.join(ROOT, "lib", "diagram-render", "dist", "diagram-render.html"), "utf8");
expect(page).not.toMatch(/chokidar|FSWatcher/);
});
test("the chokidar override keeps braces (GHSA-vfj7-8cjw-p6xm, no fixed release) out of lib/diagram-render", () => {
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, "lib", "diagram-render", "package.json"), "utf8"));
expect(pkg.overrides.chokidar).toMatch(/^4\./);
const lock = fs.readFileSync(path.join(ROOT, "lib", "diagram-render", "bun.lock"), "utf8");
expect(lock).toMatch(/"chokidar": \["chokidar@4\.\d+\.\d+"/);
expect(lock).not.toMatch(/"braces": \["braces@/);
});
});
+8 -11
View File
@@ -58,18 +58,15 @@ describe('OSV suppression expiry', () => {
for (const text of ['"$SCAN_RESULT" = "failure"', 'gh issue create', 'gh issue comment', 'gh issue close']) expect(run).toContain(text);
});
test('the image-size entries name the fixed release and the pin that blocks it (#2753)', () => {
const toml = fs.readFileSync(path.join(import.meta.dir, '..', '.osv-scanner.toml'), 'utf8');
const entries = toml.split('[[IgnoredVulns]]').slice(1).filter(e => /^id = "GHSA-(?:5p2g-fcmc-qvqq|w3rx-r6r6-pgpr)"/m.test(e));
expect(entries).toHaveLength(2);
for (const entry of entries) {
expect(entry).not.toContain('No fixed version exists');
expect(entry).toMatch(/^reason = ".*image-size 2\.0\.3.*\^1\.0\.0.*"$/m);
expect(entry).toContain('ignoreUntil = 2026-11-30T00:00:00Z');
}
// The stated blocker is the lockfile's: html-to-docx asks for image-size ^1.0.0 and resolves a 1.x.
test('image-size is overridden to a fixed 2.x and its suppressions are gone (#2753)', () => {
const toml = fs.readFileSync(path.join(ROOT, CONFIG), 'utf8');
expect(toml).not.toMatch(/GHSA-(?:5p2g-fcmc-qvqq|w3rx-r6r6-pgpr)/);
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
expect(pkg.overrides['image-size']).toBe('2.0.4');
// html-to-docx still asks for ^1.0.0; the override is what moves the lockfile off the affected 1.x.
const lock = fs.readFileSync(path.join(ROOT, 'bun.lock'), 'utf8');
expect(lock).toMatch(/"html-to-docx": \["html-to-docx@[^"]+", "", \{ "dependencies": \{[^}]*"image-size": "\^1\.0\.0"/);
expect(lock).toMatch(/"image-size": \["image-size@1\.\d+\.\d+"/);
expect(lock).toMatch(/"image-size": \["image-size@2\.0\.4"/);
expect(lock).not.toMatch(/"image-size": \["image-size@1\./);
});
});