Fixing CSP Errors That Block Wasm Compilation

This page answers one task: the console shows CompileError: WebAssembly.instantiate(): Refused to compile or instantiate WebAssembly module because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'" (or a similar message), and you need WebAssembly to work without weakening the page’s Content Security Policy more than necessary.

Prerequisites

  • [ ] Access to where the policy is set: an HTTP header, a <meta http-equiv> tag, a framework config or an extension manifest.
  • [ ] The current policy text, from the response headers or the meta tag.
  • [ ] The list of browsers you support.

Why CSP blocks WebAssembly at all

A Content Security Policy restricts where a page can load and execute code from. Its script-src directive governs JavaScript — and, by specification, the compilation of WebAssembly, which is code generation from bytes much like eval is code generation from strings. Originally the only way to allow it was 'unsafe-eval', which also allows eval(), new Function() and string arguments to setTimeout — exactly the JavaScript features strict policies exist to forbid. That trade-off kept many security-conscious sites away from WebAssembly.

The 'wasm-unsafe-eval' source keyword solves this. It allows WebAssembly compilation and instantiation — WebAssembly.compile, instantiate, instantiateStreaming, new WebAssembly.Module — and nothing else: JavaScript eval remains blocked. All current browsers support it. So the fix for nearly every case is to add 'wasm-unsafe-eval' to script-src and leave 'unsafe-eval' out.

What each CSP keyword allows Without either keyword, WebAssembly compilation and JavaScript eval are both blocked. wasm-unsafe-eval allows WebAssembly compilation only, keeping eval blocked. unsafe-eval allows both, weakening the policy for JavaScript as well. neither keyword Wasm compilation blocked eval blocked strictest, Wasm unusable breaks Wasm 'wasm-unsafe-eval' Wasm compilation allowed eval still blocked minimal relaxation use this 'unsafe-eval' Wasm compilation allowed eval and new Function allowed weakens JS protections avoid

Step 1 — find the effective policy

A page can have several policies at once — one from a header, another from a meta tag, a third from a reverse proxy — and the browser enforces all of them; any one can block compilation. Collect them:

curl -sI https://app.example.com/ | grep -i content-security-policy
curl -s https://app.example.com/ | grep -io '<meta[^>]*content-security-policy[^>]*>'

In DevTools, the console error quotes the exact directive that blocked compilation, which tells you which policy to edit. Note that default-src is used when script-src is absent, so a policy with only default-src 'self' blocks WebAssembly too.

Step 2 — add wasm-unsafe-eval to script-src

Content-Security-Policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self'; object-src 'none'; base-uri 'none'

If the policy uses nonces or hashes with 'strict-dynamic', add the keyword alongside them: script-src 'nonce-r4nd0m' 'strict-dynamic' 'wasm-unsafe-eval'. The module file itself is fetched, not executed as a script, so .wasm URLs are governed by connect-src when loaded with fetch — include the CDN origin there if modules come from elsewhere. Frameworks that generate CSP headers (Next.js, SvelteKit, Helmet for Express) accept extra script sources in their configuration; set it there rather than patching headers downstream.

Step 3 — cover workers

Dedicated workers created from script URLs get their own policy from the worker script’s response headers in some configurations, and inherit the creating document’s policy for blob: and data: workers. If the module is compiled inside a worker and the error appears in the worker’s console, set the same script-src with 'wasm-unsafe-eval' on the worker script’s response, or compile the module on the main thread and post the WebAssembly.Module to the worker. Worker loading patterns are discussed in loading Wasm in a Web Worker with ESM.

Which policy governs each step of loading Wasm Fetching the .wasm file is governed by connect-src. Compiling and instantiating is governed by script-src and needs wasm-unsafe-eval. Inside a worker, the worker's own policy applies, so the keyword must be present there too. fetch app.wasm connect-src compile / instantiate script-src + wasm-unsafe-eval worker script its own policy compile in worker needs the keyword too module running no further CSP checks

Step 4 — handle extensions and special hosts

Browser extensions follow the same rule with stricter defaults. Manifest V3 extensions cannot use 'unsafe-eval' at all in extension pages, but may add 'wasm-unsafe-eval' to content_security_policy.extension_pages in the manifest:

{
  "manifest_version": 3,
  "content_security_policy": {
    "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
  }
}

Content scripts run under the host page’s policy in some browsers, which you do not control; compile modules in the extension’s service worker or an offscreen document instead, as covered in using Wasm in a Manifest V3 browser extension. Electron apps that set a CSP in their HTML need the same keyword.

Step 5 — verify and monitor with report-only

