I recently audited a friend's startup codebase. They had 23 API keys in .env files. Three of those keys had been committed to git history. One was a production Stripe key.
This is more common than you think. Let's talk about what to do instead.
The Problem With .env Files
.env files are convenient but dangerous:
-
They get committed — even with
.gitignore, someone eventually runsgit add . - They're shared insecurely — Slack messages, emails, shared drives
- They're not encrypted — plaintext on every developer's machine
- They don't rotate — most teams never change API keys until they leak
A 2024 GitGuardian report found 12.8 million new secrets exposed in public GitHub repos. That's 35,000 per day.
Level 1: Secret Scanning (Free, 5 Minutes)
Before fixing your key management, find what's already leaked:
# GitHub's built-in secret scanning (free for public repos)
# Go to repo Settings → Code security → Secret scanning
# Or use gitleaks locally:
brew install gitleaks
gitleaks detect --source . --verbose
Trufflehog scans git history too:
trufflehog git file://. --only-verified
Level 2: Secrets Manager (Free Tier Available)
Move secrets out of files into a proper secrets manager:
AWS Secrets Manager (free for first 30 days):
import boto3
client = boto3.client('secretsmanager')
secret = client.get_secret_value(SecretId='prod/stripe/api-key')
stripe_key = secret['SecretString']
HashiCorp Vault (free, self-hosted):
import hvac
client = hvac.Client(url='http://vault:8200', token='...')
secret = client.secrets.kv.v2.read_secret_version(path='stripe')
stripe_key = secret['data']['data']['api_key']
1Password CLI (great for small teams):
# In your script or CI:
export STRIPE_KEY=$(op read "op://Production/Stripe/api-key")
Level 3: Short-Lived Tokens
The best API key is one that expires in minutes:
# Instead of static API keys, use OAuth2 client credentials
import requests
token_response = requests.post('https://api.example.com/oauth/token', data={
'grant_type': 'client_credentials',
'client_id': os.environ['CLIENT_ID'], # Only 2 secrets to manage
'client_secret': os.environ['CLIENT_SECRET'],
})
access_token = token_response.json()['access_token']
# This token expires in 1 hour — even if leaked, damage is limited
Level 4: Workload Identity (Zero Secrets)
The ultimate goal — no secrets at all:
# GitHub Actions — no API keys needed for AWS
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/deploy
aws-region: us-east-1
# Uses OIDC federation — GitHub proves who you are to AWS
Quick Wins You Can Do Today
-
Run
gitleaks detecton your repo right now - Enable GitHub secret scanning (Settings → Code security)
- Rotate any key that's ever been in git history — assume it's compromised
-
Use
.env.examplewith placeholder values, never real keys
What's Your Setup?
How does your team manage API keys? Still using .env files? Secrets manager? Something else?
I'm genuinely curious — I think most teams are still at Level 1 (or Level 0: raw .env files everywhere).
I write about API security and developer tools. My latest: npm typosquat scanner and PyPI typosquat scanner.
Top comments (0)