Repository navigation
fix(pdf): upgrade pdfjs-dist past GHSA-hq66-cqwq-w95j (5.7.284 → 6.3.289) - #124
Merged
Merged
Conversation
…cripting boundary Closes #121. `pdfjs-dist` was resolved to 5.7.284, inside the affected range of GHSA-hq66-cqwq-w95j / CVE-2026-16633 (`>=5.6.83 <6.2.108`, fixed 6.2.108): arbitrary JavaScript execution upon opening a malicious PDF. Untrusted documents are this app's normal input, so a known-high in that path is a release blocker. **The reachability finding, stated precisely: the current exploit path is not reachable under the inspected call sites.** That is a statement about our code, not about the library, and it is now enforced rather than assumed - see the guard below. - main (`PdfLoader`) and renderer (`PdfSourceReader`) both call `getDocument` with no scripting option, and neither constructs an `AnnotationLayer` nor imports `pdfjs-dist/web/*`. `PDFScriptingManager`, which is what actually executes document JavaScript, exists only in the viewer build; it is absent from the API build entirely (`grep -c PDFScriptingManager build/pdf.mjs` is 0). - The renderer additionally already carries a CSP with `script-src 'self'` and neither `'unsafe-inline'` nor `'unsafe-eval'` (`src/renderer/index.html`), so the advisory's second prerequisite - "no CSP for disallowing script-src" - is not met either. Two independent barriers on the path that renders untrusted documents. **Correction to the issue's own acceptance.** It asked for `enableScripting: false` to be passed explicitly on both `getDocument` calls. That is not implementable: `enableScripting` is declared on `AnnotationLayer` / `AnnotationLayerBuilder` / `PDFViewer`, and is **not** a `DocumentInitParameters` member - TypeScript rejects it on `getDocument`. The advisory's workaround targets the viewer, which this app does not use. The honest mitigation is therefore structural, and it is now a test: `test/pdfjsScriptingBoundary.test.ts` fails if any source file imports the viewer build, constructs the annotation layer or scripting manager, or sets `enableScripting: true`. That converts "we happen not to do this" into a CI-enforced invariant, and it carries a self-check so it cannot silently scan nothing. **The upgrade: 5.7.284 -> 6.3.289.** The whole API breakage is one moved method: `destroy()` is gone from `PDFDocumentProxy` (which keeps `cleanup()`) and now lives on `PDFDocumentLoadingTask`. Three call sites, all mechanical: - `PdfLoader.ts` destroys the loading task it already holds. - `PdfSourceReader.tsx` now keeps the loading task for teardown instead of the document, and clears it before destroying on the cancelled path so unmount cannot destroy the same task twice. `engines` on 6.x is `>=22.13.0 || >=24`; CI and CONTRIBUTING both pin Node 24. **Rollback plan.** Mechanically it is two steps: restore `^5.x` and revert the three `destroy()` call sites. It is deliberately not the default, because it re-opens this issue: the version would be inside a known-high range again. The structural mitigation and the CSP survive a rollback, so the residual risk is specifically "a known-high version ships", which is the thing the release policy forbids - so a rollback needs an explicit maintainer decision and a note in the release, not a quiet revert. Verification: `npm run typecheck` (all three projects), `npm test` (258 pass, 4 new for the boundary guard), `npm run build:unpack && npm run smoke:packaged` **executed on the packaged app** - 20/20 checks, including `pdfjs-dist loads and polyfilled DOMMatrix` and a real `PDF import extracts text` fixture parse under 6.3.289 - and `npm audit --omit=dev --registry=https://registry.npmjs.org` now reports **0 vulnerabilities**. `npm run check:design`, `npm run lint` (0 errors, baseline unchanged), `npx prettier --check`. Not verified: the reader's rendering path (canvas render, text-layer selection, bbox highlighting, zoom) needs a real window and is manual QA; CI covers the packaged main-process parse on Windows and macOS.
This was referenced Sep 25, 2026
mrsibe
added a commit
that referenced
this pull request
Sep 25, 2026
`pdfjs-dist` 6.3.289 calls `Map.prototype.getOrInsertComputed` (and
`getOrInsert`) throughout the API build and the worker, but those shipped in
Chromium 145 and Electron 39 is Chromium 142. The renderer imported the modern
build, so the first `page.render()` threw
TypeError: this[#methodPromises].getOrInsertComputed is not a function
from `WorkerTransport.getOptionalContentConfig`, and every page painted blank.
The main-process loader already imports `pdfjs-dist/legacy/build/pdf.mjs`, whose
core-js polyfills install those methods, so text extraction was unaffected while
the reader was broken.
Point the reader and its worker at the same legacy build. Switch back to
`pdfjs-dist/build/*` once the app runs on Chromium >= 145.
The reader path slipped through #124 (the upgrade for #121), whose own message
records the rendering path as unverified manual QA. `test/pdfjsScriptingBoundary.test.ts`
now fails if any source file imports the modern build, and its self-check accepts
the legacy subpath.
Verified: `npm test` (271 pass), `npm run typecheck`, `npm run build`; the built
`out/renderer/assets/pdf.worker.min-*.mjs` carries the polyfill
(`target:"Map",proto:!0`) and the renderer bundle references that legacy worker.
The new guard was falsified by temporarily restoring the modern import, which
fails it as intended.
Not verified: canvas render, text-layer selection, bbox highlighting and zoom
under a real window - that needs manual QA, and is #125's job.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #121.
pdfjs-distresolved to 5.7.284, inside the affected range of GHSA-hq66-cqwq-w95j / CVE-2026-16633 (>=5.6.83 <6.2.108, fixed6.2.108). Untrusted documents are this app's normal input, so a known-high in that path is a v1.4 release blocker.Reachability: the current exploit path is not reachable under the inspected call sites
That wording is deliberate — it is a statement about our code, not a claim that the library is safe. The distinction is the whole reason this is worth writing down.
getDocumentwith no scripting option, and neither constructs anAnnotationLayernor importspdfjs-dist/web/*.PDFScriptingManager— the component that actually executes document JavaScript — exists only in the viewer build.grep -c PDFScriptingManageragainst the API buildbuild/pdf.mjsis 0, and barepdfjs-distresolves tomain: build/pdf.mjs. The only other pdfjs import in the repo is the parsing worker._bindJSActiononly binds DOM listeners that dispatch into a sandbox the viewer is expected to provide.So the path is not reachable today — because of what we do or don't construct.
Correction to this issue's own acceptance
Item 2 asked for
enableScripting: falseto be passed explicitly on bothgetDocumentcalls. That is not implementable, and I did not force it:enableScriptingis declared onAnnotationLayer,AnnotationLayerBuilderandPDFViewer— it is not agetDocumentparameter. The advisory's workaround ("setenableScriptingtofalseor set a CSP") targets the viewer, which this app does not use. There is no switch to flip at our call sites.So the mitigation is structural, and it is now a test instead of a one-time inspection:
test/pdfjsScriptingBoundary.test.tsfails if any source filepdfjs-dist/web/*/pdf_viewer), where the scripting manager lives,AnnotationLayer/AnnotationLayerBuilder/PDFScriptingManager/ScriptingManager, orenableScripting: true.It carries a self-check (
the guard actually scans the source tree) so it cannot pass by scanning nothing, and I confirmed it fails when anew AnnotationLayer()probe is injected. That is what turns "we happen not to do this" into a CI-enforced invariant — and it is what makes the "not reachable" conclusion above durable rather than historical.The CSP item is already satisfied
The issue asked whether a CSP is additionally warranted for the reader. It is already in place, in
src/renderer/index.html:script-src 'self'with no'unsafe-inline'and no'unsafe-eval'. The advisory requires "no CSP for disallowing script-src"; that condition is not met. So the renderer — the process that renders untrusted documents — has two independent barriers: no scripting machinery is constructed, and injected script would be blocked by CSP anyway.The upgrade: 5.7.284 → 6.3.289
The entire API breakage is one moved method.
destroy()is gone fromPDFDocumentProxy(which keepscleanup()) and now lives onPDFDocumentLoadingTask. Three mechanical call sites:PdfLoader.tsPdfSourceReader.tsxNothing else broke.
engineson 6.x is>=22.13.0 \|\| >=24; CI and CONTRIBUTING both pin Node 24.Rollback plan
Mechanically two steps: restore
^5.x, revert the threedestroy()call sites.It is deliberately not the default, because it re-opens this issue — the version would be inside a known-high range again. The structural guard and the CSP survive a rollback, so the residual risk is specifically "a known-high version ships", which is what the release policy forbids. A rollback therefore needs an explicit maintainer decision and a note in the release, not a quiet revert.
Acceptance
enableScripting: falseon bothgetDocumentcalls>=6.2.108PdfLoaderregressionnpm auditclean for pdfjs-distfound 0 vulnerabilitiesVerification
npm run typecheck— all three projectsnpm test— 258 pass / 0 fail (4 new: the boundary guard)npm run build:unpack && npm run smoke:packaged— executed on the packaged app, 20/20 checks, includingpdfjs-dist loads and polyfilled DOMMatrix(the historical startup crash point) and a realPDF import extracts textfixture parse under 6.3.289:npm audit --omit=dev --registry=https://registry.npmjs.org— found 0 vulnerabilitiesnpm run check:design,npm run lint(0 errors, 107 warning baseline unchanged),npx prettier --checkNot verified
The reader's rendering path — canvas render, text-layer selection (#73 excerpts depend on it), bbox highlighting, worker loading, zoom — needs a real window and remains manual QA. Typecheck and the packaged build cover it structurally, and
PdfSourceReaderhad to change for thedestroy()move, so this is the one place worth a human pass before release. CI covers the packaged main-process parse on Windows and macOS.isEvalSupported(font-dataeval) is a different vector from this CVE and is not addressed here.