Skip to content

Vendor wasm-feature-detect instead of loading it unpinned from unpkg (scrutineer #2520) - #2404

Draft
JasonGross wants to merge 1 commit into
masterfrom
fable/fix-2520-pin-wasm-feature-detect
Draft

Vendor wasm-feature-detect instead of loading it unpinned from unpkg (scrutineer #2520)#2404
JasonGross wants to merge 1 commit into
masterfrom
fable/fix-2520-pin-wasm-feature-detect

Conversation

@JasonGross

Copy link
Copy Markdown
Collaborator

Fixes scrutineer finding #2520 (CWE-829, Low).

The bug

fiat-html/fiat-crypto.html (line 251) loaded https://unpkg.com/wasm-feature-detect/dist/umd/index.js with no version in the URL, no integrity attribute and no crossorigin attribute. The page is deployed verbatim from fiat-html/ to https://mit-plv.github.io/fiat-crypto/ (by deploy-html-fast.yml and the deploy-js-wasm-of-ocaml job of coq-docker.yml), so every visitor executed whatever unpkg returned for that URL, as a classic script with full access to the page origin (localStorage synthesis cache, generated output in the DOM). The only consumer is disable-wasm-option.js, which calls wasmFeatureDetect.tailCall()/gc()/exceptions() to decide whether to enable the WASM checkbox.

The fix

Vendor the UMD build of wasm-feature-detect@1.9.0 (latest release, published 2026-08-11, and the version the unversioned unpkg URL currently 302-redirects to, so the page's behaviour is unchanged) into fiat-html/vendor/wasm-feature-detect/:

  • wasm-feature-detect-1.9.0.umd.js: byte-for-byte dist/umd/index.js from the npm tarball (not edited, so it can be re-verified against upstream by hash).
  • LICENSE: the package's Apache-2.0 licence text (the package is Apache-2.0, not MIT).
  • README.md: version, upstream URLs, the registry sha512 integrity of the tarball, the SRI sha384 of the vendored file, and update instructions.

fiat-crypto.html now loads vendor/wasm-feature-detect/wasm-feature-detect-1.9.0.umd.js with an HTML comment recording version, licence and source URL. The page no longer runs any third-party-hosted script.

Alternatives considered: pinning the version in the unpkg URL and adding integrity="sha384-..." crossorigin="anonymous" would also close the finding, but keeps a runtime dependency on a CDN (an outage or a crossorigin misconfiguration would silently disable the WASM option). Vendoring a 3.6 KB file is simpler and makes the deployed page self-contained. No other unpinned third-party script exists: grep -rnE 'https?://[^" ]*\.(js|mjs|css)|unpkg|jsdelivr|cdnjs' fiat-html .github/workflows only matched this line (the dl-cdn.alpinelinux.org hits are apk repositories, not scripts).

Verification

  • curl https://registry.npmjs.org/wasm-feature-detect: dist-tags.latest = 1.9.0, license = Apache-2.0, integrity sha512-zonE+xlIIYtxPy++L24ow0hAD8CICb4+FgPyROd3buyXIqsJvUEDkBgfCCoXOd1Hu3DUr0GOfnPIdcGV+YpNaA==.
  • Downloaded the tarball; openssl dgst -sha512 of it matches the registry integrity hash.
  • cmp package/dist/umd/index.js <(curl https://unpkg.com/wasm-feature-detect@1.9.0/dist/umd/index.js): identical; cmp of the vendored file against that unpkg copy: identical. curl -I https://unpkg.com/wasm-feature-detect/dist/umd/index.js is a 302 to /wasm-feature-detect@1.9.0/dist/umd/index.js, i.e. the deployed page was already getting exactly these bytes.
  • node --check fiat-html/vendor/wasm-feature-detect/wasm-feature-detect-1.9.0.umd.js: OK (node v22.23.1). Loading it in a vm context defines global wasmFeatureDetect with 23 probes; tailCall, gc, exceptions are functions and each resolves to true under node.
  • Served fiat-html/ with python3 -m http.server and fetched index.html (symlink to fiat-crypto.html), fiat-crypto.html, vendor/wasm-feature-detect/wasm-feature-detect-1.9.0.umd.js and vendor/wasm-feature-detect/LICENSE: all 200, so the relative path resolves from the page.
  • make -f Makefile.js-html fiat-html/version.js (the only Makefile target the deploy workflows run on fiat-html/) still works; git check-ignore fiat-html/vendor/... confirms nothing under fiat-html/vendor/ is gitignored. Both deploy jobs check out the repo before publishing the whole fiat-html/ folder, and deploy-html-fast.yml restores tracked files with git checkout HEAD --, so the vendored files are published.

Not verified

  • I did not load the page in a real browser; the check that disable-wasm-option.js still enables/disables the checkbox correctly was done only by running the vendored script under node. The dry-run deploy in CI (deploy-js-wasm-of-ocaml on PRs) should confirm the folder layout.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Rbn2gww3MGhvrh52fNjpD

fiat-html/fiat-crypto.html loaded
https://unpkg.com/wasm-feature-detect/dist/umd/index.js with no version,
no `integrity` and no `crossorigin` attribute.  The published page at
https://mit-plv.github.io/fiat-crypto/ (deployed verbatim from fiat-html/)
therefore executed whatever the CDN returned for that URL, with full
access to the page origin, even though the script is only used by
disable-wasm-option.js for a cosmetic capability probe (scrutineer
finding #2520, CWE-829).

Vendor the UMD build of wasm-feature-detect@1.9.0 (the version the
unversioned unpkg URL currently redirects to) under
fiat-html/vendor/wasm-feature-detect/, byte-for-byte identical to
dist/umd/index.js from the npm tarball, together with its Apache-2.0
LICENSE and a README recording the version, source URL, registry
integrity hash and SHA-384 of the file, plus update instructions.  Load
it from the local path so the page no longer runs any third-party-hosted
script.  The deploy workflows publish the whole fiat-html/ folder from a
checkout, so the vendored files ship with the page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Rbn2gww3MGhvrh52fNjpD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant