A single API key committed to a public GitHub repository can be copied by automated scanners before anyone notices. Deleting the line in a later commit does not remove it from the repository’s history, and the key may already have been used or shared elsewhere. This guide explains how leaks happen, how to respond, and how to reduce the risk with safer secret storage, least privilege, and automated scanning.
Key takeaways
- Treat any API key committed to a public repository as compromised, even if the commit was quickly deleted.
- Revoke or rotate the exposed credential first; removing it from Git history is a separate cleanup step.
- Keep secrets out of source code and use a secret manager or protected deployment settings to provide them at runtime.
- Limit each credential’s permissions, scope, and lifetime where the provider supports it.
- Add secret scanning and developer checks, but do not treat scanners as a replacement for sound handling practices.
How API keys get stolen from public repositories
Attackers and other automated services can scan public repositories for strings that resemble credentials. A newly pushed commit may be discoverable even if it stays public only briefly. A secret can also leak through a public fork, a copied patch, a build log, or a package that includes configuration files.
A common path to exposure looks like this:
- A developer pastes a key into application code or a configuration file.
- The key is committed and pushed to a public repository.
- A scanner finds the value in the current files or commit history.
- Someone tests whether the credential works and uses the access it grants.
The impact depends on the provider, the key’s permissions, and the systems it can reach. Possible consequences include unauthorized API requests, access to data, unexpected usage charges, or changes to services. Do not assume that an exposed key is harmless because it appears to be for development or has not produced an alert.
Prevent API key leaks on GitHub
Keep secrets out of the repository
Do not put credentials in source files, configuration committed to Git, issue comments, pull requests, or documentation. For local development, load secrets from a file that is excluded from version control or from a local secret-management tool. For deployed applications, use the hosting platform’s protected environment settings or a dedicated secret manager.
For example, a Node.js application can read a key supplied by its runtime environment:
const apiKey = process.env.PAYMENT_API_KEY;
if (!apiKey) {
throw new Error("PAYMENT_API_KEY is not configured");
}
// Pass apiKey to the provider's client without printing it to logs.
Configure PAYMENT_API_KEY in the environment where the application runs; do not replace it with a literal value in the source. Environment variables are a delivery mechanism, not a complete secret-management system: access to the running process or its configuration may expose them. Restrict who can view or change deployment settings, avoid dumping the process environment into logs, and prefer a secret manager when it fits your platform and operational needs.
For local .env files, add the filename to .gitignore before creating or adding the file. Check that it is not already tracked: ignoring a file does not remove it from Git if it has already been committed. Commit a safe template such as .env.example with variable names and non-sensitive placeholders, never live credentials.
Apply least privilege
Create separate credentials for development, staging, and production rather than sharing one key across environments. Grant each credential only the permissions and resources it needs. Where available, use restrictions such as allowed operations, source networks, application identifiers, or spending limits. Set expiration or rotation policies when supported, and remove credentials that are no longer used.
A narrowly scoped key can reduce the damage from exposure, but it does not make committing the key safe. Continue to treat every exposed credential as compromised.
Turn on automated scanning
Use GitHub secret-scanning protections available to your repository and organization, and enable push protection where it is supported and appropriate. These controls can detect recognized secret patterns or block some pushes, but coverage and availability vary. They may not recognize custom tokens, encoded values, or every provider’s credential format.
Add checks at multiple points in the development workflow:
- Scan proposed changes before they are merged.
- Run a secrets scanner in CI or as a repository check.
- Use pre-commit checks to catch accidental additions early.
- Review and act on alerts promptly; confirm whether a credential is valid with its provider rather than testing it against unrelated systems.
Keep scanning tools and their detection rules up to date. A clean scan is useful evidence, not proof that no secret exists in the repository or its history.
What to do if a key is exposed
Respond as though the credential is compromised. Prioritize invalidating its access; repository cleanup alone cannot prevent someone who already copied it from using it.
- Revoke or rotate the key with the provider. Follow the provider’s documented process. If rotation requires a transition period, create a replacement, update authorized applications, verify they work, and then disable the old key as quickly as practical.
- Check for unauthorized use. Review provider audit events, access logs, usage, and billing for the period the key may have been exposed. Preserve relevant evidence and follow your incident-response process.
- Find other copies. Search the current repository, commit history, branches, tags, pull requests, CI logs, deployment settings, and relevant forks or mirrors. Check whether the same credential was reused elsewhere.
- Remove the value from the working tree and prevent reintroduction. Replace committed configuration with runtime secret injection and add appropriate ignore rules and scanning checks.
- Consider history cleanup. Rewriting Git history can remove the value from the repository’s reachable commits, but it is disruptive: collaborators may need to re-clone or carefully resynchronize, and force-pushing rewritten history can overwrite others’ work. Coordinate with repository owners and contributors before doing it.
- Assume copies may remain. History rewriting cannot erase clones, screenshots, caches, forks, or copies already made by others. Keep the credential revoked even after cleanup, and follow GitHub’s current guidance for sensitive-data removal if the exposure requires it.
Deleting the file in a new commit is not sufficient: earlier commits can still contain the secret. Conversely, rewriting history is not a substitute for revocation. Perform both actions when appropriate, in that order of urgency.
A practical prevention workflow for teams
Use this checklist when setting up a repository or improving an existing project:
- Inventory credentials. Identify which services issue keys, where each key is used, who can access it, and what permissions it has.
- Choose a secure delivery method. Use a managed secret store or protected deployment configuration for each environment. Keep local development credentials outside tracked files.
- Separate and scope credentials. Use distinct keys by environment and service, and remove permissions that the application does not need.
- Protect the development workflow. Add
.envand other local secret files to.gitignore; provide a non-secret example configuration; enable pre-commit and repository scanning. - Protect CI/CD. Store deployment secrets in the CI platform’s protected settings, limit access to trusted workflows, and avoid exposing secrets to untrusted pull-request code. Review who can edit workflow files and change secret settings.
- Plan for rotation and incident response. Document how to revoke, replace, test, and remove a key. Make sure someone can act on provider alerts and repository scanning alerts.
- Review regularly. Remove unused credentials, confirm permissions remain appropriate, and check that scanning and alert workflows still run.
For a quick look at how a URL is structured, TidyURLs offers a URL parser. Parsing a URL can help inspect its components, but it does not secure API credentials or replace server-side access controls.
Common mistakes
- Relying on
.gitignorealone. It prevents some untracked files from being added, but it does not untrack a file already committed or remove old commits. - Deleting the exposed line and stopping there. The key remains usable until it is revoked or otherwise invalidated.
- Assuming a private repository is a secret vault. Repository access can change, and secrets may spread through forks, logs, or integrations. Store credentials in a system designed to control secret access.
- Putting secrets in client-side code. Values bundled into browser JavaScript or mobile applications can be extracted by users. Keep privileged credentials on a server and expose only appropriately limited functionality to clients.
- Printing credentials for debugging. Logs, build output, and error reports can become additional leak locations. Redact sensitive values and avoid logging request headers or full environment dumps.
- Trusting a scanner to catch every secret. Scanners have detection limits and can miss custom or transformed values. Combine automated checks with careful design and review.
- Rewriting history before rotating the key. A history rewrite does not invalidate copies already taken. Revoke or rotate first.
Conclusion
Preventing API key leaks on GitHub starts with keeping credentials out of tracked files and supplying them through controlled runtime configuration. Least privilege limits potential impact, while scanning and protected development workflows help catch mistakes. If a key reaches a public repository, revoke or rotate it promptly, investigate for misuse, and then clean up the repository and its history as appropriate.
