I've been running a small fleet of AI agents on Raspberry Pis for about a year, doing real work in my one-person business: triaging support email, generating reports, and occasionally writing and deploying code for small side projects. The setup worked well enough that I got careless. In July, one of those agents pushed a live API key to a public GitHub repository — and I didn't find out for four days.
This is the honest post-mortem: what the agent did, why it was really my fault, and the exact checks I now run so it can't happen again.
What the agent was asked to do
The task sounded boring, which is usually when things go wrong. I wanted a tiny service that emails me a daily summary of new signups. I told the agent to "wire up the email provider, write the config, and push it to GitHub so I can pull it on my other machines."
What I didn't say: use environment variable references, keep the repo private, and never write secret values into files that get committed. I assumed those things went without saying. That assumption is the whole story, really.
What it actually did
The agent did a perfectly competent job. It scaffolded a small Python service, wrote a config.yaml, created the GitHub repo, and pushed. The problem was how it handled the credential. The API key lived in a .env file on the box. When the agent wrote the config, it didn't reference the variable — it read the value and embedded it:
email:
provider: "postmark"
api_key: "sk_live_9f2e..." # the actual, live key
Then it committed config.yaml — and, being thorough, also committed the .env — and pushed everything to a brand-new repo. My default for new repos is private, but the agent created this one public, because I'd said "so I can pull it on my other machines" and it interpreted that as "make it maximally accessible."
So: two copies of a live production key, in a public repo, with a helpful README explaining exactly what the project does.
Four days of silence
Nothing dramatic happened. No spike in usage, no strange emails, no alert from my monitoring. The key just sat there, indexed and searchable, for four days. I didn't notice because the agent's report said "deployed successfully, tests passing" — which was true.
I found out because my email provider's own secret-leak detection spotted the key on GitHub, automatically revoked it, and emailed me about it. Their scanner caught it before any human did. If they hadn't been running that system, I genuinely don't know how long it would have stayed up.
The damage assessment took an evening: pull the usage logs, check every API call made with that key, rotate everything adjacent to it. Total direct cost: zero. Nobody abused the key. But that's luck, not security posture. Anyone who'd found that key could have sent email as me (phishing from my own domain), burned through my quota, or used the .env to map out what else I run.
What I actually got wrong
The tempting story is "my AI agent leaked my API key." That's not what happened. The agent did exactly what the environment allowed. Every failure on this list is mine:
I gave it raw secrets. The agent had read access to a .env file containing live production keys. It never needed the values — it needed the names.
I didn't scope the blast radius. That key could do everything my account could do. There was no send-only, single-purpose key for this one project.
I shipped without a scan. No pre-commit hook, no secret scanner, no review step between "the agent did a thing" and "the thing is on the public internet."
I trusted a success report. "Pushed successfully" is not the same as "pushed safely." My monitoring told me the action happened, not that the action was sane.
When you run agents, you can't outsource judgment. You outsource execution. The guardrails stay your job.
What I changed
Five concrete changes, which together took under an hour:
1. Agents get variable names, never values
Every agent prompt I write now includes a hard rule:
You must never write secret values into files, logs, commit messages, or output. If a secret is needed, reference the environment variable name (e.g.,
EMAIL_API_KEY) and assume it will be injected at runtime. If you cannot complete the task without a literal secret, stop and report that instead.
The config now reads api_key: ${EMAIL_API_KEY} and that's it.
2. gitleaks runs before every commit
On every machine the agents can push from:
# install
brew install gitleaks # or: go install github.com/gitleaks/gitleaks@latest
# in each repo, as a pre-commit hook
gitleaks git --pre-commit --redact -v
I also ran gitleaks detect over the full history of every repo the agents touch — because finding out you have a problem is step one, and I wanted the full picture before I relaxed again.
3. GitHub secret scanning + push protection on everything
This one embarrassed me: secret scanning was only enabled on the repos I remembered to enable it on. Now it's on for every repo, with push protection, which blocks a push containing a known secret pattern before it ever lands. This is the safety net that caught my leak — I just wish it had been the thing that prevented it instead.
4. One scoped key per agent
Every agent now gets its own API key with the minimum permissions it needs. When a key leaks — not if — the blast radius is one capability on one project, not the whole account. Rotation becomes a two-minute chore instead of an incident.
5. A weekly leak sweep
A cron job on the Pi scans every repo I own every Sunday night:
#!/usr/bin/env bash
cd ~/repos || exit 1
for d in */; do
(cd "$d" && gitleaks detect --redact --no-banner \
--report-path ~/scan-reports/"${d%/}"-$(date +%F).json)
done
If anything shows up, I get an email before breakfast. Boring, cheap, and it turns "I wonder if I've leaked something" into a question with a weekly answer.
The real lesson
The uncomfortable truth about running AI agents is that they amplify whatever process you give them — including the gaps in it. An agent inside good guardrails is a tireless junior engineer. An agent inside no guardrails is a very fast way to make a very public mistake.
I don't trust my agents less than I did before this. I just trust the system around them more, because now there actually is one.
I've turned all of this into a repeatable pre-launch routine: the checklist I run before anything my agents build touches the public internet, plus the scripts (the gitleaks config, the weekly sweep, the key-rotation runbook). The full checklist + scripts are in Ship Safe — The Launch-Day Security Kit — code LAUNCH90 at checkout makes it $1.50. Enter the code on the checkout page itself (it isn't applied via URL). If it doesn't catch at least one thing before your next launch, reply to the receipt for a refund.
Top comments (0)