Skip to content

fix(wasm): load without script-src 'unsafe-eval' - #717

Merged
andiwand merged 2 commits into
mainfrom
fix/wasm-no-dynamic-execution
Aug 20, 2026
Merged

fix(wasm): load without script-src 'unsafe-eval'#717
andiwand merged 2 commits into
mainfrom
fix/wasm-no-dynamic-execution

Conversation

@andiwand

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Closes #709.

embind generates its invoker functions at run time with new Function, which CSP treats as eval — so Odr.load() threw

EvalError: Evaluating a string as JavaScript violates the following Content Security Policy
directive because 'unsafe-eval' is not an allowed source of script: script-src 'self' 'wasm-unsafe-eval'

under the policy a page whose selling point is that documents are processed locally would actually want to ship. The glue contains no literal eval(, which is what makes it easy to misdiagnose.

-sDYNAMIC_EXECUTION=0 picks embind's closure-based path instead (#if DYNAMIC_EXECUTION == 0 in embind.js, present in the pinned emsdk 3.1.73). What it costs is emscripten_run_script, dlopen, and ccall/cwrap for anything not exported in advance — none of which the bindings use. script-src 'self' 'wasm-unsafe-eval' is now enough.

Verified

Built locally with the emscripten-wasm profile, emsdk 3.1.73:

before after
new Function / eval( in odr-core.mjs present none
loads with dynamic code construction blocked EvalError at initRuntime loads and renders
node test suite 28 pass 30 pass
odr-core.wasm, brotli 950086 950086 (glue 94973 → 93508)

The "blocked" run is a child process with globalThis.Function proxied to throw on construct and apply, standing in for a policy without 'unsafe-eval'; it renders mixed-layout.odt end to end afterwards. That is the first of the two tests in wasm/tests/csp.test.mjs; the second asserts statically that the shipped glue builds no code at run time, so dropping the flag fails the suite rather than only failing in a browser.

Note: this documents only the module's own script-src line, under Hosting. What the rendered output needs — #710 — is a separate section in a separate PR; the two do not overlap.

andiwand and others added 2 commits August 20, 2026 14:25
embind generates its invoker functions at run time with `new Function`,
which CSP treats as eval, so `Odr.load()` threw an `EvalError` under any
policy that did not grant `'unsafe-eval'` — the one thing a page whose
selling point is that documents are processed locally would rather not
grant. The glue contains no literal `eval(`, which makes it easy to
misdiagnose.

`-sDYNAMIC_EXECUTION=0` picks embind's closure-based path instead. What
it costs is `emscripten_run_script`, `dlopen`, and `ccall`/`cwrap` for
anything not exported in advance — none of which the bindings use.
`script-src 'self' 'wasm-unsafe-eval'` is now enough.

Closes #709

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDs5aK3ZGSZsEvqUUwBBXU
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015d5RcmsA777vwXiuafjx6k
@andiwand
andiwand force-pushed the fix/wasm-no-dynamic-execution branch from a3451ff to 99b6eb1 Compare August 20, 2026 12:25
@andiwand
andiwand merged commit 7f0c461 into main Aug 20, 2026
28 checks passed
@andiwand
andiwand deleted the fix/wasm-no-dynamic-execution branch August 20, 2026 12:32
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.

Odr.load() needs script-src 'unsafe-eval': embind builds invokers with new Function

1 participant