Skip to content

Bump pdfjs-dist to >= 6.2.108 (GHSA-hq66-cqwq-w95j); inlined worker pins an exact version and blocks overrides #43

Description

@mikehale

Summary

@rolemodel/spider 0.0.10 pins pdfjs-dist: ^5.4.624 (package.json#L42), which is affected by GHSA-hq66-cqwq-w95j (high — arbitrary JS execution on opening a malicious PDF). The fix is in pdfjs-dist 6.2.108, a major bump the current caret range forbids.

This surfaces as an undismissable Dependabot alert in every downstream consumer. We hit it in RoleModel/fmi-atlas, where spider is the sole path to pdfjs-dist.

While digging in, I found a second, independent packaging bug that is the reason consumers can't remediate this on their own — and that already breaks <rm-pdf-viewer> at runtime. Details below; happy to split it into its own issue if you'd prefer.

1. Requested change

Bump pdfjs-dist to >= 6.2.108 and cut a release.

For what it's worth, I don't think the advisory's exploit path is actually reachable through spider today, so this doesn't look urgent from a security standpoint. enableScripting is an annotation-layer option (in pdfjs-dist's built pdf.mjs it's only ever read as AnnotationElement/AnnotationLayer params), and spider renders canvas-only — pdf-page.js#L69 calls page.render({ canvasContext, viewport }) with no annotation or text layer, and enableScripting never appears in src/. So the scripting sandbox is never constructed. The bump is mostly about clearing the alert for consumers.

2. Packaging bug: the pdf.js worker is inlined at an exact version

vite.config.js externalizes pdfjs-dist as an exact string:

rollupOptions: {
  external: [/^lit/, /^@lit/, 'pdfjs-dist'],
}

That matches only the bare specifier. The ?url subpath imports in pdf-viewer.js#L5-L8 are not matched:

import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?url'
import jbig2WasmUrl from 'pdfjs-dist/wasm/jbig2.wasm?url'
import openjpegWasmUrl from 'pdfjs-dist/wasm/openjpeg.wasm?url'
import qcmsWasmUrl from 'pdfjs-dist/wasm/qcms_bg.wasm?url'

So Vite inlines them into the published dist/ as base64 data: URIs (dist/node_modules/pdfjs-dist/build/pdf.worker.js, ~1.98 MB decoded), and GlobalWorkerOptions.workerSrc is pinned to that copy.

The consequence: the main-thread API stays external and floats across the ^5.4.624 range, while the worker is frozen at whatever exact version spider was built against. pdf.js hard-throws on any mismatch:

const workerVersion = "5.4.624";
if (apiVersion !== workerVersion) {
  throw new Error(`The API version "${apiVersion}" does not match the Worker version "${workerVersion}".`);
}

This already breaks the viewer

In fmi-atlas the caret range resolved pdfjs-dist to 5.7.284, so the API sends apiVersion: "5.7.284" to an inlined worker hard-coded to "5.4.624" — every getDocument() throws. Any consumer resolving a pdfjs-dist newer than spider's exact build version gets a non-functional <rm-pdf-viewer>. (It went unnoticed for us only because we import the barrel and never instantiate the element.)

Reproduced against @rolemodel/spider@0.0.10 from the registry:

$ node -e '<decode the base64 data: URI in dist/node_modules/pdfjs-dist/build/pdf.worker.js>'
inlined worker bytes: 1978096
version literals: ["5.4.624"]

$ grep -n 'apiVersion' node_modules/pdfjs-dist/build/pdf.mjs
14826:    apiVersion: "5.7.284",

It also blocks downstream remediation

A consumer-side resolutions/overrides override of pdfjs-dist cannot fix issue 1, because it only moves the external API module. The vulnerable 5.x worker — where PDF parsing actually happens — stays bundled in spider's dist/, and the override guarantees the version-mismatch throw above. So downstream has no workaround at all: we had to leave the alert open and document why.

Suggested fix

Externalize the subpaths too, so the worker and WASM resolve from the consumer's own pdfjs-dist:

external: [/^lit/, /^@lit/, /^pdfjs-dist/],

That keeps API and worker on the same version by construction, drops ~2 MB of base64 from dist/, and lets consumers override pdfjs-dist if they ever need to. It does mean consumers must resolve the ?url subpath imports, so it's worth confirming against a plain webpack consumer (fmi-atlas is webpack 5) before release. Pinning pdfjs-dist to an exact version would also make the mismatch impossible, but leaves the override problem and the bundle size in place.

Glad to open a PR for either part if that's useful.


Found while triaging Dependabot alerts in RoleModel/fmi-atlas (PR #513).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions