agentsclimarketplace

Nextjs security scan

Skill Dolphinllc/claude-security-skills/skills/defensive/web/nextjs-security-scan

Defensive security scan for Next.js App Router projects. Detects environment-variable leakage to the browser bundle, unauthenticated server actions, missing middleware auth, unsafe CORS on route handlers, and trust-boundary violations in revalidatePath/revalidateTag. Invoke when the user asks to "review", "audit", or "scan" a Next.js codebase, or when editing files under app/ or middleware.ts.From its SKILL.md

Install
npx -y skills add Dolphinllc/claude-security-skills --skill nextjs-security-scan

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

5.8 KB, ~1.4k tokens by cl100k_base, as published. Nobody here has run it

Next.js Security Scan

Performs a defensive security review of a Next.js App Router project (Next.js 13+). Reports findings using the shared scoring schema.

Scope

Detects only — does not modify code. Targets:

  • app/** — Server Components, Client Components, server actions, route handlers
  • middleware.ts / middleware.js
  • next.config.{js,ts,mjs}
  • .env*

Out of scope: dependency CVEs (use npm audit / Snyk), runtime DAST.

Procedure

  1. Enumerate target files via Glob/Grep. Read each one once with Read.
  2. Apply each rule in the table below. For every match, record a finding with severity, location, evidence, and fix.
  3. Emit the final report in the schema from SCORING.md.

Rules

IDSeverityDetectionFix
NEXTJS-ENV-001criticalprocess.env.NEXT_PUBLIC_* referencing a secret-shaped name (*_KEY, *_SECRET, *_TOKEN, *_PASSWORD, *_DSN)Remove NEXT_PUBLIC_ prefix; access only in Server Components / route handlers
NEXTJS-ENV-002highSecret-shaped env var read inside a file containing "use client"Move read to a Server Component or route handler; pass derived non-secret value via props
NEXTJS-SA-001highExported async function in a "use server" file with no auth check (no call to auth(), getServerSession, cookies()-based check, or equivalent) before mutationAdd session check at the top of the action; return early on unauthenticated
NEXTJS-SA-002highServer action accepts unvalidated FormData/object and passes fields directly to DB / fetchValidate with zod/valibot schema before use
NEXTJS-MW-001highmiddleware.ts has a matcher that excludes auth-sensitive routes (e.g., /api/admin, /dashboard) but no per-route check existsTighten matcher or add per-route guards
NEXTJS-MW-002mediumMiddleware reads JWT but does not verify signature (jwt.decode instead of jwt.verify / jose.jwtVerify)Use jose.jwtVerify with explicit issuer/audience
NEXTJS-RH-001highroute.ts returns response with Access-Control-Allow-Origin: * together with Access-Control-Allow-Credentials: truePin origin to an allowlist; never combine wildcard with credentials
NEXTJS-RH-002mediumroute.ts POST/PUT/DELETE handler reads request.json() without schema validation before persistingValidate with zod before use
NEXTJS-RH-003mediumroute.ts does not set runtime and uses Node-only crypto/secrets (risk of accidental Edge migration losing protections)Add export const runtime = 'nodejs' explicitly
NEXTJS-REVAL-001mediumrevalidatePath / revalidateTag called with a path/tag built from request inputWhitelist allowed paths; never interpolate user input
NEXTJS-IMG-001mediumnext.config images.remotePatterns uses ** host or omits pathnamePin host and path prefix
NEXTJS-CSP-001mediumNo Content-Security-Policy set in middleware.ts / next.config headersAdd CSP with nonce-based script-src
NEXTJS-DSI-001highdangerouslySetInnerHTML with value not provably static (template literal, prop, state, fetched data)Render as text, or sanitize with DOMPurify (server-side)
NEXTJS-LOG-001mediumconsole.log of request.headers, cookies(), session, or full request.bodyRedact before logging; log IDs not payloads

Wrong vs. right

NEXTJS-ENV-001 (env leak)

// ❌ Exposes the API key to every browser bundle
const key = process.env.NEXT_PUBLIC_ANTHROPIC_API_KEY;
// ✅ Server-only access
// app/api/chat/route.ts
export async function POST(req: Request) {
  const key = process.env.ANTHROPIC_API_KEY;
  // ...
}

NEXTJS-SA-001 (unauth server action)

// ❌ Anyone who can hit the form can call this
"use server";
export async function deleteUser(id: string) {
  await db.user.delete({ where: { id } });
}
// ✅ Session-checked, schema-validated
"use server";
import { z } from "zod";
import { auth } from "@/auth";

const schema = z.object({ id: z.string().uuid() });

export async function deleteUser(input: unknown) {
  const session = await auth();
  if (!session?.user || session.user.role !== "admin") {
    throw new Error("Unauthorized");
  }
  const { id } = schema.parse(input);
  await db.user.delete({ where: { id } });
}

NEXTJS-RH-001 (CORS + credentials)

// ❌ Browsers will reject this in modern versions, but older clients won't
return new Response(data, {
  headers: {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Credentials": "true",
  },
});
// ✅ Origin allowlist
const ALLOWED = new Set(["https://app.example.com"]);
const origin = req.headers.get("origin") ?? "";
const allow = ALLOWED.has(origin) ? origin : "";
return new Response(data, {
  headers: {
    "Access-Control-Allow-Origin": allow,
    "Access-Control-Allow-Credentials": "true",
    "Vary": "Origin",
  },
});

References

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. 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.