agentleFS
ChristopherAlphonseChristopherAlphonse/calphonse-skills/nextjs-proxy-csp-hardening/SKILL.md

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

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
  1. Distinctive:

    Add a strict Content Security Policy.

    line 23
  2. Distinctive:

    Add browser security headers.

    line 27
  3. Distinctive:

    Avoid common CSP mistakes.

    line 28
  4. Distinctive:

    Keep development mode usable without weakening production.

    line 29
  5. Distinctive:

    Use this skill when the user asks for:

    line 37
  6. Distinctive:

    Do not use this skill for:

    line 51
  7. Distinctive:

    Only allow scripts from trusted sources.

    line 95
  8. Very specific:

    Prefer nonce-based scripts over unsafe-inline.

    line 96
  9. Very specific:

    Avoid unsafe-eval in production.

    line 97
  10. Distinctive:

    Avoid leaking referrer data.

    line 103
  11. Distinctive:

    Use this when the user wants a complete secure starter file.

    line 135
  12. Very specific:

    When using next/script, read the nonce from headers.

    line 255
  13. Distinctive:

    Only use this pattern for scripts that actually need to run on the page.

    line 274
  14. Distinctive:

    Start strict. Loosen only when something breaks and there is a real reason.

    line 280
  15. Distinctive:

    Only add the services your app actually uses.

    line 297
  16. Very specific:

    Avoid this in proxy.ts:

    line 431
  17. Distinctive:

    Use Web Crypto instead:

    line 438
  18. Very specific:

    When reviewing a proxy.ts file, verify:

    line 449
  19. Distinctive:

    If the app breaks after adding CSP:

    line 475
  20. Distinctive:

    Look for CSP violation messages.

    line 479
  21. Distinctive:

    Add the narrowest possible allowlist entry.

    line 481
  22. Distinctive:

    Avoid adding global exceptions.

    line 482
  23. Distinctive:

    Use this command to test production behavior locally:

    line 485
  24. Very specific:

    Do not rely only on npm run dev, because development mode may require looser CSP rules.

    line 492
  25. Very specific:

    When the user asks for a secure Next.js CSP, provide:

    line 498
  26. Distinctive:

    Do not suggest these as defaults:

    line 516
  27. Distinctive:

    Do not remove these unless the user has a specific, legitimate need:

    line 527
  28. Distinctive:

    When asked for the full file, return this structure:

    line 540
  29. Distinctive:

    When asked for a reusable skill, return:

    line 548
  30. Distinctive:

    If the user does not provide app-specific requirements, use this default policy:

    line 560

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 from middleware.ts to proxy.ts in 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, and frame-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.ts file.
  • A secure CSP for Next.js.
  • A nonce-based CSP.
  • Help migrating middleware.ts to proxy.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:

  1. Only allow scripts from trusted sources.
  2. Prefer nonce-based scripts over unsafe-inline.
  3. Avoid unsafe-eval in production.
  4. Block plugins and embedded legacy content.
  5. Restrict the <base> element.
  6. Restrict form submission targets.
  7. Prevent clickjacking.
  8. Limit browser APIs through Permissions-Policy.
  9. Avoid leaking referrer data.
  10. Prevent MIME sniffing.

4. CSP DIRECTIVES TO ALWAYS CONSIDER

These directives are especially important.

DirectivePurposeRecommended Default
default-srcFallback for many fetch directives'self'
script-srcControls JavaScript execution'self' 'nonce-{nonce}' 'strict-dynamic'
style-srcControls CSS'self' 'nonce-{nonce}'
img-srcControls image loading'self' blob: data: https:
font-srcControls fonts'self' data:
connect-srcControls fetch, XHR, WebSocket'self'
media-srcControls audio and video'self'
frame-srcControls frames created by the app'self'
object-srcControls <object>, <embed>, and <applet>'none'
base-uriControls the <base> element'self'
form-actionControls where forms can submit'self'
frame-ancestorsControls 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:

ProblemSafer FixAvoid
Third-party script blockedAdd the exact script domain if requiredAdding *
Inline script blockedUse a nonceAdding unsafe-inline
Dev mode breaksAllow unsafe-eval only in developmentAllowing unsafe-eval in production
CSS-in-JS breaksTry nonce support firstPermanent unsafe-inline
External API blockedAdd exact API origin to connect-srcBroad https: in connect-src
App must be embeddedSet exact allowed parent in frame-ancestorsRemoving 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, not middleware.
  • It uses NextResponse.next().
  • It generates a fresh nonce per request.
  • It uses Web Crypto, not Node crypto.
  • It sets x-nonce on request headers.
  • It sets Content-Security-Policy on the response.
  • It avoids unsafe-inline in production.
  • It avoids unsafe-eval in 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:

  1. Open Chrome DevTools.
  2. Go to the Console tab.
  3. Look for CSP violation messages.
  4. Identify the blocked directive.
  5. Add the narrowest possible allowlist entry.
  6. Avoid adding global exceptions.
  7. 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:

  1. A full proxy.ts.
  2. A short explanation of what it protects.
  3. A warning about third-party services.
  4. A note that unsafe-eval should be development-only.
  5. 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-inline without explanation.
  • Adds unsafe-eval in 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.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.