スキル一覧に戻る
IgorWarzocha

security-nextjs

by IgorWarzocha

An ever evolving repository of Opencode workflow examples that might enhance your experience with it. I only left the stuff that actually works. YMMV.

51🍴 6📅 2026年1月22日
GitHubで見るManusで実行

SKILL.md


name: security-nextjs description: |- 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

Security audit patterns for Next.js applications covering environment variable exposure, Server Actions, middleware auth, API routes, and App Router security.

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:

  1. grep -r "NEXT_PUBLIC_" . -g "*.env*"
  2. For each var, ask: "Would I be OK if this was in view-source?"
  3. Common mistakes:
    • NEXT_PUBLIC_API_KEY (SHOULD be server-only)
    • NEXT_PUBLIC_DATABASE_URL (MUST NOT use)
    • NEXT_PUBLIC_STRIPE_SECRET_KEY (use STRIPE_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,
  },
};

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
        ],
      },
    ];
  },
};

<severity_table>

Common Vulnerabilities

IssueWhere to LookSeverity
NEXT_PUBLIC_ secrets.env* filesCRITICAL
Unauth'd Server Actionsapp/**/actions.tsHIGH
Unauth'd API routesapp/api/**/route.ts, pages/api/**HIGH
Middleware matcher gapsmiddleware.tsHIGH
Missing input validationServer Actions, API routesHIGH
IDOR in dynamic routes[id] params without ownership checkHIGH
dangerouslySetInnerHTMLComponentsMEDIUM
Missing security headersnext.config.jsLOW

</severity_table>

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"

スコア

総合スコア

60/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です