agentsclimarketplace

Nextjs proxy csp hardening

Skill ChristopherAlphonse/calphonse-skills/nextjs-proxy-csp-hardening

Curated skills and configurations for AI-assisted development

Install
npx -y skills add ChristopherAlphonse/calphonse-skills --skill nextjs-proxy-csp-hardening

Assembled 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 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="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:

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' 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, 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.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.