API Keys in Plain Sight: The Security Debt Every Developer Is Carrying
The pattern that appears in every security audit
Every security audit of a web application eventually arrives at the same moment: whether it’s a booking engine for a dental clinic or an inventory system for a local retail chain, a .env file, a configuration file, or a source code comment containing a production API key, a database connection string with credentials, or a private key that controls access to infrastructure.
The developer who put it there is rarely careless. They were moving fast, following a tutorial that used hardcoded values for simplicity, or inheriting code from someone else who made the same decision. The problem is not individual negligence. It is that the standard development workflow (especially for solo developers and small teams building solutions for restaurants or driving schools) makes credential exposure the path of least resistance.
Tutorials don’t model secrets management. Framework starter kits ship with configuration examples that use real-looking placeholder values. .gitignore templates frequently omit .env from the default list. The result is a significant portion of the developer community that learned to build real applications without learning how secrets should be handled in them.
How GitHub scans find what developers miss
GitHub’s secret scanning (which automatically alerts when it detects credential patterns in repositories) reported over 12 million secrets exposed on the platform in 2023 alone. These include API keys for major services, database credentials, private SSH keys, and service account tokens. The majority were committed to public repositories, but a significant portion were from repositories that had been public before being made private, the history was already indexed.
The scan patterns are well-known: AWS keys start with AKIA, Stripe live keys start with sk_live_, OpenAI keys start with sk-. Automated bots that specifically target these patterns operate continuously across every major git hosting platform. The window between an accidental push and the first automated test of those credentials is measured in seconds, not hours.
GitGuardian’s 2024 State of Secrets Sprawl report documented that 90% of exposed secrets remain active more than 5 days after exposure, meaning the developers responsible either don’t know the keys are exposed or don’t know how to revoke them correctly.
The specific failure modes
Hardcoded credentials in source code are the most visible failure because they travel with the code everywhere it goes. A key hardcoded in a function runs correctly in development, gets committed to version control, gets pushed to GitHub, gets deployed to a CI/CD system, and may end up in a container image that gets pushed to a public registry. At each step, the key is readable to anyone with access at that level. That’s often more people (and more automated systems) than the developer realized.
The .env file committed by accident is the most common failure. The .env file is the standard solution for keeping secrets out of source code. It works unless it gets committed. The scenarios where this happens: the developer initializes a new git repository, runs git add . before setting up .gitignore, and pushes. The .env file is in the initial commit. Even if caught immediately, the public repository’s history contains the credentials, and history rewrites require non-trivial git operations that many developers don’t know how to perform.
Client-side JavaScript is the failure mode that’s genuinely invisible. Any key embedded in JavaScript that runs in the browser is readable by anyone who opens the browser’s developer tools and inspects the source. This includes API keys passed directly from frontend code to third-party services. The correct architecture is a backend proxy that holds the key server-side and makes authenticated calls on behalf of the client. The incorrect architecture (which ships constantly) passes the key directly from the frontend.
What the remediation actually looks like
When a production key is found in a public repository, the response sequence matters:
First, revoke the key immediately through the service’s credential management dashboard. Generating a replacement is step two, not step one. A replacement key in a new location doesn’t help if the old key is still active and in an attacker’s hands.
Second, audit what the key had access to. Most services log API activity. Check the logs for the exposure period for any requests that weren’t made by your systems. AWS CloudTrail, Stripe’s logs, Twilio’s dashboard, all of these provide activity records that can confirm whether the credentials were used by someone else.
Third, clean the git history. A commit that says “removed API key” does not remove it from the repository history, the key is still visible in every previous commit. Tools like BFG Repo-Cleaner can rewrite history to remove specific strings, but this requires a force-push and coordination with anyone who has cloned the repository.
The infrastructure that prevents the next one
OWASP’s Secrets Management Cheat Sheet defines the baseline: secrets never in source code, secrets injected at runtime via environment variables or a secrets manager, access to secrets controlled by role and environment, and rotation on a schedule that doesn’t depend on manual intervention.
For most web applications, the minimum viable implementation is:
- A
.env.examplefile with placeholder values checked into source control - A
.envfile with real values explicitly excluded from source control via.gitignore - Environment variables injected by the deployment platform (Vercel, Netlify, Railway, AWS) rather than carried in files
- Secret scanning enabled in the repository settings
This prevents the most common failure modes without requiring dedicated secrets management infrastructure. The x078 standard for any application handling credentials includes technical consulting: automated secret scanning in CI/CD and environment-specific access controls before any production deployment.
The question every team should be able to answer: if a developer laptop was stolen today, what credentials would need to be revoked? If the answer requires investigation to find out, the secrets infrastructure has a gap.
Frequently Asked Questions
How do API keys get exposed in the first place?
The most common paths: hardcoded into source code that gets pushed to a public GitHub repository, stored in a .env file that gets accidentally committed, included in client-side JavaScript that anyone can read in the browser, left in error messages that display in production, and logged by default logging libraries. Each of these is a different failure mode requiring a different mitigation.
How quickly are exposed API keys found by attackers?
GitGuardian's 2024 data shows that exposed secrets are detected by automated scanners within seconds of being pushed to a public repository. Attackers operate continuous bots that monitor GitHub, GitLab, and Bitbucket for credential patterns. An AWS key exposed at 2pm on a Saturday will be found before end of business. The assumption that 'no one will find it' has a measured failure rate approaching 100% within 24 hours.
What is a secrets manager and do I actually need one?
A secrets manager is a dedicated service (AWS Secrets Manager, HashiCorp Vault, Doppler) that stores credentials encrypted, controls access by role and environment, rotates secrets automatically, and provides an audit log of every access event. For a solo developer with one project: a properly configured .env file that never gets committed and a password manager for backup is sufficient. For anything with a team, multiple environments, or compliance requirements: a secrets manager is not optional infrastructure.
What should I do if I find an exposed key in my repository history?
Assume the key has been compromised regardless of how long it was there. Revoke the key immediately through the issuing service's dashboard. Generate a new key and deploy it through secure channels. Then remove it from git history using git filter-branch or BFG Repo-Cleaner, pushing a revocation commit is not enough because the history is still publicly visible. Enable GitHub's secret scanning alerts to prevent recurrence.
[ RELATED_NODES ]
> START_PROJECT
Need a website that earns trust, ranks in search, and gives your business a stronger digital presence? Start the conversation here.