Security & CSP
The viewer runs under a strict Content Security Policy: no 'unsafe-inline', no 'unsafe-eval',
and Trusted Types on. This is the policy to start from:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'wasm-unsafe-eval';
style-src 'self';
worker-src 'self';
img-src 'self' blob: data:;
connect-src 'self';
require-trusted-types-for 'script';
trusted-types embedpdf;| Directive | Why the viewer needs it |
|---|---|
script-src 'wasm-unsafe-eval' | The engine is WebAssembly, and the browser asks for this to compile any WebAssembly. It allows nothing else: no eval, no inline scripts. With CloudPDF it’s needed only for Stamps and Signatures, which open their libraries in the browser. |
worker-src 'self' | The engine runs in a Web Worker, loaded from a file next to the viewer’s |
img-src blob: data: | Pages are drawn into images the browser makes, and tool cursors are small inline images |
connect-src 'self' | The engine’s WebAssembly, your documents, and any fonts or stamps you configure. Add each origin you load from. |
trusted-types embedpdf | The viewer’s one Trusted Types policy, which only allows URLs of its own files and the ones you configure |
style-src 'self' is enough, without 'unsafe-inline' or a nonce: the viewer never adds a
<style> tag or a style attribute to your page. Its styles go straight into its own shadow DOM,
which the browser doesn’t count as inline styles.
Loading from a CDN#
A browser only starts a Web Worker from its own origin. When the viewer’s script comes from
jsDelivr, it starts the engine’s workers from blob: URLs instead. Add the CDN to script-src and
connect-src (the script, its chunks and the WebAssembly come from there), and allow blob:
workers:
script-src 'self' https://cdn.jsdelivr.net 'wasm-unsafe-eval';
connect-src 'self' https://cdn.jsdelivr.net;
worker-src blob:;To keep worker-src 'self', serve the viewer’s files from your own site: install it from npm, or
copy its folder. See Self-hosting.
Builds that use blob: workers#
Most bundlers (Vite, webpack, Rspack, Parcel, Next.js) emit the workers as files of your build,
so worker-src 'self' covers them. Two builds can’t, and start the workers from blob: URLs, so they
need worker-src blob::
- Angular, which loads the engine’s portable build;
- plain esbuild, which needs that build too: see Self-hosting.
To keep worker-src 'self' there, copy the two worker files to your site and point the viewer at
them: see Self-hosting.
Your bundler’s Trusted Types policy#
With require-trusted-types-for 'script', the browser also checks how your bundler loads the rest
of your app’s code. Vite, esbuild and Angular need nothing more. The others load it through a
Trusted Types policy of their own, which trusted-types names next to embedpdf:
| Build | What to add |
|---|---|
| webpack 5 | output: { trustedTypes: 'webpack' } in your config, and trusted-types embedpdf webpack; |
| Rspack | output: { trustedTypes: 'rspack' } in your config, and trusted-types embedpdf rspack; |
| Next.js (webpack) | trusted-types embedpdf nextjs#bundler;. Next.js’s own inline scripts need its nonce setup. |
| Next.js (Turbopack) | Turbopack can’t run with require-trusted-types-for 'script' yet: leave that line out. |
A PDF you can’t trust#
A PDF can carry links, scripts and forms that send data somewhere. The viewer treats every one as untrusted:
- Scripts in a PDF, for form checks and calculations, run in a sandbox with no access to your
page, its cookies or the network. Turn them off with
plugins: { actions: { javascript: { enabled: false } } }. - Links open only after the user clicks them, and only
http,https,mailtoandtellinks.javascript:andfile:links do nothing, and so does a link relative to the document, such as/account, which would otherwise open a page of your own app. - Form submissions never send anything by themselves. A form’s submit button tells your code, and you decide where the data goes. See Actions & scripts.
Good to know#
Annotations and comments#
Text people type into annotations and comments is shown as text, never as HTML, so a comment can’t run a script in anyone else’s viewer.
Signatures#
A signature is checked against the certificates you trust and nothing else: the viewer never fetches a certificate or a revocation list from a URL inside the PDF.
Your feedback goes directly to the documentation team.