Skip to content
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: 4 additions & 1 deletion svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ const config = {
adapter: adapter({ out: 'build' }),
csp: {
mode: 'auto',
reportOnly: {
// Enforced CSP (previously report-only while issue #60 collected violations).
// `report-uri` is retained so browsers still POST blocked-violation reports
// to /api/csp-report even though the policy is now enforced.
directives: {
'default-src': ['self'],
'script-src': ['self', inlineScriptHash],
'style-src': ['self', 'unsafe-inline'],
Expand Down
18 changes: 9 additions & 9 deletions tests/unit/csp-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ describe('SvelteKit CSP configuration', () => {
expect(csp).toBeDefined();
});

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

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

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

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

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

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

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