agentsclimarketplace

Nextjs security scan

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

Defensive security skills for Claude Code and the Claude Agent SDK — web applications and generative AI systems.

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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

5.8 KB, 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

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.