Add security-headers middleware with strict CSP (TDD)
Exports buildSecurityHeaders() (pure, testable) and wires it into the Astro onRequest middleware. Adds astro:middleware alias in vitest config so the unit tests run outside Astro's build context. 14 tests pass (7 existing + 7 new). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
51
src/middleware.ts
Normal file
51
src/middleware.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { defineMiddleware } from 'astro:middleware';
|
||||
|
||||
/**
|
||||
* Emits the site-wide security header set. Astro does not attach these to
|
||||
* statically-built responses at runtime on a shared host — but `preview` uses
|
||||
* them locally, and when Cloudflare Transform Rules are configured (per
|
||||
* docs/superpowers/specs/2026-04-24-cameleer-website-design.md §5.3) the
|
||||
* edge re-emits the same set for the prod origin. Having both is defense
|
||||
* in depth.
|
||||
*/
|
||||
export function buildSecurityHeaders(): Record<string, string> {
|
||||
const csp = [
|
||||
"default-src 'self'",
|
||||
"img-src 'self' data:",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
"script-src 'self'",
|
||||
"connect-src 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'none'",
|
||||
"object-src 'none'",
|
||||
].join('; ');
|
||||
|
||||
const permissionsPolicy = [
|
||||
'geolocation=()',
|
||||
'microphone=()',
|
||||
'camera=()',
|
||||
'payment=()',
|
||||
'usb=()',
|
||||
'fullscreen=(self)',
|
||||
].join(', ');
|
||||
|
||||
return {
|
||||
'Content-Security-Policy': csp,
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||
'Permissions-Policy': permissionsPolicy,
|
||||
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
|
||||
};
|
||||
}
|
||||
|
||||
export const onRequest = defineMiddleware(async (_context, next) => {
|
||||
const response = await next();
|
||||
const headers = buildSecurityHeaders();
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
response.headers.set(name, value);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
Reference in New Issue
Block a user