nextjs-proxy-csp-hardening
Skill1 starsChanged 10 days agoMITView on GitHub
Build and review secure Next.js proxy.ts files that apply Content Security Policy, nonce-based script protection, and browser security headers. Use when adding CSP to a Next.js App Router project, migrating from middleware.ts to proxy.ts, or hardening an application against XSS, clickjacking, script injection, form hijacking, and unsafe browser capabilities.
- Reads credentials
In this repository29 other files it gives its agents.
- AGENTS.mdAGENTS.md
- vercel-react-best-practices/AGENTS.mdAGENTS.md
- application-lifecycle-traceSkill
- architecture-reviewSkill
- awsSkill
- codebase-mapperSkill
- code-review-frontendSkill
- comment-sickoSkill
- cspSkill
- demo-scriptSkill
- documentation-writerSkill
- dotnetSkill
- feature-orchestratorSkill
- frontend-patternsSkill
- lets-talk-about-itSkill
- owasp-securitySkill
- pr-body-styleSkill
- prd-modeSkill
- prove-it-worksSkill
- security-reviewSkill
- simplifySkill
- sweSkill
- systems-architectureSkill
- task-generation-modeSkill
- thermo-nuclear-code-quality-reviewSkill
- typescript-react-reviewerSkill
- typescript-strict-styleSkill
- vercel-react-best-practicesSkill
- whats-going-onSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
30 rules. About 0 are common, 23 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈23
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
Add a strict Content Security Policy.
- Distinctive: line 27
Add browser security headers.
- Distinctive: line 28
Avoid common CSP mistakes.
- Distinctive: line 29
Keep development mode usable without weakening production.
- Distinctive: line 37
Use this skill when the user asks for:
- Distinctive: line 51
Do not use this skill for:
- Distinctive: line 95
Only allow scripts from trusted sources.
- Very specific: line 96
Prefer nonce-based scripts over unsafe-inline.
- Very specific: line 97
Avoid unsafe-eval in production.
- Distinctive: line 103
Avoid leaking referrer data.
- Distinctive: line 135
Use this when the user wants a complete secure starter file.
- Very specific: line 255
When using next/script, read the nonce from headers.
- Distinctive: line 274
Only use this pattern for scripts that actually need to run on the page.
- Distinctive: line 280
Start strict. Loosen only when something breaks and there is a real reason.
- Distinctive: line 297
Only add the services your app actually uses.
- Very specific: line 431
Avoid this in proxy.ts:
- Distinctive: line 438
Use Web Crypto instead:
- Very specific: line 449
When reviewing a proxy.ts file, verify:
- Distinctive: line 475
If the app breaks after adding CSP:
- Distinctive: line 479
Look for CSP violation messages.
- Distinctive: line 481
Add the narrowest possible allowlist entry.
- Distinctive: line 482
Avoid adding global exceptions.
- Distinctive: line 485
Use this command to test production behavior locally:
- Very specific: line 492
Do not rely only on npm run dev, because development mode may require looser CSP rules.
- Very specific: line 498
When the user asks for a secure Next.js CSP, provide:
- Distinctive: line 516
Do not suggest these as defaults:
- Distinctive: line 527
Do not remove these unless the user has a specific, legitimate need:
- Distinctive: line 540
When asked for the full file, return this structure:
- Distinctive: line 548
When asked for a reusable skill, return:
- Distinctive: line 560
If the user does not provide app-specific requirements, use this default policy:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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.tsUse 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="/scripts/third-party.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' PAYMENTS_SCRIPT_HOST`,
`frame-src 'self' PAYMENTS_SCRIPT_HOST PAYMENTS_HOOKS_HOST`,
`connect-src 'self' PAYMENTS_API_HOST`,Google Analytics
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' TAG_MANAGER_HOST`,
`connect-src 'self' ANALYTICS_HOST ANALYTICS_REGION_HOST`,
`img-src 'self' blob: data: https: ANALYTICS_HOST`,PostHog
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' PRODUCT_ANALYTICS_HOST`,
`connect-src 'self' PRODUCT_ANALYTICS_HOST PRODUCT_ANALYTICS_US_HOST PRODUCT_ANALYTICS_EU_HOST`,
`img-src 'self' blob: data: https:`,Sentry
`connect-src 'self' ERROR_TRACKING_HOST`,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 startDo 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-requestsIn 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.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

