Static keys leak. The question isn't if but how fast you notice and how clean your response runbook is when the pager goes off.
A contractor pushed a .env file to a public fork at 2am. By the time our GuardDuty alert fired, someone in another timezone had spun up eight p4d.24xlarge instances to mine crypto. We caught it inside forty minutes and the bill was still four figures. That's the good outcome, and it's the one you plan for. Static credentials leak. Not because your team is careless, but because a long-lived string of characters that grants API access will eventually end up somewhere it shouldn't: a committed config, a debug log, a Slack paste, a stolen laptop, a CI variable printed by a well-meaning set -x.
If your security model assumes keys stay secret, your model is wrong. Assume the leak. Build for detection and fast response.
No single control catches everything, so stack them.
Pre-commit and pre-push. The cheapest place to stop a leak is before it lands. Run gitleaks in CI and, better, as a local hook.
# scan the whole history of a repo
gitleaks detect --source . --report-format json --report-path leaks.json
# scan only what's staged, as a pre-commit hook
gitleaks protect --staged --verbose
trufflehog goes a step further and actually verifies whether a found key is live, which cuts the noise from expired or fake test keys:
trufflehog git file://. --only-verified
Turn on GitHub push protection at the org level. It blocks pushes containing recognized secret patterns and has stopped more real leaks for us than any scanner we run ourselves, because it catches the human before the mistake is public.
Provider-side detection. AWS scans public code hosts for exposed access keys. When it finds one, it attaches the AWSCompromisedKeyQuarantine policy to the user, which denies the high-blast-radius actions (IAM changes, spinning up expensive instances) and emails the account root. Do not treat that email as spam. If you use a third party, GitGuardian watches public GitHub in near real time and will often ping you before your own scanners run.
Behavioral detection. Scanning finds keys that leaked through code. It does nothing for a key stolen off a laptop. For that you need to watch behavior. GuardDuty flags anomalies like calls from a new geography, GetCallerIdentity reconnaissance, or a burst of RunInstances. If you're rolling your own, CloudTrail plus a few Athena queries for "actions this principal has never called before" gets you most of the way.
When a key is confirmed leaked, work in this order. Speed matters more than elegance.
1. Disable first, ask questions later. Deactivating a key is reversible and instant. Deleting it destroys evidence. Do this before anything else:
aws iam update-access-key \
--user-name ci-deploy \
--access-key-id AKIAEXAMPLE12345 \
--status Inactive
Now the attacker is locked out and you still have the key on record for the investigation.
2. Assess blast radius. What did the key touch? Pull its recent activity from CloudTrail:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLE12345 \
--start-time 2026-07-13T00:00:00Z \
--query 'Events[].{Time:EventTime,Name:EventName,Source:EventSource}' \
--output table
Read it looking for two things: reconnaissance (ListBuckets, GetCallerIdentity, ListUsers) and impact (RunInstances, CreateUser, PutObject, GetObject on data buckets). If you see CreateAccessKey or CreateUser, the attacker made themselves persistence and disabling one key isn't enough. Widen the hunt.
3. Rotate. Issue a new key, deploy it to the legitimate consumer, verify the workload is healthy, then delete the old one. Order matters so you don't cause your own outage.
4. Root cause. How did it leak? A committed file means you also need to purge git history and assume it's public forever. A CI log means fix the logging, not just the key. Write it down in a postmortem while it's fresh.
5. Eliminate the credential type. This is the step most teams skip, and it's the only one that stops the incident from recurring. If a workload leaked a key, that workload probably shouldn't have had a static key at all.
Every step above is response work you're forced into because a long-lived secret existed. The durable fix is to stop having the secret.
Most things that hold static keys don't need them. CI runners, EC2 instances, EKS pods, Lambda, GitHub Actions, and workloads in other clouds can all get short-lived credentials through role assumption and OIDC federation instead. There's no key to scan for, nothing to rotate, and a stolen token expires in an hour whether you notice or not. We wrote up the mechanics in keyless authentication; pair it with tight least privilege so that even a live token can't do much.
Static keys don't disappear entirely. A legacy vendor integration, an on-prem box, a partner API: some corners keep them. For those, scope them to the minimum, put them in a secrets manager with rotation, and keep the detection layers running. But treat every remaining static key as a liability you're carrying deliberately, not a default.
Detection and a rehearsed runbook are table stakes: run gitleaks in CI, enable push protection, wire up CloudTrail anomaly alerts, and drill the disable-assess-rotate sequence until it's muscle memory. But don't let a smooth runbook become an excuse to keep the keys around. Every static credential you federate away is one you never have to detect, rotate, or explain in a postmortem. Spend your effort there. The best leaked-credential incident is the one that can't happen because there was no credential to leak.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
Explore more articles in this category
The edge is fast because it's constrained. This is the decision map for what belongs at the edge, what belongs at origin, and how compute, data, caching, and auth fit together.
Edge code runs in hundreds of PoPs, lives for milliseconds, and gives you no shell. Here's how we get logs, traces, and metrics out of it anyway.
Verifying signed tokens at the edge with WebCrypto blocks bad traffic early and saves a full origin hop. Here's the pattern we ship, and the traps.
Evergreen posts worth revisiting.