Skip to content

Commit 4f19e09

Browse files
committed
refactor(docs): improve csp
1 parent 23defb5 commit 4f19e09

File tree

3 files changed

+68
-49
lines changed

3 files changed

+68
-49
lines changed

apps/docs/src/app/layout.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import { Sidebar } from "@/components/Sidebar";
99

1010
import "@/styles/globals.css";
1111

12-
import Script from "next/script";
13-
1412
import Fathom from "@/components/Fathom";
1513
import { Header } from "@/components/header/header";
1614

apps/docs/src/middleware.ts

Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,79 @@
11
import { NextResponse, type NextRequest } from "next/server";
22

3+
// file: middleware.ts
34
export function middleware(request: NextRequest) {
5+
const requestHeaders = new Headers(request.headers);
6+
const response = initResponse();
47
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
8+
const reportUri =
9+
"https://o4505075539902464.ingest.us.sentry.io/api/4505075559825408/security/?sentry_key=e137a5ec37cf03e1ed168b772c98c0bc";
510

6-
const cspHeader = `
7-
default-src 'self';
8-
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
9-
style-src 'self' 'nonce-${nonce}';
10-
img-src 'self' blob: data:;
11-
font-src 'self';
12-
object-src 'none';
13-
base-uri 'self';
14-
form-action 'self';
15-
frame-ancestors 'none';
16-
upgrade-insecure-requests;
17-
`;
18-
19-
// Replace newline characters and spaces
20-
const contentSecurityPolicyHeaderValue = cspHeader.replace(/\s{2,}/g, " ").trim();
11+
response.headers.set(
12+
"Content-Security-Policy",
13+
getContentSecurityPolicyHeaderValue(nonce, reportUri)
14+
);
15+
response.headers.set("Report-To", getReportToHeaderValue(reportUri));
2116

22-
const requestHeaders = new Headers(request.headers);
17+
request.headers.set(
18+
"Content-Security-Policy",
19+
getContentSecurityPolicyHeaderValue(nonce, reportUri)
20+
);
2321
requestHeaders.set("x-nonce", nonce);
2422

25-
if (process.env.NODE_ENV === "production") {
26-
requestHeaders.set("Content-Security-Policy", contentSecurityPolicyHeaderValue);
27-
}
23+
return response;
24+
}
2825

29-
const response = NextResponse.next({
30-
request: {
31-
headers: requestHeaders,
32-
},
33-
});
26+
function initResponse(): NextResponse {
27+
return NextResponse.next();
28+
}
3429

35-
if (process.env.NODE_ENV === "production") {
36-
response.headers.set("Content-Security-Policy", contentSecurityPolicyHeaderValue);
37-
}
30+
function getReportToHeaderValue(reportUri: string): string {
31+
const reportTo = {
32+
group: "csp",
33+
max_age: 10886400, // 1 day
34+
endpoints: [{ url: reportUri }],
35+
};
3836

39-
return response;
37+
return JSON.stringify(reportTo);
4038
}
4139

42-
export const config = {
43-
matcher: [
44-
/*
45-
* Match all request paths except for the ones starting with:
46-
* - api (API routes)
47-
* - _next/static (static files)
48-
* - _next/image (image optimization files)
49-
* - favicon.ico (favicon file)
50-
*/
51-
{
52-
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
53-
missing: [
54-
{ type: "header", key: "next-router-prefetch" },
55-
{ type: "header", key: "purpose", value: "prefetch" },
56-
],
57-
},
58-
],
59-
};
40+
function getContentSecurityPolicyHeaderValue(nonce: string, reportUri: string): string {
41+
// Default CSP for Next.js
42+
const contentSecurityPolicyDirective = {
43+
"base-uri": [`'self'`],
44+
"default-src": [`'none'`],
45+
"frame-ancestors": [`'none'`],
46+
"font-src": [`'self'`],
47+
"form-action": [`'self'`],
48+
"frame-src": [`'self'`],
49+
"connect-src": [`'self'`],
50+
"img-src": [`'self'`, "cdn.usefathom.com"],
51+
"manifest-src": [`'self'`],
52+
"object-src": [`'none'`],
53+
"report-uri": [reportUri], // for old browsers like Firefox
54+
"report-to": ["csp"], // for modern browsers like Chrome
55+
"script-src": [
56+
`'nonce-${nonce}'`,
57+
`'strict-dynamic'`, // force hashes and nonces over domain host lists
58+
],
59+
"style-src": [`'self'`, `'unsafe-inline'`],
60+
};
61+
62+
if (process.env.NODE_ENV === "development") {
63+
// Webpack use eval() in development mode for automatic JS reloading
64+
contentSecurityPolicyDirective["script-src"].push(`'unsafe-eval'`);
65+
}
66+
67+
if (process.env.NEXT_PUBLIC_VERCEL_ENV === "preview") {
68+
contentSecurityPolicyDirective["connect-src"].push("https://vercel.live");
69+
contentSecurityPolicyDirective["connect-src"].push("wss://*.pusher.com");
70+
contentSecurityPolicyDirective["img-src"].push("https://vercel.com");
71+
contentSecurityPolicyDirective["font-src"].push("https://vercel.live");
72+
contentSecurityPolicyDirective["frame-src"].push("https://vercel.live");
73+
contentSecurityPolicyDirective["style-src"].push("https://vercel.live");
74+
}
75+
76+
return Object.entries(contentSecurityPolicyDirective)
77+
.map(([key, value]) => `${key} ${value.join(" ")}`)
78+
.join("; ");
79+
}

turbo.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"WEBFLOW_API_KEY",
3333
"WEBFLOW_BLOG_ID",
3434
"WEBFLOW_CASE_STUDIES_ID",
35-
"NODE_ENV"
35+
"NODE_ENV",
36+
"NEXT_PUBLIC_VERCEL_ENV"
3637
]
3738
},
3839
"clean": {

0 commit comments

Comments
 (0)