Props passed to Client Components are visible in the browser. Never pass API keys, tokens, or full database records to client code. [CWE-200 · A01:2021]
prevents exposure of API keys, tokens, and internal data to browsers
BeforeMerge scans your pull requests against this rule and 5+ others. Get actionable feedback before code ships.
Impact: CRITICAL (prevents exposure of API keys, tokens, and internal data to browsers)
Any prop passed from a Server Component to a Client Component is serialized into the HTML response and visible in the browser. This includes the RSC payload, which is inspectable in the network tab. Never pass API keys, internal IDs, full database records, or any data the client doesn't need.
Incorrect (leaking secrets via props):
// app/page.tsx (Server Component)
export default async function Page() {
const config = {
apiKey: process.env.STRIPE_SECRET_KEY, // ❌ Secret key!
dbUrl: process.env.DATABASE_URL, // ❌ Connection string!
internalUserId: 'usr_admin_29384', // ❌ Internal ID!
}
return <Dashboard config={config} />
}
// components/Dashboard.tsx
'use client'
export function Dashboard({ config }) {
// All of config is now in the browser HTML and RSC payload
}Also incorrect (over-fetching database records):
// ❌ Passing entire user record to client
async function Page() {
const user = await db.user.findUnique({
where: { id: userId },
}) // Returns 30+ fields including passwordHash, ssn, etc.
return <Profile user={user} />
}Correct (pass only what the client needs):
// app/page.tsx (Server Component)
export default async function Page() {
// ✅ Keep secrets server-side, pass only public config
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_KEY
return <Dashboard stripeKey={publishableKey} />
}// ✅ Select only the fields the client needs
async function Page() {
const user = await db.user.findUnique({
where: { id: userId },
select: {
name: true,
email: true,
avatarUrl: true,
// Only fields the UI actually renders
},
})
return <Profile user={user} />
}Use server-only to prevent accidental imports:
// lib/secrets.ts
import 'server-only'
export const config = {
stripeSecret: process.env.STRIPE_SECRET_KEY!,
dbUrl: process.env.DATABASE_URL!,
}
// Importing this file in a Client Component will now cause a build errorDetection hints:
# Find env vars being passed as props (potential leaks)
grep -rn "process\.env\." src/ --include="*.tsx" | grep -v "NEXT_PUBLIC"
# Find large objects being passed to client components
grep -rn "findUnique\|findFirst\|findMany" src/ --include="*.tsx" | grep -v "select:"Reference: Next.js Server Components · server-only package