# An API key leaked into your JavaScript bundle

A credential reached a deployed bundle. How to find out what your live site exposes right now, what the next build changes for the value, and why rotation is the step that closes it.

Source: https://www.afterpack.dev/docs/use-cases/accidental-secrets

A credential, token or internal URL that was never meant to leave your codebase reached a bundle you deployed. You need to know what is live right now, and what to do about it.

## A shipped credential is found by scanners, not by people

A leaked credential is a live incident from the moment it is served. The discovery path is automated: a secret scanner, a bundle scraper or a researcher's script finds a plaintext key in a JavaScript file long before a person sits down and reads your code. The exposure clock started at deploy, whatever your dashboards show.

The cost scales with how the credential is scoped. A key with broad permissions on a shared account is a worse incident than a narrowly scoped one. Both are incidents from the moment they are reachable over the network.

## Find out what your live site exposes

Run [`audit`](https://www.afterpack.dev/docs/audit) against the deployed site before you change anything:

```bash
npx afterpack@latest audit https://yoursite.com
```

It reports exposed secrets, reachable source maps and how protected the shipped JavaScript is, for the bundle a visitor's browser receives right now. Read the secrets and the source maps first. A report with no findings means this scan found nothing at those URLs, and it is not evidence that nothing leaked.

## What the next build changes for the value

The [default preset](https://www.afterpack.dev/docs/presets) encodes your string literals at build time, so the internal endpoint, the flag name and the credential stop being plaintext in the file you serve. A `grep` of the new artifact for the key, or for the text around it, does not find it, and the automated scrapers that turn up most leaked keys come away with nothing. Some literals stay readable at every preset, and the [Protection Map](https://www.afterpack.dev/docs/protection-map) lists which ones.

That buys you the time to finish a rotation properly. It does not undo the leak. The old build already went out, and the value is still present in the new one. Encoding changes what a reader finds in the file, while the code still holds the value and uses it.

## Keep source maps and backups out of the pipeline

Check that nothing in your pipeline turns on the outputs that hand your original source back:

```json
{
  "sourceMap": { "enabled": false, "emitUrl": false, "sourcesContent": false },
  "build": { "backup": false },
  "protectionMap": { "enabled": true }
}
```

A source map maps the obfuscated file back to your readable source, and [`sourceMap.sourcesContent`](https://www.afterpack.dev/docs/config#sourceMap-sourcesContent) embeds that source inside the map. Maps default off in production, and a pipeline that switches them on for debugging is the usual way one reaches a server; [build with the map and keep it private](https://www.afterpack.dev/docs/best-practices#debug-production-traces-without-publishing-the-map) when you need production traces.

The [`build.backup`](https://www.afterpack.dev/docs/config#build-backup) copy is your source verbatim. The CLI keeps it in the gitignored `.afterpack/backup/` at the project root, outside whatever you deploy. Set it to `false` when a framework plugin writes the copy beside the output and your deploy ships that directory wholesale. [`protectionMap.enabled`](https://www.afterpack.dev/docs/config#protectionMap-enabled) writes the Protection Map into `.afterpack/` on the build machine, and the CLI refuses to write it into a served path.

## Confirm the finding is gone

Run the audit again after you rotate and redeploy, and confirm the earlier finding is gone. Add [`--diagnostics.format=json`](https://www.afterpack.dev/docs/config#diagnostics-format) to read the result in a script. If the build reports [`DIAG_SURVIVING_READABLE_LITERAL`](https://www.afterpack.dev/docs/diagnostics), a readable literal is still in the output, and [`strings.leaks.max`](https://www.afterpack.dev/docs/config#strings-leaks-max) fails the build when more than the number you set remain. That diagnostic counts literals in general and does not tell you which one is the credential.

## What obfuscation cannot undo

Encoding applies to every build from here on, so the plaintext value leaves the artifact a scanner reads, and the audit tells you what the live site exposes today. What it cannot do is reach the build that already went out: rotating the credential is the step that closes the incident, and redeploying is what stops the old readable bundle from being served.

## Next

- [Audit a live site's JavaScript](https://www.afterpack.dev/docs/audit): the full check list and how to read a report.
- [Best practices](https://www.afterpack.dev/docs/best-practices#if-you-shipped-a-secret-by-accident): the rotation steps in order.
- [Build & CI](https://www.afterpack.dev/docs/builds): keeping source maps and backups out of every environment.
