Nextjs proxy csp hardening
Skill ChristopherAlphonse/calphonse-skills/nextjs-proxy-csp-hardening
Curated skills and configurations for AI-assisted development
npx -y skills add ChristopherAlphonse/calphonse-skills --skill nextjs-proxy-csp-hardeningAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
15.3 KB, as published. Nobody here has run it
SKILL: Next.js Proxy CSP Hardening
AI LOAD INSTRUCTION: Use this skill when the user asks for a secure
proxy.ts, CSP headers, nonce-based script handling, security headers, or migration frommiddleware.tstoproxy.tsin a Next.js application. Prioritize defensive configuration. Do not weaken CSP unless the user clearly identifies a real framework or third-party integration requirement. Always include the no-fallback CSP directives:base-uri,object-src,form-action, andframe-ancestors.
0. PURPOSE
This skill helps create a secure proxy.ts file for a Next.js application.
The goal is to:
- Add a strict Content Security Policy.
- Generate a unique nonce per request.
- Pass the nonce into the application through request headers.
- Apply CSP to the browser response.
- Add browser security headers.
- Avoid common CSP mistakes.
- Keep development mode usable without weakening production.
This skill is defensive. It is for hardening a Next.js app, not for bypassing CSP.
1. WHEN TO USE THIS SKILL
Use this skill when the user asks for:
- A Next.js
proxy.tsfile. - A secure CSP for Next.js.
- A nonce-based CSP.
- Help migrating
middleware.tstoproxy.ts. - Security headers for a Next.js app.
- Protection against XSS.
- Protection against clickjacking.
- Protection against unsafe forms.
- Protection against untrusted embedded content.
- A safer replacement for
unsafe-inline. - A production-ready security header setup.
Do not use this skill for:
- Writing exploit payloads.
- Bypassing a third-party CSP.
- Exfiltrating data.
- Testing someone else's application without authorization.
- Weakening application security for convenience.
2. KEY NEXT.JS FILE CONVENTION
Next.js uses proxy.ts for request interception.
The file should usually live in one of these locations:
proxy.ts
src/proxy.ts
Use this shape:
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
export function proxy(request: NextRequest) {
return NextResponse.next();
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
3. SECURITY GOALS
The proxy.ts file should protect the app by default.
The baseline goals are:
- Only allow scripts from trusted sources.
- Prefer nonce-based scripts over
unsafe-inline. - Avoid
unsafe-evalin production. - Block plugins and embedded legacy content.
- Restrict the
<base>element. - Restrict form submission targets.
- Prevent clickjacking.
- Limit browser APIs through
Permissions-Policy. - Avoid leaking referrer data.
- Prevent MIME sniffing.
4. CSP DIRECTIVES TO ALWAYS CONSIDER
These directives are especially important.
| Directive | Purpose | Recommended Default |
|---|---|---|
default-src | Fallback for many fetch directives | 'self' |
script-src | Controls JavaScript execution | 'self' 'nonce-{nonce}' 'strict-dynamic' |
style-src | Controls CSS | 'self' 'nonce-{nonce}' |
img-src | Controls image loading | 'self' blob: data: https: |
font-src | Controls fonts | 'self' data: |
connect-src | Controls fetch, XHR, WebSocket | 'self' |
media-src | Controls audio and video | 'self' |
frame-src | Controls frames created by the app | 'self' |
object-src | Controls <object>, <embed>, and <applet> | 'none' |
base-uri | Controls the <base> element | 'self' |
form-action | Controls where forms can submit | 'self' |
frame-ancestors | Controls who can frame the page | 'none' |
Important rule:
base-uri, form-action, and frame-ancestors do not safely fall back to default-src. Always set them explicitly.
5. DEFAULT RECOMMENDED PROXY.TS
Use this when the user wants a complete secure starter file.
// proxy.ts
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
const isDevelopment = process.env.NODE_ENV !== "production";
function createNonce(): string {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
return btoa(String.fromCharCode(...bytes));
}
function buildContentSecurityPolicy(nonce: string): string {
const directives = [
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${
isDevelopment ? "'unsafe-eval'" : ""
}`,
`style-src 'self' 'nonce-${nonce}'`,
`img-src 'self' blob: data: https:`,
`font-src 'self' data:`,
`connect-src 'self' ${isDevelopment ? "ws: wss:" : ""}`,
`media-src 'self'`,
`frame-src 'self'`,
`object-src 'none'`,
`base-uri 'self'`,
`form-action 'self'`,
`frame-ancestors 'none'`,
`upgrade-insecure-requests`,
];
return directives
.join("; ")
.replace(/\s{2,}/g, " ")
.trim();
}
function applySecurityHeaders(response: NextResponse): NextResponse {
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set(
"Permissions-Policy",
[
"camera=()",
"microphone=()",
"geolocation=()",
"payment=()",
"usb=()",
"magnetometer=()",
"gyroscope=()",
"accelerometer=()",
].join(", "),
);
return response;
}
export function proxy(request: NextRequest) {
const nonce = createNonce();
const contentSecurityPolicy = buildContentSecurityPolicy(nonce);
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", contentSecurityPolicy);
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set("Content-Security-Policy", contentSecurityPolicy);
return applySecurityHeaders(response);
}
export const config = {
matcher: [
{
source:
"/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
missing: [
{
type: "header",
key: "next-router-prefetch",
},
{
type: "header",
key: "purpose",
value: "prefetch",
},
],
},
],
};
6. USING THE NONCE IN SERVER COMPONENTS
When using next/script, read the nonce from headers.
import { headers } from "next/headers";
import Script from "next/script";
export default async function Page() {
const nonce = (await headers()).get("x-nonce") ?? undefined;
return (
<Script
src="https://example.com/script.js"
nonce={nonce}
strategy="afterInteractive"
/>
);
}
Only use this pattern for scripts that actually need to run on the page.
7. WHEN TO LOOSEN THE CSP
Start strict. Loosen only when something breaks and there is a real reason.
Common cases:
| Problem | Safer Fix | Avoid |
|---|---|---|
| Third-party script blocked | Add the exact script domain if required | Adding * |
| Inline script blocked | Use a nonce | Adding unsafe-inline |
| Dev mode breaks | Allow unsafe-eval only in development | Allowing unsafe-eval in production |
| CSS-in-JS breaks | Try nonce support first | Permanent unsafe-inline |
| External API blocked | Add exact API origin to connect-src | Broad https: in connect-src |
| App must be embedded | Set exact allowed parent in frame-ancestors | Removing frame-ancestors |
8. EXAMPLES FOR THIRD-PARTY SERVICES
Only add the services your app actually uses.
Stripe
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://js.stripe.com`,
`frame-src 'self' https://js.stripe.com https://hooks.stripe.com`,
`connect-src 'self' https://api.stripe.com`,
Google Analytics
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://www.googletagmanager.com`,
`connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com`,
`img-src 'self' blob: data: https: https://www.google-analytics.com`,
PostHog
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://app.posthog.com`,
`connect-src 'self' https://app.posthog.com https://us.i.posthog.com https://eu.i.posthog.com`,
`img-src 'self' blob: data: https:`,
Sentry
`connect-src 'self' https://*.ingest.sentry.io`,
9. PRODUCTION VS DEVELOPMENT
Development can be more permissive because Next.js tooling may need extra capabilities.
Development can allow:
'unsafe-eval'
ws:
wss:
Production should avoid:
unsafe-inline
unsafe-eval
*
data: in script-src
http:
Production should prefer:
nonce-based scripts
specific domains
object-src 'none'
base-uri 'self'
form-action 'self'
frame-ancestors 'none'
10. COMMON CSP MISTAKES TO AVOID
Mistake 1: Relying only on default-src
Bad:
default-src 'self'
Better:
default-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none'
Mistake 2: Using unsafe-inline in production
Bad:
script-src 'self' 'unsafe-inline'
Better:
script-src 'self' 'nonce-{nonce}' 'strict-dynamic'
Mistake 3: Allowing all HTTPS scripts
Bad:
script-src 'self' https:
Better:
script-src 'self' 'nonce-{nonce}' 'strict-dynamic'
Mistake 4: Forgetting frame-ancestors
Bad:
default-src 'self'
Better:
default-src 'self';
frame-ancestors 'none'
Mistake 5: Using Node APIs in proxy.ts
Avoid this in proxy.ts:
import crypto from "node:crypto";
Buffer.from("value");
Use Web Crypto instead:
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
11. REVIEW CHECKLIST
When reviewing a proxy.ts file, verify:
- It imports from
next/server. - It exports
proxy, notmiddleware. - It uses
NextResponse.next(). - It generates a fresh nonce per request.
- It uses Web Crypto, not Node crypto.
- It sets
x-nonceon request headers. - It sets
Content-Security-Policyon the response. - It avoids
unsafe-inlinein production. - It avoids
unsafe-evalin production. - It includes
object-src 'none'. - It includes
base-uri 'self'. - It includes
form-action 'self'. - It includes
frame-ancestors 'none'. - It sets
X-Content-Type-Options. - It sets
Referrer-Policy. - It sets
Permissions-Policy. - It excludes static assets in
config.matcher. - It does not accidentally block required API routes.
- It does not use broad wildcards.
12. DEBUGGING CSP ISSUES
If the app breaks after adding CSP:
- Open Chrome DevTools.
- Go to the Console tab.
- Look for CSP violation messages.
- Identify the blocked directive.
- Add the narrowest possible allowlist entry.
- Avoid adding global exceptions.
- Retest in production mode.
Use this command to test production behavior locally:
npm run build
npm run start
Do not rely only on npm run dev, because development mode may require looser CSP rules.
13. SAFE RESPONSE PATTERNS
When the user asks for a secure Next.js CSP, provide:
- A full
proxy.ts. - A short explanation of what it protects.
- A warning about third-party services.
- A note that
unsafe-evalshould be development-only. - A reminder to test in production mode.
Example response:
Here is a secure starter proxy.ts for Next.js. It uses a per-request nonce, applies CSP to the browser response, blocks object/embed content, restricts base-uri and form-action, and prevents clickjacking with frame-ancestors.
14. UNSAFE RESPONSE PATTERNS
Do not suggest these as defaults:
script-src *;
script-src 'self' 'unsafe-inline' 'unsafe-eval';
default-src * data: blob:;
frame-ancestors *;
object-src *;
form-action *;
Do not remove these unless the user has a specific, legitimate need:
object-src 'none'
base-uri 'self'
form-action 'self'
frame-ancestors 'none'
15. FINAL OUTPUT TEMPLATE
When asked for the full file, return this structure:
Here is the full proxy.ts:
Then provide the complete code.
When asked for a reusable skill, return:
Here is the full SKILL.md:
Then provide the complete markdown.
16. BEST DEFAULT
If the user does not provide app-specific requirements, use this default policy:
default-src 'self';
script-src 'self' 'nonce-{nonce}' 'strict-dynamic';
style-src 'self' 'nonce-{nonce}';
img-src 'self' blob: data: https:;
font-src 'self' data:;
connect-src 'self';
media-src 'self';
frame-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests
In development only, allow:
script-src 'self' 'nonce-{nonce}' 'strict-dynamic' 'unsafe-eval';
connect-src 'self' ws: wss:
17. QUALITY BAR
A good answer using this skill should be:
- Complete.
- Defensive.
- Practical.
- Copy-paste ready.
- Strict by default.
- Clear about tradeoffs.
- Honest about third-party integrations.
- Compatible with the Edge Runtime.
- Focused on production safety.
A bad answer using this skill:
- Adds
unsafe-inlinewithout explanation. - Adds
unsafe-evalin production. - Uses wildcard sources.
- Omits
base-uri. - Omits
object-src. - Omits
form-action. - Omits
frame-ancestors. - Uses Node-only APIs inside
proxy.ts. - Gives offensive CSP bypass steps instead of defensive hardening.