Building Apps with AILesson 70 of 87

Contents

Building Apps with AI

Lesson 70 of 87. Sign in to keep your place.

Module 8 · The security holes AI code has by default

Secrets in the built bundle, and in the history

8 min read

When you find a key where it should not beRevoke itAt the provider, now,before anything elseIssue a new oneInto the host’ssettings, server-side,no prefixRead the usageFor the whole periodit was exposedThen tidy upDelete the file, addit to .gitignore,commitThe instinct is to delete the file, and it is the least useful step. A key that has been in a publicrepository or a public bundle is compromised, and no operation on your side changes that.
The instinct is to delete the file, and it is the least useful step. A key that has been in a public repository or a public bundle is compromised, and no operation on your side changes that.

Figure

Two places a secret hides

The first security lesson showed the leak in principle: a key behind a public prefix, or a .env file committed. This lesson is the procedure for finding both, because both are invisible in the editor. The key is not in any file you would open; it is in the output of the build, and in commits you have forgotten.

Searching the bundle

Build the production output and search it for the shapes of keys:

Bash
npm run build
grep -rEo "(sk-[A-Za-z0-9_-]{10,}|AIza[A-Za-z0-9_-]{30,}|AKIA[A-Z0-9]{16}|postgres(ql)?://[^\"' ]+)" .next/static dist build 2>/dev/null | head

Adjust the patterns to the providers you use; most keys have a recognisable prefix, and a database URL is unmistakable. Anything that prints is in a file every visitor downloads.

The same check from the outside, on the deployed site: open the developer tools, the Sources panel, and use search across all files for the same patterns. This is what a scanner does to every public site, automatically, and it takes it seconds.

Source maps are the quiet version. A production build that ships .map files lets anyone reconstruct the original source, including comments, and sometimes server-side files bundled by mistake. Most frameworks disable source maps in production by default; check that yours has not been switched on by an agent debugging a build.

Searching the history

Bash
git log -p --all | grep -nE "sk-[A-Za-z0-9_-]{10,}|AIza|AKIA|BEGIN (RSA|OPENSSH) PRIVATE KEY" | head

That searches every diff of every commit on every branch. It is slow on a large repository and it is worth the wait once. For a proper job, gitleaks is free, runs in seconds, and knows several hundred key formats:

Bash
gitleaks detect --source . --verbose

Run it before making any repository public, and put it in a pre-commit hook or the CI so that a key cannot be committed in the first place. Catching the commit is worth a hundred times the scan afterwards.

When you find one

The order matters, and the instinct, delete the file, is the least useful step.

  1. Revoke the key at the provider. Now, before anything else. A key that has been in a public repository, or in a bundle on a public site, is compromised, and no operation on your side can change that. The provider’s dashboard has a revoke or delete button next to every key.
  2. Issue a new one and put it in the host’s environment settings, server-side, no prefix.
  3. Check the provider’s usage for the period the key was exposed. A bill you did not expect is the sign somebody found it first.
  4. Then tidy the repository. Delete the file, add it to .gitignore, commit.

Rewriting history to remove the commit (git filter-repo does it) is a reasonable extra step for a repository that will be public, and it is not a fix. Anyone who cloned in the meantime has the old history, and GitHub keeps orphaned commits reachable by id for some time after a rewrite. Revocation is the fix. Everything else is housekeeping.

Where else it lands

Logs. A request that failed with the key in a header, printed in full by a library’s debug mode, is in the host’s log retention for as long as that is set. An error tracker that captures request context captures the header too. Check what your error tracking is configured to scrub; the good ones scrub Authorization by default and nothing else.

Chat transcripts. A .env pasted into an agent’s conversation to “help it understand the setup” is in that provider’s retention. Agents can read the file themselves; they do not need it pasted, and the rules file can say so.

Screenshots and screen recordings. The demo video with the terminal open showing DATABASE_URL. This has happened to companies.

The variables that are not secrets

Not everything in .env is sensitive. A public analytics id, a map tile key designed to be public, the app’s own URL: these belong with the prefix and it is fine. The distinction is whether a stranger with the value can cost you anything or read anything: if yes, it is a secret and it never leaves the server; if no, the prefix is honest.

The check to keep

Before every public release: the bundle grep, the history scan, and a look at the Network tab for any request leaving the browser with a credential attached. Five minutes. The founding lesson of this block said a key in the browser is published; this is how you make sure you did not publish one.

Every lesson here is open. Your progress is recorded once you pass the Building with AI exam.

Check your understanding

A key was committed in .env three weeks ago, then .env was added to .gitignore and deleted from the repository, and the repository was later made public. What is the state of the key?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minInjection in three places: the query, the shell, the URL

Stuck on something?Ask Beatriz, your AI tutor

How to cite: Addaly (2026) ‘Secrets in the built bundle, and in the history’, in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/the-built-bundle-and-the-history (Accessed: 13 October 2026).