.env File Exposed? How a Leaked API Key Gets Found and Abused (2026)

Written by the Rafter Team

A .env file that reaches a public GitHub repository, a Docker image, or a build log is not a hypothetical risk — any real API key inside it should be treated as compromised the moment it left your machine. Automated scanners built by both attackers and security researchers crawl public commits continuously, and documented honeypot research puts the window between a secret hitting GitHub and someone testing it at one to five minutes. Deleting the file or the commit does nothing to change that; the fix is to revoke the credential at the provider first and clean up the repository second.
This post is about the part that happens before rotation: how these keys actually get found, what the prefix on your key tells an attacker before they even run it, and how to stop the next .env from ever reaching a place a scanner can see it. If you already know a key is exposed right now and need the rotation steps, go straight to the emergency response runbook — this article goes deeper on detection and prevention instead of repeating that checklist.
If you landed here because a real OPENAI_API_KEY, STRIPE_SECRET_KEY, or similar value is sitting in a public repo, build log, or Docker image right now, stop reading and revoke it at the provider first. Everything below is still true in ten minutes.
The Quick Verdict
Whether a leaked .env is a real incident depends less on where you found it and more on whether the key was ever reachable outside your machine, even briefly. Being in .gitignore today does not undo history.
| Situation | Key status | What to do |
|---|---|---|
.env was committed to a public repo, even once, even in an old commit | Compromised | Revoke now, then rotate |
.env was only ever in a private repo | Assume compromised | Rotate; audit who had repo, CI, and fork access |
.env was never committed but got COPY'd into a Docker image pushed to a registry | Compromised | Revoke now, treat the image as public |
| Key appeared in CI/CD build logs (echoed during a debug step) | Compromised | Revoke now; scrub log retention |
.env is .gitignore'd, never committed, and loaded only from a secrets manager at runtime | Not exposed via this vector | Still worth a one-time repo-history scan |
Only the last row is actually safe, and only for this specific vector — a .gitignore entry protects future commits, not past ones and not other leak paths like logs, images, or public storage buckets.
How Fast Do Bots Find a Leaked Key in a Public Repo?
Fast enough that "I'll rotate it after lunch" is already too slow. GitGuardian and Truffle Security both run honeypot experiments — deliberately leaked, monitored credentials — and consistently observe automated probing within one to five minutes of a secret appearing in a public commit.
The mechanics are simple. GitHub's event firehose publishes every public push in near real time, and attackers (along with researchers, and GitHub's own partner scanning program) subscribe to it and grep new diffs for known key patterns. A regex match on sk-proj-[A-Za-z0-9]{20,} or AKIA[0-9A-Z]{16} doesn't require a human to notice your commit — it requires a script that already exists and is already running, watching every public repo on the platform simultaneously.
Private repos change the odds, not the outcome. There is no equivalent public firehose for private pushes, so the "found in minutes by a stranger's bot" scenario mostly doesn't apply. But a private repo still has collaborators, CI runners, third-party integrations, and forks from before a permission change, any of which can carry the secret out. Treat a private-repo leak as slower to discover, not safe.
My .env Is in .gitignore — Am I Safe?
Only for commits made after you added it. A .gitignore entry tells git to stop tracking new changes to a path; it does nothing to files that were already committed before the rule existed.
This is the single most common false sense of security in .env incidents. A developer adds .env to .gitignore on day 30 of a project, not realizing the file was committed on day 2 and has been sitting in git history — and in every clone and fork made since — the whole time.
Check for this directly instead of assuming:
# Was .env ever committed, even if it's gitignored now?
git log --all --full-history -- .env
# Does the actual secret value appear anywhere in history?
git log --all -p | grep -n "sk-proj-\|sk_live_\|AKIA"
If either command returns a hit, the key is exposed regardless of what your current .gitignore says, and it needs to be rotated the same way a fresh leak would.
What Do sk-proj-, sk-or-v1, sk_live, ghp_, and AKIA Actually Mean?
Every major API provider prefixes its keys so that both scanners and attackers can identify the provider — and often the environment — without making a single API call. That prefix is the first thing a scanner's regex matches on, and the first thing an attacker reads before deciding whether a key is worth their time.
| Prefix | Provider | What it typically grants |
|---|---|---|
sk-proj- | OpenAI, project-scoped key | API access limited to one project's models and usage, billed to that project |
sk- (no proj) | OpenAI, legacy org-wide key | Access across the whole organization, no project boundary |
sk-or-v1- | OpenRouter | Routes calls to whichever underlying model provider is configured, billed against your OpenRouter credit balance |
sk_live_ | Stripe, live mode | Real charges, refunds, and customer payment data |
sk_test_ | Stripe, test mode | No real money, but exposes API structure and often signals a live key is stored nearby |
ghp_ | GitHub, classic personal access token | Whatever scopes were granted at creation — often broader than needed |
github_pat_ | GitHub, fine-grained PAT | Access scoped to specific repos and permissions, narrower by design |
AKIA... | AWS, IAM access key | Whatever the attached IAM user or role can do — compute, storage, billing, all of it |
The OpenRouter case is worth calling out specifically because it's easy to under-rate. An OPENROUTER_API_KEY=sk-or-v1-... value doesn't just expose one provider's models — it exposes a routing layer sitting in front of whichever models you've configured, meaning a single leaked key can generate usage against several backend providers at once, all billed to your OpenRouter balance. For the OpenAI-specific version of this problem, including why sk-proj- keys have no built-in expiration, see OpenAI API Key Exposure: Risks, Recovery, and Prevention; for the Stripe case, see Stripe API Key Security.
The Three Ways a .env File Actually Gets Out
1. Committed to git, then history never cleaned
The most common path: .env is created early, before a .gitignore rule exists, gets committed, and the rule added later only stops future tracking. Every subsequent clone, fork, and CI checkout still carries the original commit.
2. Baked into a container image
A Dockerfile with COPY . . before a .dockerignore excludes .env copies the file straight into the image layer. Push that image to a public registry, or even a private one with broad read access, and the key ships with every pull — indistinguishable, from the attacker's side, from a key committed to git.
3. Echoed into build or deploy logs
CI systems that print environment variables during a debug step, or an application that logs its own startup configuration "for troubleshooting," write the secret into log storage that often has looser access controls and longer retention than the source repo itself. A key that never touched git can still leak through a log line.
All three paths end at the same outcome: a key sitting somewhere a scanner, a curious collaborator, or an attacker can read it, with no cryptographic difference from a key posted on a public forum.
Does Rotating the Key Fix It?
Rotation fixes the exposure going forward; it does not undo whatever happened during the exposure window. Generating a new key and revoking the old one closes the door, but it doesn't tell you whether anyone walked through it first.
That's why the emergency response sequence is revoke, then investigate, then remediate — not revoke and move on. Provider usage logs (OpenAI's usage dashboard, Stripe's event log, AWS CloudTrail, GitHub's security log) are the only way to know whether the leaked key was actually used before you rotated it, and for how long it was live. The full rotation and audit runbook walks through exactly what to check for each provider and how to remove the secret from git history without leaving it in forks or caches.
Prevention: Stopping the Next .env Before It Ships
The cheapest place to catch a secret is before it's committed, not after. A pre-commit hook that scans staged changes for known key patterns blocks the leak at the exact point it would otherwise become permanent history — see Pre-Commit Hooks for Secret Detection for the setup.
CI-stage scanning is the second layer, catching anything a local hook missed or a contributor bypassed. Comparing the common open-source options — Gitleaks, TruffleHog, and newer entrants — comes down to detection rules and verification behavior more than raw speed; see Secret Scanning Tools Compared if you're choosing one for a CI pipeline.
For GitHub specifically, native secret scanning and push protection catch a wide set of known token formats and can block a push before it lands, but coverage has real gaps around custom formats and private-repo defaults. GitHub Secret Scanning: What It Catches and Misses breaks down where that native protection stops and a dedicated scanner needs to pick up the rest.
The container and log paths need their own controls: a .dockerignore that excludes .env and any credential files, and a CI configuration that never echoes environment variables in debug output. None of these controls are exotic — they're a .gitignore entry, a pre-commit hook, a .dockerignore line, and a CI check that runs on every pull request, not just at release time.
That last point matters more now that a growing share of commits are written by AI coding agents rather than humans — an agent that pastes a working key into a .env example or a debug log doesn't pause to second-guess itself the way a person might. This is the gap Rafter is built for: it runs as a security review in CI, on the pull request, doing secret scanning alongside static analysis and software composition analysis before code merges — security for a world where the code, and increasingly the .env file next to it, might not have been written by a human at all.
Frequently Asked Questions
My .env is in .gitignore — am I safe?
Only from this point forward. .gitignore prevents new commits to a path; it does nothing to a file that was already committed before the rule was added. Run git log --all --full-history -- .env to check whether the file — or the secret value itself — ever entered history, and rotate any key that shows up.
How fast do bots find a leaked key in a public GitHub repo?
Honeypot research from GitGuardian and Truffle Security consistently shows automated probing within one to five minutes of a secret appearing in a public commit. Scanners subscribe to GitHub's public push events and match known key patterns in near real time, so there is no meaningful delay to rely on.
What does the sk-proj- prefix mean?
sk-proj- identifies an OpenAI project-scoped API key, meaning its access is limited to one project's models and usage rather than the whole organization. It's a narrower blast radius than a legacy sk- org-wide key, but it still has no built-in expiration and grants full access within that project if leaked.
What does sk-or-v1 mean?
sk-or-v1- is OpenRouter's key format. It grants access to a routing layer that can call multiple underlying model providers on your behalf, billed against your OpenRouter credit balance, so a single leaked key can generate usage across several backend providers at once.
Does rotating the key fix it?
Rotation stops future misuse but doesn't erase whatever happened during the time the key was exposed. Always pair rotation with a check of provider usage logs to confirm whether the key was actually used, then follow the full incident response sequence for audit and cleanup.
How do I prevent API key exposure on GitHub going forward?
Layer three controls: a pre-commit hook that blocks secrets before they're staged, GitHub's own secret scanning and push protection as a backstop, and a .dockerignore/CI configuration that keeps .env out of images and logs. See the pre-commit hooks guide and what GitHub's native scanning catches and misses for the specifics of each layer.