DEV Community

Cover image for 1,033 Live Stripe Secret Keys Leaked: How Exposed .env Files Became a Payment Rail Breach
Stanley A.
Stanley A.

Posted on Originally published at wardenbit.com

1,033 Live Stripe Secret Keys Leaked: How Exposed .env Files Became a Payment Rail Breach

1,033 Live Stripe Secret Keys Leaked: How Exposed .env Files Became a Payment Rail Breach

On August 18, 2026, a threat actor dumped 1,033 live Stripe sk_live_... keys from 669 vendors on an illicit forum — with a direct claim to Hudson Rock that ~20,000 keys are held for staggered release. The leaked keys had charge capabilities, were tied to real invoices and promo-code tables, and the victims ran completely different stacks. Hudson Rock found no infostealer infections tied to the vendor domains. The working hypothesis: automated mass-scanning for publicly exposed .env files and debug logs.

No vulnerability in Stripe. No single WordPress plugin. One misconfigured file, found at scale.

If you build or operate anything that talks to Stripe, this is a build-pipeline and deployment-hygiene incident you can verify in five minutes.

What the dump actually contained

Per Hudson Rock's Infostealers analysis:

  • 669 vendors, 1,033 sk_live keys, customer rows, charges, balances, payouts, invoices
  • Advertised 33 GB total; the provided archive was 2.37 GB — consistent with a staggered release
  • JSON samples show "sk_live_51H..." with explicit charge-access flags
  • Invoice-linked CSVs with customer name / email / phone / home address / IP at purchase / internal transaction IDs / third-party integration identifiers — plus hosted invoice URLs that resolve to valid Stripe invoices exposing last-four of card
  • A separate promo-code spreadsheet (code, discount amount, duration) — mass abuseable

The 20,000 figure is an actor claim to Hudson Rock researchers, not an independent count. Treat it as claimed; the 1,033 observed keys are verified. That distinction matters if you cite the story.

What sk_live actually unlocks

Depending on scope, a standard sk_live is not read-only:

GET /v1/customers
GET /v1/invoices/:id
GET /v1/charges
POST /v1/refunds        # with charge capability → direct financial control
POST /v1/payouts        # if account-settings write is allowed
GET /v1/coupons         # promo-code enumeration
Enter fullscreen mode Exit fullscreen mode

That is programmatic access through normal Stripe API calls. No exploit. A scanner that finds the key gets the same interface your backend uses.

Why .env and not a package bug

Two signals rule out a single-dependency explanation:

  1. Victim tech stacks are heterogeneous — PHP storefronts, Node services, Python APIs, hosted site builders. No common plugin or framework.
  2. Hudson Rock's telemetry found no infostealer infections on the specific vendor domains in the dump.

Both point to infrastructure-level exposure: a document root that serves dotfiles, a Docker image that copies .env into the served directory, or a debug/error page that dumps process.env / $_ENV. Scanners for /.env, /.git/HEAD, and /debug are older than this breach and fully automated.

A quick check you can run from any machine:

for p in "/.env" "/.env.example" "/.git/HEAD" "/debug"; do  # keep /.well-known/security.txt public per RFC 9116
  echo -n "$p -> "
  curl -s -o /dev/null -w "%{http_code}\n" "https://yourdomain.com$p"
done
# all should be 403/404 — any 200 that returns key-value secrets is an incident (note: /.well-known/security.txt should return 200 and is not part of this check)
Enter fullscreen mode Exit fullscreen mode

Server-side, the fix is boring and decisive. Examples:

# nginx — block dotfiles before any try_files
location ~ /\. { deny all; access_log off; log_not_found off; }
location = /.env { deny all; }
location ~ /\.git { deny all; }
Enter fullscreen mode Exit fullscreen mode
# Apache .htaccess
<FilesMatch "^\.env">
  Require all denied
</FilesMatch>
RedirectMatch 404 /\.git
Enter fullscreen mode Exit fullscreen mode
# Dockerfile — do not copy .env into the image
# .dockerignore must contain: .env
# and: RUN --mount=type=secret,id=stripe_key ...  # BuildKit secrets, not COPY
Enter fullscreen mode Exit fullscreen mode

