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-003: Opaque object pass

An unprojected fetch or database result is sent whole to the client.

Severity: warning

The raw, unprojected result of a fetch or database call is handed whole to the client: as a Client Component prop, as a Server Action's return value, or as a route handler's response body.

Why it fires

Nobody picked fields, so whatever the backend returns gets serialized to the browser. Today, and again after its next deploy. The set of fields that cross is not decided by your code; it is decided by whoever changes that endpoint.

That is why it is a warning rather than an error: there may be nothing sensitive in the response today. The finding is about the shape of the decision, not about a value that is known to be dangerous.

It is not a type error, and it is not about keywords

TypeScript does not save you here. Types are erased at compile time, so even when user is declared as interface PublicUser { name: string }, React Server Components serialize the actual runtime object across the React Flight protocol. If the query returned fifteen columns, all fifteen travel in the payload and are readable in the browser's network tab.

It is also not the keyword list that catches this. BG-001 fires on named sensitive fields such as password_hash, token, or secret. BG-003 fires on unprojected ingress data whether or not any field is sensitive today, which is what protects you when a later migration adds a column nobody thought to project.

Fixing it

Project the fields explicitly

Pass the specific fields the client component needs, in a new object literal:

// Passes every database column to the browser
<StudentProfileScreen user={studentUser} />

// Only these fields are serialized
<StudentProfileScreen
  user={{
    id: studentUser.id,
    firstName: studentUser.first_name,
    lastName: studentUser.last_name,
    experiences: studentUser.experiences,
  }}
/>

Parse it through a validator or DTO

A schema parse or a mapping function does the same job and keeps the projection in one place. Declare the function under sanitizers so the analysis knows it clears the value:

const safeUser = publicStudentSchema.parse(studentUser);

return <StudentProfileScreen user={safeUser} />;

Suppress it when you have checked the query

If you have read the query and know that every column is safe and intended for the browser, record that at the crossing:

return (
  <main>
    {/* antra-disable-next-line BG-003 reason="public profile DTO verified" */}
    <StudentProfileScreen user={studentUser} />
  </main>
);
Previous
BG-002: Unvalidated input
Next
BG-004: Secret in markup
On this page
  • Why it fires
  • It is not a type error, and it is not about keywords
  • Fixing it
  • Project the fields explicitly
  • Parse it through a validator or DTO
  • Suppress it when you have checked the query