security-nextjs
Review Next.js security audit patterns for App Router and Server Actions. Use for auditing NEXT_PUBLIC_* exposure, Server Action auth, and middleware matchers. Use proactively when reviewing Next.js apps. Examples: - user: "Scan Next.js env vars" → find leaked secrets with NEXT_PUBLIC_ prefix - user: "Audit Server Actions" → check for missing auth and input validation - user: "Review Next.js middleware" → verify matcher coverage for protected routes - user: "Check Next.js API routes" → verify auth in app/api and pages/api - user: "Secure Next.js headers" → audit next.config.js for security headers
How do I install this agent skill?
npx skills add https://github.com/igorwarzocha/opencode-workflows --skill security-nextjsIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill is a security auditing tool designed to detect vulnerabilities in Next.js applications, such as secret exposure in environment variables and unauthenticated server actions. It includes a shell script to automate these checks. While useful, the skill involves executing local commands and reading sensitive project files, which creates a surface for indirect prompt injection from the code it analyzes.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
- Runlayerwarn
2/2 files flagged
- ZeroLeakspass
Score: 93/100 · 2 sections analyzed
What does this agent skill do?
Security audit patterns for Next.js applications covering environment variable exposure, Server Actions, middleware auth, API routes, and App Router security.
</overview> <rules>Environment Variable Exposure
The NEXT_PUBLIC_ Footgun
NEXT_PUBLIC_* → Bundled into client JavaScript → Visible to everyone
No prefix → Server-only → Safe for secrets
Audit steps:
grep -r "NEXT_PUBLIC_" . -g "*.env*"- For each var, ask: "Would I be OK if this was in view-source?"
- Common mistakes:
NEXT_PUBLIC_API_KEY(SHOULD be server-only)NEXT_PUBLIC_DATABASE_URL(MUST NOT use)NEXT_PUBLIC_STRIPE_SECRET_KEY(useSTRIPE_SECRET_KEY)
Safe pattern:
// Server-only (API route, Server Component, Server Action)
const apiKey = process.env.API_KEY; // ✓ No NEXT_PUBLIC_
// Client-safe (truly public)
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY; // ✓ Publishable
next.config.js env Is Always Bundled
Values set in next.config.js under env are inlined into the client bundle, even without NEXT_PUBLIC_. Treat them as public.
// ❌ Sensitive values here are exposed to the browser
module.exports = {
env: {
DATABASE_URL: process.env.DATABASE_URL,
},
};
</rules>
<vulnerabilities>
Server Actions Security
Missing Auth (Most Common Issue)
// ❌ VULNERABLE: No auth check
"use server"
export async function deleteUser(userId: string) {
await db.user.delete({ where: { id: userId } });
}
// ✓ SECURE: Auth + authorization
"use server"
export async function deleteUser(userId: string) {
const session = await getServerSession();
if (!session) throw new Error("Unauthorized");
if (session.user.id !== userId && !session.user.isAdmin) {
throw new Error("Forbidden");
}
await db.user.delete({ where: { id: userId } });
}
Input Validation
// ❌ Trusts client input
"use server"
export async function updateProfile(data: any) {
await db.user.update({ data });
}
// ✓ Validates with Zod
"use server"
import { z } from "zod";
const schema = z.object({ name: z.string().max(100), bio: z.string().max(500) });
export async function updateProfile(formData: FormData) {
const data = schema.parse(Object.fromEntries(formData));
await db.user.update({ data });
}
API Routes Security
App Router (app/api/*/route.ts)
// ❌ No auth
export async function GET(request: Request) {
return Response.json(await db.users.findMany());
}
// ✓ Auth middleware
import { getServerSession } from "next-auth";
export async function GET(request: Request) {
const session = await getServerSession();
if (!session) return new Response("Unauthorized", { status: 401 });
// ...
}
Pages Router (pages/api/*.ts)
// Check for missing auth on all handlers
// Common issue: GET is public but POST has auth (inconsistent)
Middleware Security
Auth in middleware.ts
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("session");
// ❌ Just checking existence
if (!token) return NextResponse.redirect("/login");
// ✓ SHOULD verify token
// But middleware can't do async DB calls easily!
// Solution: Use next-auth middleware or verify JWT
}
// CRITICAL: Check matcher covers all protected routes
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*", "/api/admin/:path*"],
};
Matcher Gaps
// ❌ Forgot API routes
matcher: ["/dashboard/:path*"]
// Admin API at /api/admin/* is unprotected!
// ✓ Include API routes
matcher: ["/dashboard/:path*", "/api/admin/:path*"]
Headers & Security Config
next.config.js
// Check for security headers
module.exports = {
async headers() {
return [
{
source: "/:path*",
headers: [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
// CSP is complex - check if present and not too permissive
],
},
];
},
};
</vulnerabilities>
<severity_table>
Common Vulnerabilities
| Issue | Where to Look | Severity |
|---|---|---|
| NEXT_PUBLIC_ secrets | .env* files | CRITICAL |
| Unauth'd Server Actions | app/**/actions.ts | HIGH |
| Unauth'd API routes | app/api/**/route.ts, pages/api/** | HIGH |
| Middleware matcher gaps | middleware.ts | HIGH |
| Missing input validation | Server Actions, API routes | HIGH |
| IDOR in dynamic routes | [id] params without ownership check | HIGH |
| dangerouslySetInnerHTML | Components | MEDIUM |
| Missing security headers | next.config.js | LOW |
</severity_table>
<commands>Quick Grep Commands
# Find NEXT_PUBLIC_ usage
grep -r "NEXT_PUBLIC_" . -g "*.env*" -g "*.ts" -g "*.tsx"
# Find next.config env usage (always bundled)
rg -n 'env\s*:' next.config.*
# Find Server Actions without auth
rg -l '"use server"' . | xargs rg -L '(getServerSession|auth\(|getSession|currentUser)'
# Find API routes
fd 'route\.(ts|js)' app/api/
# Find dangerouslySetInnerHTML
rg 'dangerouslySetInnerHTML' . -g "*.tsx" -g "*.jsx"
</commands>How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/igorwarzocha/opencode-workflows/security-nextjs">View security-nextjs on skillZs</a>