BG-001: Server-to-client leak
A sensitive value leaves the server through a Client Component prop, a Server Action return, or a route response.
Severity: error
A concrete sensitive value leaves the server where the browser can read it:
- as a prop, a spread, or children of a Client Component rendered by a Server Component
- as the return value of a Server Action
- in the body of a route handler response (
Response.json,NextResponse.json,new Response(...))
Why it fires
That value crosses the React Flight boundary as serialized data the browser can read. The rule looks at Server Components specifically, finds JSX sinks whose target file is classified as a client boundary, and checks the attributes being passed.
What counts as sensitive
The analysis treats a value as sensitive when it is:
- a field named by your schema, through a configured provider
- a field name matching a configured keyword
- a
process.envvalue, including one read through a variable (const { KEY } = process.env) or a t3-env object (env.KEY)
Public variables are skipped: NEXT_PUBLIC_*, NODE_ENV, NEXT_RUNTIME,
VERCEL_ENV, and anything declared under t3-env's client or shared.
process.env is in that list because a server-side environment variable read
into a prop is a leak regardless of what your schema says. The schema was never
asked about it.
How far it follows a value
Through assignments, destructuring, object literals, function returns (including
wrappers such as cache() and unstable_cache), up to three imports, and one
hop into a component's props, even when that component lives in another file.
next/dynamic and React.lazy components resolve to the file they import.
What the client itself sent is never reported: a request body, a Server Action
argument, or a page's params and searchParams.
The mistake to avoid
Serializing the value is not the same as needing it on the client. An object crossing into client props is well-typed either way; the question is whether those fields were meant to travel.
Fixing it
Project the fields the client actually needs into a new object before the crossing. When the crossing is intentional and the value is safe, record the decision at the line:
// antra-disable-next-line
return <AccountCard account={account} />;
For a whole report at once, antra baseline records
the current violations instead.