Question presented to candidate: "A security review found our Stripe secret key in the HTML of one page. In a Next.js App Router app, how can secrets and server-only code leak to the browser, and what layers would you put in place so it cannot happen again?"
What a strong answer should cover:
- The leak paths: props passed from a Server Component to a Client Component are serialized into the RSC payload and HTML; modules imported by a Client Component are bundled for the browser; a Client Component also renders on the server during prerendering, where
process.envholds real values. - Environment variables: only
NEXT_PUBLIC_values are inlined into the browser bundle; everything else is server-only, but a Client Component that reads one can still put it into server-rendered HTML. import "server-only"turns "a Client Component imported this module" into a build error.- A Data Access Layer: the only code that touches the database and secrets, does the authorization, and returns minimal DTOs instead of whole rows.
- React taint APIs (
experimental.taint):experimental_taintObjectReferenceandexperimental_taintUniqueValuemake React throw if a marked object or value reaches a Client Component; they are a backstop, not the main defence (copies and derived values escape).
Clarifying questions expected:
- "How did the key get there: a prop, a client import or an env var read in a client file?" β each path has a different fix.
- "Is there a shared
lib/imported by both server and client code?" β the main placeserver-onlybelongs.
Code / implementation expected: Yes β a server-only DAL that returns a DTO, and a taint example.