nextjs-server-action-validation — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited nextjs-server-action-validation (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
What it is: The validation and authorization discipline for Next.js Server Actions that accept client-originated input. Mental model: A Server Action is a public POST endpoint wearing function-call syntax. Why it exists: Convenience syntax can hide the HTTP boundary, leading developers to skip auth, input validation, or org scoping. What it is NOT: It is not GET route handling, Server Component data fetching, or Stripe webhook verification. Adjacent concepts: Zod schemas, auth checks, org-scoped mutations, controlled error surfaces. One-line analogy: It is the security checkpoint every form submission crosses before touching the database. Common misconception: Only the project UI can call the action; any HTTP client can call the generated endpoint.
'use server' functions are exposed as POST endpoints that any HTTP client can call directly; the Next.js call graph is not a security boundary.safeParse() over .parse() to return structured errors rather than throwing; returning { error: ... } shapes that the calling Client Component can renderinput.orgId matches the session's org, not just that the user is logged intry/catch around database calls prevents internal errors from propagating to the client responseServer Actions are a convenience feature that makes form submission feel like a function call. That convenience obscures a critical fact: the action is a public HTTP POST endpoint. A developer who writes const { data } = await myAction(formData) in a Client Component is writing what looks like a local function call, but the runtime sends an HTTP request that any script can replicate. Skipping auth or validation because "it's called from our own UI" is the same reasoning that made getServerSideProps data-fetching functions leaky in the Pages Router — the server boundary does not restrict callers.
"use server";
import { z } from "zod";
import { getServerSession } from "@/lib/auth";
import { orgQuery } from "@/lib/db";
const CreateOrderSchema = z.object({
orgId: z.string().uuid(),
planId: z.string().min(1),
quantity: z.number().int().positive(),
});
export async function createOrder(input: unknown) {
// 1. Auth check — before anything else
const session = await getServerSession();
if (!session?.user) {
return { error: "Unauthorized" };
}
// 2. Zod parse — structured errors, not thrown exceptions
const parsed = CreateOrderSchema.safeParse(input);
if (!parsed.success) {
return { error: parsed.error.flatten() };
}
// 3. Org-scope check — user must belong to the org they are acting on
if (session.user.orgId !== parsed.data.orgId) {
return { error: "Forbidden" };
}
// 4. Database write — inside orgQuery for RLS enforcement
try {
const [order] = await orgQuery(parsed.data.orgId, (tx) =>
tx`INSERT INTO orders (org_id, plan_id, quantity) VALUES (
${parsed.data.orgId}, ${parsed.data.planId}, ${parsed.data.quantity}
) RETURNING *`
);
return { data: order };
} catch {
return { error: "Failed to create order" };
}
}| Order | Risk |
|---|---|
| Auth → Zod → orgScope → DB | Correct — each gate eliminates the next attack surface |
| Zod → Auth → DB | Attacker can probe the schema structure without authenticating |
| Auth → DB (no Zod) | Malformed input reaches the query layer; injection risk |
| DB → Auth (anywhere) | Unauthenticated database reads before rejection |
'use server' function calls getServerSession() as its first statement'use server' function runs Schema.safeParse() before any database callorgQuery, not bare SQL| Use instead | When |
|---|---|
stripe-webhook-signature-verification | The entry point is a webhook route handler, not a Server Action |
| (a data-fetching skill) | The function is a Server Component that fetches data, with no user-submitted input |
postgres-rls-pattern | The task is defining the database-layer policy, not the action-layer validation |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.