Skip to content

Commit 0231588

Browse files
fix: enforce Content-Security-Policy instead of report-only mode (#126)
Rename the `reportOnly` key to `directives` in the SvelteKit `csp` config so browsers enforce the Content-Security-Policy rather than only reporting violations. `report-uri` is retained inside the enforced directives so blocked-violation reports still reach /api/csp-report. Update tests/unit/csp-config.test.ts to assert the enforced (`directives`) mode. Refs #124 Co-authored-by: dobby-yivi-agent[bot] <275734547+dobby-yivi-agent[bot]@users.noreply.github.com>
1 parent 38b7260 commit 0231588

2 files changed

Lines changed: 13 additions & 10 deletions

File tree

svelte.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ const config = {
2727
adapter: adapter({ out: 'build' }),
2828
csp: {
2929
mode: 'auto',
30-
reportOnly: {
30+
// Enforced CSP (previously report-only while issue #60 collected violations).
31+
// `report-uri` is retained so browsers still POST blocked-violation reports
32+
// to /api/csp-report even though the policy is now enforced.
33+
directives: {
3134
'default-src': ['self'],
3235
'script-src': ['self', inlineScriptHash],
3336
'style-src': ['self', 'unsafe-inline'],

tests/unit/csp-config.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ describe('SvelteKit CSP configuration', () => {
1010
expect(csp).toBeDefined();
1111
});
1212

13-
it('uses Report-Only mode for the initial rollout', () => {
14-
expect(csp?.reportOnly).toBeDefined();
15-
expect(csp?.directives).toBeUndefined();
13+
it('enforces the policy (directives), not report-only mode', () => {
14+
expect(csp?.directives).toBeDefined();
15+
expect(csp?.reportOnly).toBeUndefined();
1616
});
1717

1818
it('uses auto mode so SvelteKit picks nonce vs hash per route', () => {
1919
expect(csp?.mode).toBe('auto');
2020
});
2121

2222
it('locks down framing, base-uri, form-action and object-src', () => {
23-
const r = csp!.reportOnly!;
23+
const r = csp!.directives!;
2424
expect(r['frame-ancestors']).toEqual(['none']);
2525
expect(r['base-uri']).toEqual(['self']);
2626
expect(r['form-action']).toEqual(['self']);
2727
expect(r['object-src']).toEqual(['none']);
2828
});
2929

3030
it('keeps script-src restricted to same-origin (no unsafe-inline / unsafe-eval)', () => {
31-
const scriptSrc = csp!.reportOnly!['script-src'] ?? [];
31+
const scriptSrc = csp!.directives!['script-src'] ?? [];
3232
expect(scriptSrc).toContain('self');
3333
expect(scriptSrc).not.toContain('unsafe-inline');
3434
expect(scriptSrc).not.toContain('unsafe-eval');
@@ -39,18 +39,18 @@ describe('SvelteKit CSP configuration', () => {
3939
const inlineScriptBody = appHtml.match(/<script>([\s\S]*?)<\/script>/)?.[1];
4040
expect(inlineScriptBody, 'src/app.html should contain an inline <script>').toBeDefined();
4141
const expected = `sha256-${createHash('sha256').update(inlineScriptBody!).digest('base64')}`;
42-
expect(csp!.reportOnly!['script-src']).toContain(expected);
42+
expect(csp!.directives!['script-src']).toContain(expected);
4343
});
4444

4545
it('allows data: images for inline icon SVGs', () => {
46-
expect(csp!.reportOnly!['img-src']).toContain('data:');
46+
expect(csp!.directives!['img-src']).toContain('data:');
4747
});
4848

4949
it('allows the Iconify API in connect-src for @iconify/svelte icon loading', () => {
50-
expect(csp!.reportOnly!['connect-src']).toContain('https://api.iconify.design');
50+
expect(csp!.directives!['connect-src']).toContain('https://api.iconify.design');
5151
});
5252

5353
it('points violations at the in-app CSP report sink', () => {
54-
expect(csp!.reportOnly!['report-uri']).toEqual(['/api/csp-report']);
54+
expect(csp!.directives!['report-uri']).toEqual(['/api/csp-report']);
5555
});
5656
});

0 commit comments

Comments
 (0)