Before tightening an existing policy, deploy it in Content-Security-Policy-Report-Only mode with a reporting endpoint, so violations are reported but not enforced. Check that no WebAssembly-related violations appear, then switch to enforcement. In production, keep reporting enabled: a later change that drops 'wasm-unsafe-eval' — for example a security team standardising headers — shows up in reports before users complain. Combined with the recommendations in writing a Content Security Policy for Wasm, this keeps both the policy and the feature intact.

Why wasm-unsafe-eval is a reasonable risk

The keyword’s name sounds alarming, and security reviewers sometimes push back. It helps to explain what it permits. JavaScript eval is dangerous because attackers who inject a string can turn it into code with the page’s full privileges. WebAssembly compilation can only produce code that runs in the Wasm sandbox: it can touch its own linear memory and call only the imports the page explicitly provides. An attacker who could feed bytes to WebAssembly.compile would still need the page to instantiate the result with useful imports and call it, and the page’s code decides that. The realistic risk is therefore much smaller than 'unsafe-eval', which is why the keyword was introduced as a separate permission. The remaining concern — loading modules from untrusted origins — is handled by connect-src restrictions on where modules can be fetched, by subresource-integrity-style hash checks, as in verifying Wasm integrity before instantiation, and by never compiling bytes derived from user input.

Legacy browsers and older guidance

Older articles and Stack Overflow answers often recommend 'unsafe-eval' because 'wasm-unsafe-eval' did not exist or was not yet supported everywhere when they were written. Support arrived in Chromium 97, Firefox 102 and Safari 16; if you must support older Safari, the choices are to accept 'unsafe-eval' for those users only (by varying the header per user agent, which is fragile), to use a JavaScript fallback such as wasm2js output for them, or to drop support. For most audiences today the keyword alone is sufficient. Remove 'unsafe-eval' from policies that added it only for WebAssembly; leaving it in silently re-enables eval for every script on the page.

Testing the policy in CI

Header changes are made by many hands — platform teams, CDN configuration, framework upgrades — so a regression test is worth having. Run an end-to-end test that loads a page using WebAssembly with the production headers (served by the same configuration that deploys them, or fetched from a staging deployment) and asserts that the module instantiates and that no securitypolicyviolation events fire. A short script can also parse the production policy from curl -I and assert that script-src contains 'wasm-unsafe-eval' and does not contain 'unsafe-eval'. The second check protects the strictness of the policy as much as the first protects the feature. Add both to the deployment smoke tests, so a change that silently disables WebAssembly or weakens the policy is caught before users see it.

Expected output

With script-src 'self' 'wasm-unsafe-eval', the module compiles and runs; eval("1+1") in the console still fails with a CSP error; and report-only monitoring shows no WebAssembly-related violations.

Gotchas

  • Adding 'unsafe-eval' instead. It works but re-enables JavaScript eval. Use 'wasm-unsafe-eval'.
  • Two policies, one forgotten. A meta tag or proxy header may still block it. Check every policy.
  • Only default-src present. It applies to scripts too. Add an explicit script-src.
  • Worker scripts with their own policy. Add the keyword there or compile on the main thread.
  • Missing connect-src for CDN-hosted modules. The fetch fails before compilation. Allow the origin.
  • Stale policies cached by a CDN. Header changes need a cache purge to take effect everywhere.

Performance note

CSP checks happen once per compilation call and added no measurable time in Chrome. The only performance effect of getting CSP wrong is indirect: sites that fall back to wasm2js JavaScript to avoid the keyword ran the same workload 2–5× slower.

Running the same workload under different CSP choices Relative run time of a compute workload when WebAssembly is allowed with wasm-unsafe-eval, and when a strict policy forces a fallback to wasm2js-generated JavaScript. run time relative to Wasm Wasm with wasm-unsafe-eval 1 × wasm2js fallback (strict CSP) 3.4 ×

Frequently Asked Questions

Does 'wasm-unsafe-eval' allow eval()? No. It applies only to WebAssembly compilation and instantiation.

Do I need it for instantiateStreaming only? For every way of compiling: streaming, from bytes, and new WebAssembly.Module.

Can I restrict which modules may be compiled? Not with CSP itself; restrict where they can be fetched with connect-src and verify hashes before compiling.

Is it needed in Node or Deno? No — CSP is a browser mechanism. Deno has its own permission model instead.

How do I listen for CSP violations in the page? Add a securitypolicyviolation event listener on document; it fires with the blocked directive and source, which you can report.

Does the order of keywords in script-src matter? No. Source expressions are a set; the browser allows anything any of them permits.

← Back to Troubleshooting Common Wasm Errors