On Cloudflare, a single WAF custom rule blocking URI Path contains /.env or /.git covers the edge, but it is not a substitute for origin hygiene — the origin must not serve the file even if the edge is bypassed by direct IP.

The check that actually invalidates a copied key

Scanning fixes the exposure. Rotation invalidates what was already copied.

In the Stripe Dashboard:

Developers → API keys → Roll secret key → Revoke previous value
Enter fullscreen mode Exit fullscreen mode

If you use restricted keys, roll each one and audit whether any key was issued as a standard-type key with full charge + refund + payout scope when your integration only needs payment_intents:write and customers:read. Scope reduction after rotation:

# list keys and their scopes (restricted keys API)
curl https://api.stripe.com/v1/restricted_keys \
  -u sk_live_...: \
  -G

# create a least-privilege restricted key for a checkout-only service
curl https://api.stripe.com/v1/restricted_keys \
  -u sk_live_...: \
  -d "name=checkout-only $(date -I)" \
  -d "restricted_resources[0][resource]=payment_intent" \
  -d "restricted_resources[0][permissions][]=write" \
  -d "restricted_resources[1][resource]=customer" \
  -d "restricted_resources[1][permissions][]=read"
Enter fullscreen mode Exit fullscreen mode

A copied standard key and a copied restricted key are both breaches — but one can refund and reroute payouts, the other cannot. That scope boundary is the difference between a privacy incident and a financial one.

Inventory every place the key has lived

The .env on production is rarely the only copy. After rotation, inventory and clean:

  • .env committed to a private repo that later became public
  • CI logs and build artifacts that echoed environment
  • Backup snapshots of the app directory (app-2026-03-01.tar.gz that includes .env)
  • Staging / legacy subdomains sharing the same key (staging.yourdomain.com/.env)
  • Third-party deploy tools and env-sync services that replicate variables
  • Local .env files on developer machines that were never rotated after the exposure window

A single git log --all --full-history -- ".env" and a secrets scan (GitGuardian, TruffleHog, gitleaks in CI) catches most historical commits. The point of the inventory is speed the next time any one location leaks: you rotate the key once and every copy is covered.

Detection after the fact

After rotating, look backwards 30 days in both places:

  • Stripe Dashboard → Developers → Logs: filter for POST /v1/refunds, POST /v1/payouts, and high-volume GET /v1/customers or GET /v1/invoices from unfamiliar IPs / ASNs. Normal integrations have a predictable call pattern; bulk listing is anomalous.
  • Origin access logs: GET /.env and GET /.git/HEAD from scanner user-agents in the weeks before the dump date. Even a 404-heavy scan leaves a fingerprint.

If either log shows unfamiliar calls, treat the window as an active incident — preserve logs, notify the payment processor, and consider a focused review of the key's lifetime.

Why this keeps happening

Doppler's Breach Dashboard timeline makes the pattern explicit. Recent examples include n8n (4,576 tokens found in public GitHub, 321 still valid, August 2026) and LiteLLM on PyPI — where CloudSEK initially tracked roughly 434,000 pipelines potentially exposed and Hudson Rock's independent analysis corroborated the scale at about 2,500 organizations (March 2026 backdoored releases 1.82.7/1.82.8) — alongside two disclosures from May 2026: Nx Console (poisoned VS Code extension 18.95.0 published May 18, more than 2.2 million installs) and CISA's own 844 MB public repo of production secrets (also May 2026, reportedly exposed for about six months). The Hudson Rock note on LiteLLM applies verbatim: removal of the file does not end the incident — copied credentials outlive the exposure.

For small teams, the durable fix is three habits rather than a vault project:

  1. Time-box secrets — Stripe keys every 90 days, DB URLs every 180, on the same calendar as domain renewal
  2. Keep secrets out of the repo.env in .gitignore, history scanned in CI, BuildKit secrets or env injection at deploy time
  3. Maintain a live inventory of every location that holds each secret, so rotation is one action, not a scavenger hunt

TL;DR for the reviewer who finds /.env returning 200

Your .env is your payment rail's access-control list. Check whether it is public, rotate the key, inventory where it lives, scope its permissions, and review the logs. That is the hour of work that 669 vendors are doing after the fact. You can do it before the next batch drops.


Originally published on WardenBit.

Top comments (0)