Sitemap
Ali Mahmood

Ali Mahmood

Your AI Coding Agent Will Eventually Commit Your API Keys

4 min readOct 10, 2026

--

I built envguard, a single Go binary that stops it at the commit. Then GitHub blocked my own push, and my first scan found keys in one of my repos.

AI coding agents are great at getting things working, and getting things working usually means pasting a key somewhere. Your agent writes STRIPE_SECRET_KEY=sk_live_… into a config file to "test quickly". It creates a .env and forgets that .gitignore doesn't cover it. You review the logic in the diff and skim straight past line 14.

Then it’s in git history, which effectively lasts forever.

GitHub’s push protection catches some of this, but only when you push, only on GitHub, and only for patterns GitHub knows. By then the secret is already in a commit on your laptop, in every backup, and in every tool that has read the repo.

So I built envguard: a git pre-commit hook that blocks a commit if it adds a secret.

$ git commit -m "wire up billing"
envguard: 1 possible secret in staged changes; commit blocked
  src/billing.js:14:22  stripe-secret-key  Stripe live secret or restricted key  sk_l********

The mental model: a bouncer at git’s front door

Picture a bouncer standing at the door to your repository, following three rules:

  • Only check who’s coming in. envguard looks at the lines a commit adds, not the whole repo. An old secret already in history shouldn’t block every unrelated commit forever.
  • Know the usual troublemakers on sight. Stripe, AWS, Anthropic, OpenAI, GitHub, Slack, Google and Resend keys, private keys, and passwords in database URLs all have recognisable shapes.
  • Never shout the name. The output shows the first four characters and a fixed-width mask. Even the secret’s length stays hidden, so CI logs and screenshots are safe.

How it works

1. Read only what’s being added. envguard runs git diff --cached -U0 and parses the hunk headers, so every added line comes with its real file and line number.

2. Match known formats first. Each provider’s key has a recognisable shape: Stripe live keys start with sk_live_, AWS key IDs with AKIA, private keys with a -----BEGIN … PRIVATE KEY----- header.

3. Then catch the unknown ones. A generic rule looks for a variable named like a secret (*_SECRET, *_TOKEN, api_key, password) set to a value that looks random. That means a mix of letters and digits and high Shannon entropy, a measure of how unpredictable the characters are.

Get Ali Mahmood’s stories in your inbox

Join Medium for free to get updates from this writer.

Placeholders like your-key-here, ${VAR} and process.env.X are ignored, so it doesn't cry wolf on template files.

4. Flag files that should never be committed at all: .env (but not .env.example), Cloudflare's .dev.vars, SSH keys, and .pem and .key files.

The whole thing is about 750 lines of Go with no dependencies. It checks files in parallel and skips binary files and anything over 5 MB.

The irony: GitHub blocked my secret scanner

A secret scanner needs test cases that look like secrets. My first push was rejected by GitHub’s push protection, because one test file contained a fake Stripe test key written out in full.

The fix is a pattern worth stealing: build fake secrets at runtime from pieces.

stripeKey := j("sk_", "live_", "51Hq8ZtLm2Vx9Rk4Pn7Wb3Jd")

Now the repo contains nothing that looks like a key to envguard, to GitHub, or to anyone scraping public code. And push protection checks every commit in a push, so fixing it in a later commit wasn’t enough. I had to amend the original.

The first real run

Before publishing, I ran envguard scan over my own projects. Three came back clean. One, a private production repo, had a committed .env and high-entropy values sitting in old test scripts.

My first thought was “it’s private, so it doesn’t matter”. It matters more than you’d think. Everyone with access has those keys, and so does every connected GitHub app, every clone and backup, and every AI agent that has read the files. One accidental “make public” exposes all of it, and bots scan new public repos for keys within minutes.

The fix isn’t rewriting history. Rotate the keys, and the old ones become harmless strings.

Try it

go install github.com/amahmood561/envguard@latest
cd your-repo
envguard install # adds the pre-commit hook
envguard scan . # check what's already there

If you hit a false positive, add envguard:allow to that line, or list the path in .envguardignore. Exit codes are 0 (clean), 1 (found) and 2 (error), so it works in CI unchanged.

Where it’s going

Today envguard guards git. Next it will guard the agent: a Claude Code hook that blocks the tool call the moment an agent tries to write a key to a file, before git ever sees it. After that come the other usual commit accidents: 200 MB files, leftover console.log calls, merge conflict markers, and database migrations that drop tables.

Code is on GitHub: github.com/amahmood561/envguard. Stars, issues, and the false positives it throws on your codebase are all welcome.

--

--