Yeah, you heard me right: SvelteKit. The past few weeks I have found myself increasingly developing full-stack applications using SvelteKit. I have my reservations about the framework but that is not what this blog post is about.

In line with the whole shift-left philosophy, I wanted to make it easy to add security headers across my whole application. In similar fashion to how Helmet plugs into Express apps, I found that SvelteKit could do with something similar using a handle hook.

The way to configure headers across all response objects is to create a src/hooks.server.ts. The response consists of resolving the event which then allows setting response headers.

import type { Handle } from "@sveltejs/kit";

const securityHeaders = {
    'Cross-Origin-Embedder-Policy': 'require-corp',
    'Cross-Origin-Opener-Policy': 'same-origin',
    // [...]
    'X-XSS-Protection': '0',
}

export const handle: Handle = async ({ event, resolve }) => {
    const response = await resolve(event);
    Object.entries(securityHeaders).forEach(
        ([header, value]) => response.headers.set(header, value)
    );

    return response;
}

Now, with all the Helmet defaults in place, the response headers can be seen in the HTTP response.

❯ curl -I http://localhost:5173/
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
content-length: 879
content-security-policy: script-src 'self' 'nonce-Y70QFNhAVmer2wdobT8YoQ=='
content-type: text/html
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
etag: "1maoxiy"
origin-agent-cluster: ?1
referrer-policy: no-referrer
strict-transport-security: max-age=15552000; includeSubDomains
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
x-sveltekit-page: true
x-xss-protection: 0
Date: Wed, 25 Jan 2023 11:58:03 GMT
Connection: keep-alive
Keep-Alive: timeout=5

A quick scan using shcheck to verify if this solution works.

❯ python shcheck.py http://localhost:5173/ | grep -i "Missing"
[!] Missing security header: Permissions-Policy

Oh, come on!