5 minutes. That’s how long it took.
A security researcher publishes an AWS access key on a public GitHub repository. They do it on purpose, as an experiment.
Five minutes later, someone was already using it to mine cryptocurrency.
Five. Minutes.
There are bots scanning GitHub 24/7 looking for exactly that: exposed credentials. And they’re fast. Much faster than you realizing you screwed up.
The numbers are scary
According to GitHub, 39 million secrets were leaked in public repositories in 2024. A 67% increase from the previous year.
GitGuardian, which specializes in scanning exactly this, found 23.7 million new secrets just in public repos. And the worst part: 70% of secrets detected in 2022 were still active in 2024.
Two years later. Still working. Waiting for someone to use them.
It’s not just random people
Toyota had AWS credentials exposed on GitHub that gave access to their vehicle telematics system. Pearson lost data because someone left a GitLab token in a configuration file. Otelier, a hospitality company, watched as 8TB of S3 data was exfiltrated due to credentials exposed on Bitbucket.
This doesn’t just happen to the intern. It happens to Fortune 500 companies.
The classic: “It’s just my personal project”
Yeah, right.
The problem is that personal project has the same OpenAI API key you use in production. Or your Telegram bot token. Or your staging database credentials that, oh surprise, has real data because “it’s easier to test that way”.
And one day you do git push without thinking. Or you change the repo from private to public because you want to show it to someone. Or GitHub has a bug and temporarily exposes private repos (it’s happened).
And then you discover your AWS bill went from $20 to $2,000. In one night.
“But I deleted it immediately”
Another classic.
Git is a version control system. Its literal job is to remember everything that happened. Deleting the commit doesn’t delete the secret from history. Force pushing doesn’t delete it from forks. And it definitely doesn’t delete it from bots that already copied it.
Once a secret touches a public repo, it’s compromised. Period. It has to be rotated.
The pyramid of disaster
This is how secret management typically evolves in a typical project:
Level 1: Hell
| |
Directly in the code. Committed. In production. Don’t laugh, this exists.
Level 2: Purgatory
| |
Better, but that .env ends up in a backup, in a zip you send via Slack, on a hard drive you sell on Craigslist…
Level 3: Limbo
| |
Okay, but where do you store them? On a sticky note? In a secrets.txt file on the desktop? In a Slack message to yourself?
The solution: secrets outside code, outside disk
After seeing how I almost screwed up a few times, I decided to centralize everything in 1Password and use its CLI to inject secrets when needed.
The concept is simple:
- Secrets live in 1Password, not on my disk
- Code has references to secrets, not the secrets themselves
- Secrets are injected at runtime
The .env.template + op inject pattern
In each project, instead of a .env with real values, I have a .env.template with references:
| |
When I need the real .env, I run:
| |
1Password reads the op:// references, resolves them with real values, and generates the file. The .env.local is in .gitignore, it never touches git.
What do I gain from this?
1. One place for all secrets
Before, I had credentials scattered across .env files in 15 projects, environment variables in .bashrc, tokens in Slack messages, and a few on sticky notes (don’t judge me).
Now everything is in a 1Password vault. One place. Encrypted. With change history.
2. Trivial secret rotation
Before: changing an API key meant searching for it in all projects, updating files, praying I didn’t forget any.
Now: update the value in 1Password, run op inject in each project, done.
3. Code documents what it needs
The .env.template is living documentation. Anyone who clones the project knows exactly what secrets they need. They just need to have them in their own 1Password (or ask you for them).
4. Impossible to commit secrets by accident
The file that goes to git only has op:// references. Even if you do git add . without thinking, you’re not exposing anything.
5. Free synchronization between machines
New Mac? Install 1Password, log in, op inject. All your secrets available without copying files or sending anything via Slack.
For daily use: lazy loading in Fish
For commands that need credentials (like oco for AI commits), I have this in my Fish config:
| |
The first time I run oco, it asks for Touch ID. From then on, the variable is loaded in the session.
The only downside
Yes, there’s one: you have to authorize with Touch ID or password from time to time.
I’ve optimized it by configuring 1Password to remember authorizations for 24 hours and only lock when the Mac sleeps. But yes, occasionally you have to touch the sensor.
It’s a small price to pay for not appearing in GitGuardian statistics.
It’s not optional
Look, I understand this all sounds like paranoia. “It won’t happen to me.” “It’s just a small project.” “I don’t have anything important.”
But think about this: do you have any paid API keys? OpenAI? AWS? Any service that charges per usage?
Then you have something someone can exploit.
And bots don’t rest. They don’t distinguish between a startup’s project and a student’s programming homework. They scan everything, try everything, exploit everything.
39 million secrets leaked in 2024. 70% of those from 2022 are still active.
Proper secret management isn’t a best practice. It’s basic hygiene.
Like washing your hands. You don’t do it because it’s fun. You do it because the alternative is catching an infection.
Your secrets in git are an infection waiting to happen.
Executable summary:
- Install 1Password and its CLI (
brew install 1password-cli) - Create a vault for development
- Migrate your secrets from
.envfiles to the vault - Create
.env.templatewithop://references - Add
.env.localto.gitignore - Regenerate with
op injectwhen needed
That’s it. Thirty minutes of setup that saves you from appearing in the next GitGuardian report.
Your AWS credentials will thank you.
Update: After implementing all this, I discovered that 1Password was asking for Touch ID too much. So much that I started approving without looking. That has a name in security and it’s not good. Read When security asks for permission so often you stop reading to see how we solved it.