Antra
How it worksStar1Get Started
Star1
Antra
How it worksStar1Get Started
Star1
Documentation
  • Introduction
  • Installation
  • Quick start
  • Configuration basics
  • Monorepos
  • Trust boundaries
  • Findings and severity
  • Schema providers
  • Suppressions and baselines
  • antra init
  • antra scan
  • antra sync
  • antra doctor
  • antra baseline
  • antra trace
  • antra watch
  • antra fix
  • antra query
  • antra studio
  • Studio
  • Launching Studio
  • Reading the graph
  • Configuration reference
  • BG-001: Server-to-client leak
  • BG-002: Unvalidated input
  • BG-003: Opaque object pass
  • BG-004: Secret in markup
  • GitHub Actions
  • Introduction
  • Installation
  • Quick start
  • Configuration basics
  • Monorepos
  • Trust boundaries
  • Findings and severity
  • Schema providers
  • Suppressions and baselines
  • antra init
  • antra scan
  • antra sync
  • antra doctor
  • antra baseline
  • antra trace
  • antra watch
  • antra fix
  • antra query
  • antra studio
  • Studio
  • Launching Studio
  • Reading the graph
  • Configuration reference
  • BG-001: Server-to-client leak
  • BG-002: Unvalidated input
  • BG-003: Opaque object pass
  • BG-004: Secret in markup
  • GitHub Actions

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) {
  // ...
}
Previous
BG-001: Server-to-client leak
Next
BG-003: Opaque object pass
On this page
  • Why it fires
  • What it checks
  • Why the trace is empty
  • Fixing it
  • Parse with a schema library
  • Write your own validator
  • Suppress it when something upstream validates