← All posts

You Leaked an API Key. Now What?

You Leaked an API Key. Now What?

You just found your AWS credentials in a public commit. Or your Stripe key in a Slack channel. Or your database password in CI logs that anyone on the team can read.

Your heart rate just spiked. Good—that means you understand the stakes.

Here's exactly what to do, in order.

Step 1: Rotate the Secret Immediately

Don't clean up the leak first. Don't investigate how it happened. Don't schedule a meeting.

Rotate the secret now.

The moment a credential is exposed, assume it's compromised. Bots scrape GitHub for secrets within minutes. Attackers have automated pipelines that test leaked credentials faster than you can open your cloud console.

# AWS: Deactivate the exposed key
aws iam update-access-key --access-key-id AKIA... --status Inactive --user-name your-user

# Create a new key
aws iam create-access-key --user-name your-user

For third-party services:

  • Stripe: Dashboard → Developers → API keys → Roll keys
  • Twilio: Console → Account → API keys → Create new key, delete old
  • SendGrid: Settings → API Keys → Create & delete
  • Database: Change password, update connection strings

Yes, this will break things. That's better than the alternative.

Step 2: Assess the Exposure Window

Now figure out how bad it is:

  1. When was the secret exposed? Check git history, message timestamps, or log retention.
  2. Where was it exposed? Public repo? Private Slack? CI logs?
  3. Who had access? Everyone on the internet, or just your team?
# Find when a secret was first committed
git log --all --full-history -p | grep -B 20 "AKIA"

# Check if the commit was ever pushed to remote
git branch -r --contains <commit-hash>

If the exposure was public (GitHub, GitLab public repo, public logs), assume malicious access. If it was internal-only, you have more options—but still rotate.

Step 3: Check for Unauthorized Access

Most services provide audit logs. Use them.

AWS CloudTrail:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIA... \
  --start-time 2026-01-01

What to look for:

  • API calls you didn't make
  • Access from unfamiliar IP addresses or regions
  • Resource creation (EC2 instances, S3 buckets, IAM users)
  • Data access patterns that don't match your app

If you see anything suspicious, escalate. This is now an incident response situation, not just a cleanup task.

Step 4: Clean Up the Leak Source

Now that the immediate threat is contained, remove the exposed secret.

If It's in Git History

Deleting the file doesn't remove it from history. You need to rewrite history:

# Install BFG Repo-Cleaner (faster than git filter-branch)
brew install bfg

# Remove the secret from all commits
bfg --replace-text passwords.txt repo.git

# passwords.txt contains:
# your-leaked-secret==>***REMOVED***

# Clean up and force push
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force

Important: Force-pushing rewrites history for everyone. Coordinate with your team first.

After force-pushing:

  • GitHub/GitLab may cache old commits. Contact support to purge.
  • Forks retain the original history. You can't fix those.
  • CI systems may have cached the secret. Clear build caches.

If It's in Slack/Chat

Delete the message if possible. But assume anyone with access to that channel has seen it—Slack search is too good.

If It's in Logs

Most logging services support log deletion or redaction:

  • Datadog: Log archives can be deleted
  • CloudWatch: Delete log streams
  • Application logs: Rotate and delete affected files

Step 5: Understand How It Happened

Don't skip this. If you don't understand the root cause, you'll leak secrets again.

Common causes:

| Leak Vector | How It Happens | |-------------|----------------| | Git commit | .env not in .gitignore, or file moved to new path | | CI logs | Secret printed in debug output or test failure | | Error tracking | Exception includes environment variables | | Slack/chat | Developer shares "quick" during debugging | | Screenshots | Terminal visible in screen share or recording |

Ask yourself:

  • Why was the secret accessible to be leaked?
  • What process failed that should have caught this?
  • How can the workflow change so this can't happen?

Step 6: Prevent It From Happening Again

Here's the uncomfortable truth: if your secrets workflow depends on humans not making mistakes, you'll leak secrets again. It's just a matter of time.

The fix is removing secrets from places where they can be accidentally exposed:

Stop Committing .env Files

Instead of maintaining .env files manually, pull them from a secrets manager:

keyenv pull           # Fetch secrets from encrypted store
keyenv run -- npm start   # Inject at runtime, never write to disk

No file to commit. No file to accidentally share. No file sitting in your project folder waiting to be exposed.

Stop Sharing Secrets Over Chat

When onboarding a new developer, don't paste credentials in Slack:

# Invite them to the team
keyenv team invite team_abc123 [email protected] member

# They pull secrets themselves
keyenv pull

Now there's an audit trail, and you can revoke access when they leave.

Add Pre-Commit Hooks

Catch secrets before they're committed:

# Using detect-secrets
pip install detect-secrets
detect-secrets scan > .secrets.baseline
detect-secrets-hook --baseline .secrets.baseline

Or use GitHub's secret scanning, GitGuardian, or similar tools.

Audit CI/CD Pipelines

Secrets in CI are especially risky because logs are often accessible to the whole team:

# Bad: Secret might appear in logs
- run: echo "Deploying with key $API_KEY"

# Better: Use masked variables
- run: deploy --key "$API_KEY"
  env:
    API_KEY: ${{ secrets.API_KEY }}

Better yet, use service tokens that can't be extracted from CI logs at all.

The Real Cost of a Leaked Secret

When teams brush off secret leaks with "we rotated it, we're fine," they're missing the bigger picture:

  • Direct costs: Unauthorized resource usage, fraudulent transactions
  • Incident response: Hours of engineering time investigating and cleaning up
  • Trust damage: Customers don't care that you rotated quickly
  • Compliance: Many frameworks require incident documentation and notification
  • Repeat risk: If your process allows one leak, it allows more

A single leaked AWS key has cost companies tens of thousands of dollars in crypto mining charges. A leaked Stripe key can enable fraud that takes months to unwind. A leaked database credential can mean a breach notification to every user.

TL;DR: The Playbook

  1. Rotate immediately—assume compromise
  2. Assess exposure—when, where, who
  3. Check audit logs—look for unauthorized access
  4. Clean up the source—rewrite git history if needed
  5. Understand root cause—why did this happen?
  6. Fix the workflow—make it impossible, not just unlikely

The best incident response is the one you never need. Stop managing secrets manually, and you stop having secrets to leak.

curl -fsSL https://keyenv.dev/install.sh | sh
keyenv init
keyenv pull

One command to sync. Zero files to accidentally commit. That's the fix.