Svelte generates innerHTML assignments breaking CSP "require-trusted-types-for 'script'" in production code
#10826
innerHTML assignments breaking CSP "require-trusted-types-for 'script'" in production codeDevelopment PRs
Fix webdino/sodium#1084 Work around sveltejs/svelte#10826
Svelte が innerHTML を出力しないようにコードを微調整します。他に innerHTML が使われているところは今のところ見当たりませんが、今後同じ問題が起きないようにビルド時のチェックも追加します。
Before submitting the PR, please make sure you do the following
Resolves #14438 Resolves #10826
This PR makes it possible to use Svelte on pages which require TrustedTypes support via their CSP by wrapping assignments to innerHTML in a TrustedTypePolicy called svelte-trusted-html if the TrustedTypes API exists.
Servers can allowlist the policy by setting require-trusted-types-for 'script'; trusted-types svelte-trusted-html in their Content-Security-Policy header.
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
Note: I haven't run the tests since I don't have pnpm setup properly.
I have tested that:
- A project with a CSP fails with Tip of Tree Svelte
- That project works when installing this revision of Svelte
- The project (with this revision) works in Browsers with no
TrustedTypessupport (i.e. Firefox, Safari)
- Run the tests with
pnpm testand lint the project withpnpm lint
My test project is here: https://github.com/fallaciousreasoning/svelte-tt-test/blob/master/src/routes/%2Bpage.server.js
The only changes to the default project is adding the CSP in src/routes/page.server.js
Issue
Describe the bug
In production code, Svelte may generate JS with innerHTML assignments, which are disallowed by default by the Content Security Policy (CSP) rule "require-trusted-types-for 'script'".
I can workaround this by adding the elements using document.createElement(). This way, Svelte obviously won't generate innerHTML since I already added things using JS, but it's very costly.
Another workaround is to add the following to svelte/App.svelte (see Reproduction for the repo). It seems ok to me since it's just escaping < and >, but I'm unsure about its security and performance.
<script>
if (
'trustedTypes' in window &&
window.trustedTypes !== null &&
typeof window.trustedTypes === 'object' &&
'createPolicy' in window.trustedTypes &&
window.trustedTypes.createPolicy !== null &&
window.trustedTypes.createPolicy instanceof Function
) {
window.trustedTypes.createPolicy('default', {
createHTML: (/** @type {string} */ string) => string.replace(/</g, '<').replace(/>/g, '>'),
})
}
</script>
This might be related at some level to #7943, #7908, and #8135
Reproduction
I created a minimal repo to reproduce the error. It uses NodeJS + Express and sets the CSP rules. The svelte code is under svelte and is compiled to the directory svelte/dist which is served by Express.
git clone [email protected]:planetsLightningArrester/svelte-csp-issue.git
cd svelte-csp-issue
# Build Svelte to `svelte/dist`
cd svelte
npm ci
npm run build
# Run the server
cd ..
npm ci
npm run start
Open http://localhost:3000. The page should be blank. Check the dev tools (tested on Chrome) and see the error This document requires 'TrustedHTML' assignment. in the console pointing to the generated code.
To check the expected behavior, open ./app.js, comment line 18, and re-start the server. The error should be gone and Hello should show.
Logs
index-BP59ppMc.js:5 This document requires 'TrustedHTML' assignment.
index-BP59ppMc.js:5
Uncaught TypeError: Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' assignment.
at Object.c (index-BP59ppMc.js:5:78293)
at fe (index-BP59ppMc.js:1:6802)
at new qf (index-BP59ppMc.js:5:80408)
at index-BP59ppMc.js:5:80547
System Info
System:
OS: Linux 5.15 Ubuntu 22.04.4 LTS 22.04.4 LTS (Jammy Jellyfish)
CPU: (12) x64 AMD Ryzen 5 5600G with Radeon Graphics
Memory: 10.65 GB / 15.52 GB
Container: Yes
Shell: 5.1.16 - /bin/bash
Binaries:
Node: 20.10.0 - ~/.local/bin/node
npm: 10.2.3 - ~/.local/bin/npm
npmPackages:
svelte: ^4.2.12 => 4.2.12
Severity
annoyance
Info
We ran into the same problem on YouTube with our browser extension built with Svelte. I didn’t add a policy, but instead tweaked the source a bit to remove innerHTML: https://github.com/videomark/videomark/pull/281/files
It seems ok to me since it's just escaping < and >, but I'm unsure about its security and performance.
I don't think that is what this policy is doing. It's just replacing those characters with themselves again.
What you'd really want is this one:
createHTML: (input: string) => input.replace(/<s/, '<').replace(/>/, '>')
Unfortunately, that shows how much Svelte is using this in many places in production builds as it breaks lots of things (also showing that the previous policy wasn't doing anything except letting everything through).
So with the policy mentioned in the original comment you're bypassing Trusted Types completely. You might as well turn them off rather than doing this which gives the illusion of safety without providing any.
Ideally svelte should provide a global config to set trusted types policy for strings used as innerHTML. You can see how it should be used here. A possible API and implementation:
// Usage
import { mount, set_policy } from 'svelte'
const policy = globalThis.trustedTypes?.createPolicy('svelte', { createHTML: html => html })
set_policy(policy)
// svelte/src/internal/client/dom/reconciler.js
let policy
export function set_policy(p) { policy = p }
/** @param {string} html */
export function create_fragment_from_html(html) {
var elem = document.createElement('template');
elem.innerHTML = policy?.createHTML(html) ?? html;
return elem.content;
}
The TrustedTypes API doesn't prevent {@html x} from being used to inject XSS attack from hackers if the author provides such usage. It is meant to prevent any non-trusted string from being assigned to innerHTML, where "trusted" means the string (TrustedHTML) can only be created by the policy instance, which is only accessible from author's codebase.
Since the template string generated by svelte is "known to be safe to use", it should be trusted, by adding a policy.
@dummdidumm Will the previous comment make sense?
Why not remove the use of innerHTML entirely?
See this commit for how it was implemented in Polymer:
Polymer/polymer@10220c9
Pro tip: You can prefix GitHub URLs of issues, PRs or discussions with svcl.dev/ to view them on this page! Also try it on a GitHub release ;)