BG-002: Unvalidated input
A Server Action or route handler accepts input without validation.
Severity: error
A Server Action that accepts parameters, or a route handler that reads the request body, where no configured validator is ever given that input.
Why it fires
Client input is untrusted by definition. A Server Action is a public endpoint, whether or not the component calling it sits behind a login, so "it's only reachable from our own page" is not a defence the analysis can act on.
What it checks
For a Server Action, the rule looks at whether the action accepts parameters at all. For a route handler, it looks for a read of the handler's own request (its first parameter):
- a call to
.json(),.formData(),.text(),.arrayBuffer(), or.blob() - a direct read of
request.body
Calling .json() on the response of an outgoing fetch is not a body read.
Handlers wrapped in a higher-order function are unwrapped too:
export const POST = withAuth(async (req: Request) => {
const body = await req.json();
// ...
});
It then looks for a call to a validator you have configured under sanitizers
that receives the input: a parameter, the body, or a variable derived from
them, such as Object.fromEntries(formData). A validator called on something
else does not count, and neither do parsers that decode rather than validate
(JSON.parse, Date.parse, URL.parse, path.parse, qs.parse, ...).
Query strings and route params are not checked by this rule.
GET, HEAD, and OPTIONS are exempt, since they have no body to validate.
Why the trace is empty
Most findings carry the path a value took. BG-002 does not, and that is deliberate: it is about the shape of the interface (parameters accepted, no validation), not about a particular value's journey through your code. There is no path to report.
Fixing it
All BG-002 asks for is one runtime validation step before you use the external parameters or the request body. There are three ways to give it one.
Parse with a schema library
If your project already uses Zod, Valibot, or similar, parse the input before handing it to anything internal:
import { z } from "zod";
const UpdateProfileSchema = z.object({
name: z.string().min(2),
bio: z.string().max(500).optional(),
});
export async function updateProfileAction(formData: FormData) {
"use server";
// Antra recognizes .parse() and clears the BG-002 error
const validated = UpdateProfileSchema.parse(Object.fromEntries(formData));
await db.users.update(validated);
}
Write your own validator
You do not need to install anything. A plain TypeScript function that rejects what it does not expect is enough:
// In your code: utils/validate.ts
export function validateConversationId(id: unknown): string {
if (typeof id !== "string" || id.length < 10) {
throw new Error("Invalid conversation ID");
}
return id;
}
Then register the function name in antra.config.json, or in Studio under
Sanitizers & Validators:
"sanitizers": ["validateConversationId"]
Once the name is declared, Antra treats a call to it as an approved checkpoint:
export async function uploadChatAttachmentAction(
formData: FormData,
conversationId: string,
) {
"use server";
const validId = validateConversationId(conversationId);
// ...
}
Suppress it when something upstream validates
If the endpoint is already validated before it gets here, by a proxy, an API gateway, or your own middleware, say so at the line:
// antra-disable-next-line BG-002 reason="validated upstream by authentication middleware"
export async function POST(request: Request) {
// ...
}