A module that reads a private key turns up in your shipped browser bundle. How do you find the import that pulled it in, and what else does the leak require?
answer
- production build, not the dev server
- ask the build why it is included
- barrels and shared helpers
- rotate: the build was public
- guard it so it cannot recur
basics
~20 sOn a production build, ask the bundler why that module is included and walk the chain back to the client entry; the cause is usually a re-export barrel or a helper. Then rotate the key, because that build was served.
solid answer
~50 sWork on a **production build** — a development server resolves and serves modules differently, so its graph is not what ships. Confirm the leak in the emitted files by searching for the literal, then ask the build why the module was included: bundlers can report, for any module in the output, the chain of importers back to an entry point. Walk that chain from the client entry, not from the sensitive file; the culprit is usually in the middle and looks innocent — a re-export barrel, a shared helper, a constant, or a type imported as a value. The fix is structural: split the wanted symbol into a module that imports nothing privileged, then add a build-time guard so the next one fails the build. And finish the other half: a build that was served is public, so rotate the key, scope who could have fetched it, and check the logs for use.
go deeper
Know that anything in the shipped bundle is readable, and that the first step is to look at the built output rather than reason from the source. Escalate rather than quietly patching the import.
Be able to explain how a module gets into a bundle it was never meant for — transitive imports, re-export barrels, a type imported as a value — and how to read the build's report of why a module is included.
Run both threads: fix the graph structurally and treat the credential as disclosed. Rotate, scope the exposure window and audience, check for use, and leave behind a build-time guard instead of a one-off import fix.
Ask why the codebase allowed a shared module to sit on the boundary at all, and whether privileged access is scattered widely enough that this will recur. The durable fix is consolidation plus enforcement, not a post-mortem action item.
## First, reproduce on the right artefact Investigate a **production build**, not the development server. A dev server typically serves modules on demand, with different resolution and without the elimination decisions the production compilation makes, so its module set is neither a superset nor a subset of what ships — it is simply a different graph. Anything you conclude from it may be wrong in both directions. Confirm the leak against the built client output itself: search the emitted files for the literal, or for a distinctive identifier from the module. That gives you a fact ("this string is in this chunk") rather than an inference. ## Attribute the module to an import chain 1. **Ask the build why the module is included.** Bundlers can report, for any module in the output, the reason it was pulled in and the chain of importers back to an entry point. This is the fastest path and it is exact. 2. **Walk the chain from the client entry, not from the sensitive file.** The question is not "who imports the key reader" — it is "which client entry reaches it", and the answer is usually a module that looks entirely innocent in the middle. 3. **Bisect if no report is available.** Remove or stub the suspected import, rebuild, and check the output again. Slow, but it always works. 4. **Check the compiled form, not the source.** A re-export chain can look like one import in source and three in the graph. ## The usual causes | Cause | What it looks like | Fix | |---|---|---| | Re-export barrel | client code imports one helper from a folder index that also re-exports the privileged module | import the symbol from its own module, or split the barrel | | Shared "utils" module | one generic function lives in a file that also imports data access | move the shared function to a neutral module that imports nothing privileged | | Value import of a type | a type is imported without the type-only form, so the runtime module travels | make the import type-only, or move the type to a types-only module | | Shared constant | a client module imports a constant from a module whose other exports read configuration | relocate the constant to a boundary-free module | | Boundary marker placed too high | a container was declared client-side, dragging its subtree across | push the declaration down to the unit that actually needs the browser | | Environment check assumed to prune | a branch that never runs in the browser was assumed to be stripped | do not rely on elimination; separate the module | The pattern behind all six is the same: a **shared module sitting on the wrong side of the line**. The repair is structural — split the wanted symbol out into a module that imports nothing privileged — not a rearrangement of the import that happened to trip it this time. ## The half of the answer people forget Finding and fixing the import closes the defect. It does not close the incident. A build that was served is **public**. It was downloaded by browsers, cached by intermediaries, and may have been archived. Minification renames identifiers; string literals survive it intact, and a deployed source map makes even the identifiers legible again. Therefore: - **Rotate the credential.** This is the only step that revokes it. Redeploying without it changes nothing about the copies already delivered. - **Scope the exposure.** How long was that build served, to whom, and behind what gate? A build served only to an internal audience is still disclosed, but the urgency and the notification obligations differ. - **Look for use.** Check the logs of whatever the credential authenticates against for calls that do not match your servers, over the exposure window. - **Assume more than one value leaked.** If a module shipped, everything it inlined shipped with it. ## Prevent the recurrence The fix that matters is not the import you deleted — it is the check that makes the next one impossible to merge: 1. Put a build-time guard on the module so any client-reachable import fails the build and names the chain. 2. Prefer to guard the **chokepoints** through which privileged access flows, so one decision covers many files. 3. Add a pipeline step that searches the built client output for a canary value and for obvious credential shapes, failing before deploy. 4. Write the cause into the codebase, not just the incident log: a barrel that caused this once will cause it again unless it is split. ## What a strong answer sounds like Two threads, kept separate and both finished: the **engineering** thread (production build, module-reason report, chain from the client entry, structural split, guard) and the **incident** thread (treat as disclosed, rotate, scope, check for use). A candidate who does only the first has fixed the bundle and left the credential valid in the wild.
- Why can the development server hide this problem?It typically serves modules on demand with different resolution and without the elimination decisions the production compilation makes, so its module set is a different graph rather than a preview of the shipped one. A module can be absent there and present in the build, or the reverse, which makes any conclusion drawn from it unreliable.
- The leaked build was served only behind an internal login. Does that change the response?It scopes the urgency, not the verdict. The asset was still delivered to browsers and caches, and a login in front of a static file is not a secret boundary. Rotate anyway, then let the exposure window and the audience decide the priority and any notification obligation.
- How would you catch this class of defect automatically?Two layers. Guard the chokepoint modules so a client-reachable import fails the build and names the chain, and add a pipeline step that searches the built client output for a planted canary and for obvious credential shapes, failing before deploy. The canary also proves the scanner itself works.
saying these in an interview costs you the question
- Redeploys without the key and considers the incident closed.
- Trusts the development server's module graph to match production.
- Assumes the key is safe because the bundle is minified.
- Deletes one import without adding a check that prevents the next.
- Searches only direct imports, missing a re-export three levels up.