The API key your AI app is serving to the world
AI apps ship fast — and a startling number serve their own .env, config, or build output straight to the public web, leaking OpenAI, Anthropic, AWS and database keys. There’s no exploit: anyone who loads the URL gets the key — and with it, drained API budgets, hijacked models, and access to your data. We find these with identified, read-only requests — GET and HEAD only, each carrying our identity and a signed receipt.
Why this is dangerous
An API key in a served .env isn’t a vulnerability that needs an exploit — it’s a password sitting in public:
- • LLM keys get drained instantly. Bots scrape exposed OpenAI/Anthropic keys and resell the access — you get the bill, often thousands of dollars before you notice.
- • Cloud keys become a foothold. A leaked AWS/GCP key can read your storage, spin up compute to crypto-mine, or pivot deeper into your infrastructure.
- • It’s the classic “vibe-coded” mistake — shipping the real
.envinto thepublic/,dist/orbuild/folder, or leaving.gitexposed. Automated scrapers find these within minutes.
What the radar has done so far
Candidate hosts processed: counted per candidate, so one server found on two ports counts twice, and candidates we could not probe are included.
We refuse to inflate the count. Most public .env paths on the internet are honeypots or copied templates serving the same fake keys — we filter every one of those out. A confirmed exposure is a secret we have not seen on any other host, or a file or service that should never be public, such as an open .git, and so far we’ve found 2,670 of them.
Beyond keys — catastrophic data & source exposure
The same radar now confirms the highest-impact web exposures — an open .git repo, a database dump, a Spring Boot heap dump, a private key, or an application log — by requesting each at its well-known path and reading at most the first 256 KB of the reply (of a heap dump, we request only the first 128 bytes) to confirm what it is. We never store the file, clone a repository, or log in.
By provider — and what the key unlocks
How they're leaking
- an exposed .git repo — full source downloadable488
- html-inline292
- a cloud/credentials file (.aws, .npmrc, docker)172
- an exposed .git/config93
- an exposed SSH/PEM private key91
- js-bundle88
- an application log — PII, tokens, stack traces24
- open-s3-bucket22
- .env served on the public web12
- open-gcs-bucket8
Are you exposed?
Check whether your app is serving a .env, config file, or secret-bearing bundle — a free external scan of your internet-facing surface, no signup.
How it works
How do you detect this without hacking anything?
Each time we check a host we send it up to 48 identified requests, GET and HEAD only: the well-known paths where config and secrets leak (/.env and its variants, /.git/config, /.aws/credentials, /.ssh/id_rsa, database dumps, logs), the homepage and the site’s own script bundles, observability-tool APIs, and random paths that should not exist. A finding is a secret in what comes back, or a file or service that should never be public (an open .git, a private key, a database dump, an unauthenticated Prometheus) whose content confirms what it is. A host that answers a random path with config, or serves config at more than five of these paths, is discarded as a honeypot. We never log in, run exploits, or write anything.
Do you ever use or test the keys you find?
Never. Validating a key against its provider would be unauthorized use — and could run up the victim’s bill. We detect the key’s shape and entropy, store only a redacted fingerprint (never the raw value), and notify the owner so they can rotate it.
Why is the count sometimes low?
Because we refuse to inflate it. The internet is full of honeypots and copied starter-templates that serve the same fake keys on /.env; we filter every one of those out. A secret is counted only if we have not seen it on any other host — so the number is small but honest.
Why don't you list the affected hosts?
Publishing them would be a target list. We keep host details private for responsible disclosure to affected owners and publish only aggregate counts. Use the scanner above to check your own exposure.
Frequently asked questions
What is the Exposed AI Keys & Config Radar?
It finds web apps that serve their own .env, config or build files to the public internet, leaking API keys for OpenAI, Anthropic, AWS, databases and more. EchelonGraph detects these with identified, read-only GET and HEAD requests and publishes only aggregate, host-redacted counts.
How does an API key end up in an exposed .env file?
A misconfigured deployment serves the project directory — including .env, config files, or source-map and build output — over HTTP. Anyone who requests a path like /.env downloads the secrets directly. No hacking is involved; the app hands them out.
How does EchelonGraph detect leaking apps?
It sends each host it checks up to 48 identified, read-only GET and HEAD requests, including ones for common config and secret paths, and checks that what comes back actually looks like a real secret or a file that should never be public. It is detect-only and never uses any key it finds. Findings are reported in aggregate, with hosts redacted.
My app might be leaking secrets — how do I fix it?
Stop serving dotfiles and config (block /.env, /.git and /config at the web server or CDN), move secrets into environment variables or a secret manager, and rotate every exposed key immediately — assume any key that was publicly reachable is already compromised.
EchelonGraph-<Radar>/1.0 (+https://echelongraph.io/surface-scanner; opt-out https://echelongraph.io/responsible-disclosure; support@echelongraph.io) or, from our radar prober, EchelonGraphBot/1.0 (+https://echelongraph.io/bot), and their HTTP requests to the hosts they check also carry a From: support@echelongraph.io header. Some checks are a single identified GET; others send several requests to one host. Confirming an MCP endpoint sends a POST asking the server which protocol versions it supports, a second POST with the older handshake only if the first is refused without a 2026-07-28 error, a DELETE to close any session a POST opened and, if a POST is refused with HTTP 401 or 403, up to two GETs of the server's OAuth protected-resource metadata on the same host; where neither POST finds an MCP endpoint, the older SSE transport's stream is requested with GET, only its first event is read, and the connection is closed. A server that answers the first POST with a DiscoverResult, or with an error the 2026-07-28 revision defines, opens no session and is sent no further POST, so it is not written to; if the POST is refused with HTTP 401 or 403, the metadata GETs above still follow. Confirming an exposed datastore needs a protocol-level exchange, and on Redis our client names itself so the connection is identifiable in your own CLIENT LIST. We never log in, submit credentials or exploit what we find. Who we are, exactly what each check sends, and how to opt out → A radar's HTTP requests to the host it checks also carry a signed receipt you can validate at /verify-scan.