security: tighten CSP — drop provider wildcards, add object/frame/worker-src (#45)
Browser code never calls OpenAI/Azure/Gemini directly; all AI traffic is server-side tRPC. connect-src is now locked to 'self'. Added object-src 'none', frame-src 'none', media-src 'self', and worker-src 'self' blob:. style-src keeps 'unsafe-inline' for React + @react-pdf/renderer (documented residual risk — script-src is nonce-based so CSS injection cannot escalate to JS). Added three regression tests covering connect-src no-wildcards, object/frame-src 'none', and worker-src scope. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,33 @@ describe("middleware — Content-Security-Policy", () => {
|
||||
expect(csp).toContain("frame-ancestors 'none'");
|
||||
}
|
||||
});
|
||||
|
||||
it("connect-src has no wildcards — browser cannot call external hosts directly", async () => {
|
||||
const middleware = await importMiddleware("production");
|
||||
const res = await middleware(new NextRequest("http://localhost:3100/"));
|
||||
const csp = res.headers.get("Content-Security-Policy") ?? "";
|
||||
const connectSrc = csp.split(";").find((d: string) => d.trim().startsWith("connect-src")) ?? "";
|
||||
expect(connectSrc).toMatch(/connect-src\s+'self'\s*$/);
|
||||
expect(connectSrc).not.toContain("*");
|
||||
expect(connectSrc).not.toContain("openai.com");
|
||||
expect(connectSrc).not.toContain("azure.com");
|
||||
expect(connectSrc).not.toContain("googleapis.com");
|
||||
});
|
||||
|
||||
it("object-src, frame-src are 'none' to block legacy plugin and iframe vectors", async () => {
|
||||
const middleware = await importMiddleware("production");
|
||||
const res = await middleware(new NextRequest("http://localhost:3100/"));
|
||||
const csp = res.headers.get("Content-Security-Policy") ?? "";
|
||||
expect(csp).toContain("object-src 'none'");
|
||||
expect(csp).toContain("frame-src 'none'");
|
||||
});
|
||||
|
||||
it("worker-src restricts web workers to same-origin and blob: (for Next.js)", async () => {
|
||||
const middleware = await importMiddleware("production");
|
||||
const res = await middleware(new NextRequest("http://localhost:3100/"));
|
||||
const csp = res.headers.get("Content-Security-Policy") ?? "";
|
||||
expect(csp).toContain("worker-src 'self' blob:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("middleware — API allowlist (default-deny)", () => {
|
||||
|
||||
Reference in New Issue
Block a user