Skip to content

security: avoid CSP conflict with sha/nonce during dev & add support for 'style-src-elem' #11562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/giant-years-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

feat: add CSP support for style-src-elem
5 changes: 5 additions & 0 deletions .changeset/tall-boats-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

fix: address CSP conflicts with sha/nonce during dev
38 changes: 35 additions & 3 deletions packages/kit/src/runtime/server/page/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,32 @@ class BaseProvider {
// }

// ...and add unsafe-inline so we can inject <style> elements
// Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list, so we remove those during dev when injecting unsafe-inline
if (effective_style_src && !effective_style_src.includes('unsafe-inline')) {
d['style-src'] = [...effective_style_src, 'unsafe-inline'];
d['style-src'] = [
...effective_style_src.filter(
(value) => !(value.startsWith('sha256-') || value.startsWith('nonce-'))
),
'unsafe-inline'
];
}

if (style_src_attr && !style_src_attr.includes('unsafe-inline')) {
d['style-src-attr'] = [...style_src_attr, 'unsafe-inline'];
d['style-src-attr'] = [
...style_src_attr.filter(
(value) => !(value.startsWith('sha256-') || value.startsWith('nonce-'))
),
'unsafe-inline'
];
}

if (style_src_elem && !style_src_elem.includes('unsafe-inline')) {
d['style-src-elem'] = [...style_src_elem, 'unsafe-inline'];
d['style-src-elem'] = [
...style_src_elem.filter(
(value) => !(value.startsWith('sha256-') || value.startsWith('nonce-'))
),
'unsafe-inline'
];
}
}

Expand Down Expand Up @@ -152,6 +168,11 @@ class BaseProvider {
/** @param {string} content */
add_style(content) {
if (this.#style_needs_csp) {
// this is the hash for "/* empty */"
// adding it so that svelte does not break csp
// see https://github.com/sveltejs/svelte/pull/7800
const empty_comment_hash = '9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc=';

const d = this.#directives;

if (this.#use_hashes) {
Expand All @@ -163,6 +184,13 @@ class BaseProvider {
this.#style_src_attr.push(`sha256-${hash}`);
}
if (d['style-src-elem']?.length) {
if (
hash !== empty_comment_hash &&
!d['style-src-elem'].includes(`sha256-${empty_comment_hash}`)
) {
this.#style_src_elem.push(`sha256-${empty_comment_hash}`);
}

this.#style_src_elem.push(`sha256-${hash}`);
}
} else {
Expand All @@ -173,6 +201,10 @@ class BaseProvider {
this.#style_src_attr.push(`nonce-${this.#nonce}`);
}
if (d['style-src-elem']?.length) {
if (!d['style-src-elem'].includes(`sha256-${empty_comment_hash}`)) {
this.#style_src_elem.push(`sha256-${empty_comment_hash}`);
}

this.#style_src_elem.push(`nonce-${this.#nonce}`);
}
}
Expand Down
12 changes: 8 additions & 4 deletions packages/kit/src/runtime/server/page/csp.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ test('adds nonce to script-src-elem, style-src-attr and style-src-elem if necess
const csp_header = csp.csp_provider.get_header();
assert.ok(csp_header.includes("script-src-elem 'self' 'nonce-"));
assert.ok(csp_header.includes("style-src-attr 'self' 'nonce-"));
assert.ok(csp_header.includes("style-src-elem 'self' 'nonce-"));
assert.ok(
csp_header.includes(
"style-src-elem 'self' 'sha256-9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc=' 'nonce-"
)
);
});

test('adds hash to script-src-elem, style-src-attr and style-src-elem if necessary during prerendering', () => {
Expand Down Expand Up @@ -210,7 +214,7 @@ test('adds hash to script-src-elem, style-src-attr and style-src-elem if necessa
);
assert.ok(
csp_header.includes(
"style-src-elem 'self' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"
"style-src-elem 'self' 'sha256-9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc=' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"
)
);
});
Expand All @@ -224,8 +228,8 @@ test('adds unsafe-inline styles in dev', () => {
mode: 'hash',
directives: {
'default-src': ['self'],
'style-src-attr': ['self'],
'style-src-elem': ['self']
'style-src-attr': ['self', 'sha256-9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc='],
'style-src-elem': ['self', 'sha256-9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc=']
},
reportOnly: {
'default-src': ['self'],
Expand Down