Repository navigation
fix(security): establish canonical npm, PDF.js, Nanoid, and Undici baseline #783
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 75 commits
Commits
Show all changes
93 commits
Select commit
Hold shift + click to select a range
4294ddc
test(supply-chain): require deterministic npm lock generator
seonghobae afb20be
fix(supply-chain): pin npm lock generator metadata
seonghobae e64c229
ci(supply-chain): prove npm version and lock reproduction
seonghobae ebbde9b
docs(supply-chain): record npm generator provenance
seonghobae 694ca83
docs(changelog): record npm generator contract
seonghobae 90dc2a0
fix(supply-chain): avoid serializing npm into runtime engines
seonghobae fecc36b
test(supply-chain): keep npm enforcement out of runtime engines
seonghobae 9eb83c3
docs(supply-chain): separate npm generator from runtime engines
seonghobae 5609b88
ci(supply-chain): publish deterministic lock reproduction evidence
seonghobae 032314d
test(supply-chain): require preserved lock reproduction evidence
seonghobae 0263ad4
test(security): require coordinated PDF.js and Undici baseline
seonghobae 2cbf767
test(security): disable PDF expression evaluation
seonghobae 475de96
fix(security): pin the patched Undici transitive version
seonghobae deb74ac
fix(security): pin the patched PDF.js release
seonghobae dc90d5b
fix(security): disable PDF expression evaluation
seonghobae b23b957
docs(security): record coordinated PDF and HTTP remediation
seonghobae 2e99f72
docs(changelog): record coordinated security remediation
seonghobae 8f50fe4
fix(security): anchor the Undici override to an exact root floor
seonghobae e2c0c2d
test(security): bind exact root floor and npm package locations
seonghobae dd93f96
ci(pr783): import exact npm 10.9.8 lock artifact
seonghobae dd8d1ac
fix(score): align PDF.js boundary with 6.2.108 API
seonghobae 988dc1d
test(score): prove the supported data-only PDF.js boundary
seonghobae a39e37f
docs(security): record the supported PDF.js 6.2.108 boundary
seonghobae 6b753e6
docs(changelog): describe the supported patched PDF boundary
seonghobae b773653
ci(pr783): rerun lock import after supported API repair
seonghobae 01cb39e
ci(pr783): publish the verified lock from the bounded importer
seonghobae ad558ac
ci(pr783): fetch complete lineage for verified lock publication
seonghobae 83865dc
fix(security): import verified npm 10.9.8 lock
github-actions[bot] e6b48ca
test(ci): preserve canonical npm provenance formatting
seonghobae 3edf173
ci(pr783): diagnose Ruff import ordering
seonghobae 2345219
style(ci): normalize security test imports
seonghobae dc7e8b4
style(ci): normalize npm provenance test imports
seonghobae c5ee630
chore(ci): remove completed Ruff diagnostic
seonghobae f0c9ad1
ci(pr783): finalize exact Ruff formatting
seonghobae 102a89f
chore(ci): remove temporary branch writer
seonghobae d4887ec
style(test): apply Ruff formatting to security contracts
seonghobae 459abdd
style(test): finish Ruff formatting for npm provenance
seonghobae ace4257
fix(security): refresh vulnerable npm transitive pins
seonghobae 63d0ae6
fix(deps): restore manifest-lock consistency
seonghobae 6bba612
ci: add one-shot canonical lock repair for PR 783
seonghobae ef938fa
fix(deps): regenerate canonical npm 10.9.8 lock
github-actions[bot] a48ff96
ci: retrigger exact-head validation after canonical lock generation
seonghobae fd9389a
ci: add one-shot Ruff formatter for PR 783
seonghobae afba132
style(test): apply canonical Ruff formatting
github-actions[bot] e214e96
ci: retrigger exact-head validation after canonical Ruff formatting
seonghobae 0cd6836
fix(test): verify canonical hoisted PDF.js lock record
seonghobae f4a5a19
ci: add final one-shot Ruff formatter for PR 783
seonghobae 219267e
style(test): apply final canonical Ruff formatting
github-actions[bot] db7b1c1
chore(ci): retrigger exact-head protected checks
seonghobae f4d8850
fix(ci): validate npm lock without mutable resolution
seonghobae 8a143ab
test(supply-chain): format npm toolchain contract
seonghobae b3ac826
test(supply-chain): satisfy ruff formatting gate
seonghobae 885c037
test(supply-chain): format npm toolchain contract
seonghobae de0cd6b
test(supply-chain): apply repository Ruff width
seonghobae f3cfae4
docs(security): align npm lock provenance with frozen validation
seonghobae de75178
fix(ci): disable persisted checkout credentials
seonghobae 164e6f2
docs(changelog): correct UI test coverage claim
seonghobae 07c1dd2
test(lock): reject esbuild peer metadata drift
seonghobae ad4551b
docs(lock): record esbuild metadata sentinel
seonghobae 6f81f52
docs(changelog): record lock metadata drift gate
seonghobae 6665f02
fix(security): make PDF XML boundary explicit
seonghobae bf5a3a3
test(security): lock down PDF parser options
seonghobae c65f5b1
docs(security): record PDF parser hardening evidence
seonghobae dae5d3c
docs(security): correct PDF.js advisory attribution
seonghobae d1d8641
test: measure Python branch coverage
seonghobae 2b543d5
test: cover previously hidden Python branch arcs
seonghobae 4ec36a6
refactor: remove impossible handoff membership branch
seonghobae e18894a
style: format branch-coverage source repair
seonghobae 42e0b16
chore: run one-shot Ruff formatter for PR 861
seonghobae f3a0c27
style: apply repository Python formatter
github-actions[bot] fcf7e52
test: revalidate formatted branch-coverage head
seonghobae 6afbc9f
test: cover flat novelty normalization branch
seonghobae ce35d3d
test(ci): require credential-free lock validation checkout
seonghobae 99e6be9
fix(ci): drop checkout credentials from lock validation
seonghobae 0130f7b
merge(quality): compose dependency security with branch coverage
seonghobae f8dc2e1
test(score): prove PDF byte-buffer isolation
seonghobae 06e060b
test(coverage): prove shared-hit scan reaches later stems
seonghobae 4fac4e7
test(supply-chain): validate npm CI structure fail-closed
seonghobae 4450416
test(supply-chain): reject vulnerable npm tar runtime
seonghobae b77ff7c
fix(supply-chain): verify patched npm tar runtime
seonghobae 1cd71db
fix(supply-chain): pin patched npm runtime
seonghobae f29622d
fix(ci): activate patched npm before dependency reads
seonghobae 9e06df0
docs(supply-chain): record patched npm runtime boundary
seonghobae ec40dda
docs(security): align dependency baseline with patched npm
seonghobae c516f1d
docs(changelog): record patched npm tar runtime
seonghobae 81b2901
test(ci): expose pre-Corepack npm cache bootstrap
seonghobae 27a633d
fix(ci): defer npm use until pinned Corepack runtime
seonghobae 05ca286
fix(release): activate pinned npm before dependency install
seonghobae cf1b728
fix(security): verify pinned npm before audit install
seonghobae 0facdbf
fix(build): use pinned npm after Corepack activation
seonghobae 96674b2
test(ci): accept combined npm activation audit step
seonghobae db4aa09
fix(ci): disable checkout credentials for npm consumers
seonghobae 1168c8f
chore(ci): re-trigger required checks after provider-prefix migration
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; | ||
| import { configureScorePdfWorker, loadScorePdf } from "./pdfjs"; | ||
|
|
||
| vi.mock("pdfjs-dist", () => ({ | ||
| getDocument: vi.fn(() => ({ promise: Promise.resolve(), destroy: vi.fn() })), | ||
| GlobalWorkerOptions: { workerSrc: "" } | ||
| })); | ||
|
|
||
| vi.mock("pdfjs-dist/build/pdf.worker.min.mjs?url", () => ({ | ||
| default: "/assets/pdf.worker.min.mjs" | ||
| })); | ||
|
|
||
| describe("score PDF.js boundary", () => { | ||
| beforeEach(() => { | ||
| vi.mocked(getDocument).mockClear(); | ||
| GlobalWorkerOptions.workerSrc = ""; | ||
| }); | ||
|
|
||
| it("uses the locally bundled worker asset", () => { | ||
| configureScorePdfWorker(); | ||
|
|
||
| expect(GlobalWorkerOptions.workerSrc).toBe("/assets/pdf.worker.min.mjs"); | ||
|
|
||
| configureScorePdfWorker(); | ||
| expect(GlobalWorkerOptions.workerSrc).toBe("/assets/pdf.worker.min.mjs"); | ||
| }); | ||
|
|
||
| it("copies validated bytes through the hardened data-only API", () => { | ||
| const source = new Uint8Array([0x25, 0x50, 0x44, 0x46]); | ||
|
|
||
| loadScorePdf(source); | ||
|
|
||
| expect(getDocument).toHaveBeenCalledTimes(1); | ||
| const parameters = vi.mocked(getDocument).mock.calls[0]?.[0]; | ||
| expect(parameters).toBeTypeOf("object"); | ||
| expect(Object.keys(parameters as object)).toEqual([ | ||
| "data", | ||
| "enableXfa", | ||
| "useWorkerFetch" | ||
| ]); | ||
| const hardenedParameters = parameters as { | ||
| data: Uint8Array; | ||
| enableXfa: boolean; | ||
| useWorkerFetch: boolean; | ||
| }; | ||
| expect(hardenedParameters.data).toEqual(source); | ||
| expect(hardenedParameters.data).not.toBe(source); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| expect(hardenedParameters.enableXfa).toBe(false); | ||
| expect(hardenedParameters.useWorkerFetch).toBe(false); | ||
| }); | ||
| }); | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # High-security PDF and HTTP dependency baseline | ||
|
|
||
| ## Decision | ||
|
|
||
| BandScope treats the PDF parser and its transitive HTTP client as one security-release boundary: | ||
|
|
||
| - `pdfjs-dist` is pinned exactly to `6.2.108`; | ||
| - `undici` is pinned exactly to `7.29.0` through the root npm override; and | ||
| - npm `10.9.8` is the approved generator for reviewed root-workspace dependency updates, while primary CI consumes the committed lock through frozen validation rather than re-resolving it. | ||
|
|
||
| Repository dependency/security tooling reported the protected-base `pdfjs-dist@6.1.200` as requiring a newer floor. That finding is kept distinct from the older, GitHub-reviewed CVE-2024-4367 / GHSA-wgrm-67xf-hhpq: the 2024 advisory affected `pdfjs-dist <=4.1.392` and was fixed in `4.2.67`, so it is historical parser-risk context and is **not** evidence that `6.1.200` was affected by that CVE. BandScope pins the current `6.2.108` artifact selected by the repository security baseline and requires current-head audit/security evidence rather than misattributing a scanner result to an unrelated advisory. | ||
|
|
||
| PDF.js `6.2.108` no longer exposes the legacy `isEvalSupported` member in its public `DocumentInitParameters` contract, and `getDocument` no longer reads that member. BandScope therefore does not cast or pass an unknown option that would be ignored while creating false assurance. The parser boundary is reinforced by a narrow data-only call, copied caller-owned bytes, a same-origin bundled worker, explicit `enableXfa: false`, and explicit `useWorkerFetch: false`. | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| A[Validated local PDF bytes] --> B[Copied Uint8Array] | ||
| B --> D[Data-only DocumentInitParameters] | ||
| D --> X[XFA disabled] | ||
| D --> F[Worker helper fetch disabled] | ||
| X --> C[pdfjs-dist 6.2.108] | ||
| F --> C | ||
| C --> W[Same-origin bundled worker] | ||
| W --> R[Canvas render] | ||
| J[jsdom development path] --> U[undici 7.29.0 override] | ||
| N[npm 10.9.8 approved update toolchain] --> L[Reviewed package-lock artifact] | ||
| L --> V[npm ci frozen validation] | ||
| V --> C | ||
| V --> U | ||
| ``` | ||
|
|
||
| ## Threat boundary | ||
|
|
||
| The score viewer accepts only bytes already copied into the app-owned workspace through the native PDF intake boundary. It does not accept a URL, credentials, custom request headers, or a remote worker. It also disables XFA rendering and PDF.js worker-side fetching of helper resources at this wrapper boundary. These controls prevent the caller from selecting an attacker-controlled document origin or worker asset and make the intended no-XML-form/no-worker-fetch policy explicit rather than relying on upstream defaults. | ||
|
|
||
| PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, metadata/XML parsing, and resource-exhaustion paths can still occur inside a syntactically valid PDF. The exact dependency lock, copied data-only input, explicit parser options, same-origin worker, and existing native intake limits therefore remain mandatory for locally selected files. | ||
|
|
||
| The pinned PDF.js XML parser does not expose an external-entity resolver through this wrapper: its default `onDoctype()` hook is a no-op, and `onResolveEntity()` resolves only the built-in XML entities before returning an unknown named entity literally. This source-level observation narrows what BandScope can claim; it is not a general assertion that every future PDF.js XML path is immune to entity-processing defects. Any parser upgrade must re-check the upstream implementation and repeat adversarial PDF verification. | ||
|
|
||
| Undici is currently a development dependency reached through jsdom, but development and CI parsers process attacker-controlled fixtures, generated HTML, and network-like request bodies. A dev-only label does not make header injection, shared-cache disclosure, retry desynchronization, or cookie-attribute injection acceptable in the trusted build boundary. | ||
|
|
||
| ## Strix finding adjudication boundary | ||
|
|
||
| Strix run `31871388084` on predecessor head `6f81f52c193c1e327d078eba7a2ea3bdbfbc87c2` reported a possible XXE path through `loadScorePdf`. Its attached proof-of-concept returned only a four-byte `%PDF` prefix and stated that construction of an actual PDF containing the alleged XML payload remained necessary. It did not demonstrate entity expansion, local-file disclosure, a network request, or parser output containing an external entity. | ||
|
|
||
| The finding was therefore not suppressed and was not treated as proven exploitation. Instead, the exact dependency source was inspected and the wrapper was hardened at the narrowest supported API boundary: XFA rendering and worker-side helper fetching are now explicitly disabled and regression-locked. A fresh exact-head Strix result remains mandatory; a predecessor report, whether pass or fail, is not transferable merge evidence. | ||
|
|
||
| ## Lockfile provenance | ||
|
|
||
| The dependency manifests and complete lock artifact were generated and reconciled on this branch with the approved Node `22.22.3` / npm `10.9.8` toolchain before the current frozen-validation gate was finalized. The historical generation run and artifact are provenance evidence only; they do **not** satisfy a later head's merge gate and primary CI intentionally does not repeat mutable dependency resolution. | ||
|
|
||
| For every current head, primary CI instead: | ||
|
|
||
| 1. verifies npm `10.9.8` before dependency consumption; | ||
| 2. runs `npm ci --ignore-scripts --no-audit --no-fund` in the dedicated lock-validation job; | ||
| 3. rejects any `package.json` or `package-lock.json` working-tree drift; and | ||
| 4. proceeds to normal repository verification only after the frozen lock is consumable by the approved toolchain. | ||
|
|
||
| Future dependency updates must use npm `10.9.8` to generate the complete lock in a dedicated update branch, review the entire resulting manifest/lock diff, and then prove frozen consumption on the resulting exact head. No tarball URL, SRI, dependency range, `peer` classification, or workspace record may be hand-edited merely to satisfy a validator. | ||
|
|
||
| The lock contract requires the exact public-registry tarball and SHA-512 SRI for both patched packages and requires every existing `node_modules/@esbuild/*` location to retain npm 10.9.8's `peer: true` classification. This distinguishes the intended security graph from unrelated Dependabot generator churn. The narrower provenance and validation contract is specified in `docs/doctoring/npm-lockfile-generator-provenance.md`. | ||
|
|
||
| ## Verification | ||
|
|
||
| The merge gate includes: | ||
|
|
||
| - exact manifest and lock artifact tests; | ||
| - a direct PDF.js wrapper test proving copied bytes, the locally bundled worker, `enableXfa: false`, `useWorkerFetch: false`, and no URL-bearing initialization member; | ||
| - TypeScript compilation against the installed PDF.js `DocumentInitParameters` rather than an unsafe cast; | ||
| - valid and malformed local score-PDF component tests; | ||
| - desktop lint, strict typecheck, complete measured tests, and production build; | ||
| - Tauri/Rust checks and native PDF intake regressions; | ||
| - `npm audit --workspaces --audit-level=high` with no high finding; | ||
| - repository SAST, CodeQL, security scan, secret scan, SBOM, and dependency evidence; | ||
| - current-head Strix evidence rather than predecessor-head scanner output; | ||
| - current-head central coverage and automated review; | ||
| - zero unresolved actionable threads and a qualifying independent non-author approval; and | ||
| - normal branch protection without administrative bypass. | ||
|
|
||
| ## Failure, rollback, and incident evidence | ||
|
|
||
| On a failed frozen-lock validation or parser regression, preserve the exact head SHA, Node/npm versions, original lock blob SHA, test output, audit report, and workflow run ID. If the incident concerns a dependency-generation change, also preserve the generated complete lock and the generation environment/configuration. Do not merge a partially updated graph. | ||
|
|
||
| Rollback restores the previous desktop manifest, root override, complete lock, PDF loader, tests, and CHANGELOG entry together. Because the previous graph contains known high findings, rollback is an emergency availability action only and requires an explicit security exception, compensating controls, owner, expiration, and immediate replacement plan. | ||
|
|
||
| ## References | ||
|
|
||
| GitHub. (2024). *PDF.js vulnerable to arbitrary JavaScript execution upon opening a malicious PDF* (GHSA-wgrm-67xf-hhpq) [Security advisory]. https://github.com/advisories/GHSA-wgrm-67xf-hhpq | ||
|
|
||
| Mozilla. (2026). *Document initialization parameters in PDF.js 6.2.108* [Source code]. GitHub. https://github.com/mozilla/pdf.js/blob/v6.2.108/src/display/api.js | ||
|
|
||
| Mozilla. (2026). *PDF.js XML parser in version 6.2.108* [Source code]. GitHub. https://github.com/mozilla/pdf.js/blob/v6.2.108/src/core/xml_parser.js | ||
|
|
||
| Mozilla. (2026). *PDF.js 6.2.108* [Software release]. https://github.com/mozilla/pdf.js/releases/tag/v6.2.108 | ||
|
|
||
| Node.js contributors. (2026). *Undici 7.29.0* [Software release]. https://github.com/nodejs/undici/releases/tag/v7.29.0 | ||
|
|
||
| npm, Inc. (2026). *npm ci*. npm Docs. https://docs.npmjs.com/cli/v11/commands/npm-ci/ | ||
|
|
||
| npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.