Server Actions: Hidden POST endpoints
Next.js Server Actions allow developers to write server-side functions ("use server") and call them directly from client-side forms. Under the hood, Next.js compiles these functions into HTTP POST endpoints. An attacker can scan the bundle to extract these endpoints and call them directly.
A common finding in Next.js audits is missing authorization on Server Actions. Because they look like standard functions, developers assume they inherit page-level middleware protection. They do not:
// VULNERABLE Server Action
"use server"
export async function updateBalance(userId: string, newBalance: number) {
// Missing check: does the active session have permissions to modify this user?
await db.wallets.update({ where: { userId }, data: { balance: newBalance } });
} The Fix: Treat Server Actions exactly like REST API controllers. Authenticate the session, validate input shapes using libraries like Zod, and verify resource ownership inside the function:
// SECURE Server Action pattern
"use server"
import { auth } from "@/lib/auth";
import { z } from "zod";
const schema = z.object({ userId: z.string(), newBalance: z.number() });
export async function updateBalance(input: unknown) {
const session = await auth();
if (!session || session.role !== 'admin') throw new Error("Unauthorized");
const { userId, newBalance } = schema.parse(input);
await db.wallets.update({ where: { userId }, data: { balance: newBalance } });
} React Server Components data serialization leaks
React Server Components (RSC) execute on the server and stream UI chunks to the browser. If a Server Component queries the database and passes the record object to a Client Component, Next.js must serialize that object into the HTML page state.
Consider a database table containing `User` records with a `password_hash` column. Even if the Client Component only displays the user's name, the entire serialized JSON object is sent to the client. We extract this data by inspecting the __NEXT_DATA__ or RSC payloads in the browser's source:
// VULNERABLE passing database objects to client props
export default async function ProfilePage() {
const user = await db.user.findFirst();
return <ClientProfile user={user} /> // Leak! Password hash is serialized in RSC payload
} The Fix: Implement strict Data Transfer Objects (DTOs) or use projection queries to select only the fields required for the UI, ensuring private fields never leave the server layer.
Securing API routes and Route Handlers
Next.js Route Handlers (route.ts) are standard backend endpoints. We audit these routes for CSRF protection, CORS configurations, and input validation. We verify that Next.js middleware is configured to intercept and validate JWT sessions before routing to handlers.
BOLA in Next.js Server Action
We audited a Next.js digital wallet platform. The app used a Server Action to retrieve transaction invoices. By extracting the Action ID from the JavaScript bundle and firing a direct POST request referencing a different invoice ID, we successfully retrieved invoice records belonging to other users. The action verified authentication but omitted the resource ownership check. Fix priority: critical. Remediated by adding session-owner validations to the database query.
Building a full-stack web application with Next.js? Schedule a security audit.
Book a Next.js Security AuditFrequently asked questions
Why are Next.js Server Actions vulnerable to authorization bypass?
Server Actions expose POST endpoints under the hood. If developers treat them as internal functions and skip session validation checks, an attacker can invoke the Action directly and bypass client-side permission checks.
How does data leak in React Server Components (RSC)?
If you query database records directly inside a Server Component and pass the resulting object to a Client Component, Next.js serializes the entire object into the JSON payload sent to the client. This can leak private fields (like password hashes or API keys) even if they aren't rendered in the UI.
Do environment variables starting without NEXT_PUBLIC_ leak to the client?
By default, Next.js protects variables without the `NEXT_PUBLIC_` prefix from reaching the client. However, if these variables are referenced inside Client Components or passed via props, they will be bundled into the client-side code.
Related reading
Blog: Securing Node backends · JWT security mistakes · API leaks
Services: API security testing · Secure architecture review