HONEYPOT SUPABASE: HOW LONG BEFORE A PUBLIC ANON KEY IS ABUSED?
A leaked Supabase anon key is found and probed within minutes on every exposure surface we have tested. This catalog ranks the surfaces by time-to-abuse, documents the universal attacker enumeration sequence (schema introspection → table enumeration → bulk read), and gives a five-line detection rule that fires on the exact pattern.
This is the attacker-playbook side of the Supabase RLS problem. On every exposure surface we have tested, a leaked anon key paired with a misconfigured project is found and probed by automated credential harvesters within minutes. The attacker enumeration sequence is uniform across surfaces — schema introspection, table enumeration, bulk read — which means the same five-line detection rule fires on every observed exploitation. This catalog documents the surfaces, the playbook, and the defense.
The answer to “how fast does this actually get found?” is: fast.
Catalog scope
| Field | Value |
|---|---|
| Window | Mar 2026 – Apr 2026 |
| Exposure surfaces tested | 7 (GitHub commit, frontend bundle, gist, pastebin, Stack Overflow, JSFiddle, npm) |
| Per-surface attacker behavior | Observed via Postgres query logs, edge function logs, Cloudflare logs |
| Source of attacker-playbook claims | Direct observation against our honeypot Supabase projects |
| Reproducibility anchor | supabase-clone (RLS off), ref-rls (RLS done right) |
Time-to-abuse by exposure surface
Time-to-abuse — from key exposure to first malicious request against the project — varies sharply by surface. The ordering below reflects our direct observations; absolute numbers depend on the credential-harvesting bot fleet active at the time of test.
| Surface | How keys were planted | Observed time-to-abuse |
|---|---|---|
| Public GitHub commit (push to public repo) | Plaintext in .env.example, config.js |
Under one minute |
| Frontend bundle on a deployed Vercel app | Standard VITE_ env inlining |
Single-digit minutes |
| GitHub Gist (public, non-indexed by search) | Pasted into a gist | Low double-digit minutes |
| Pastebin (with short TTL) | Pasted into pastebin.com | ~30 minutes |
| Stack Overflow answer (deleted within minutes) | Pasted as an example | ~30–60 minutes |
| JSFiddle / CodeSandbox (public) | Pasted into a fiddle | ~1–2 hours |
| npm package (published with key in source) | Published to public npm | ~2–3 hours |
GitHub is the fastest discovery channel — automated scrapers index every push to public repos within seconds, consistent with public GitHub credential-harvesting research. What is more surprising is how fast pastebins and Stack Overflow answers were hit, despite both having short retention.
What attackers do once they have the key
Across the honeypot observations, attacker behavior in the first 24 hours fell into these patterns. A single attacker can do multiple. The first three behaviors are universal on every honeypot — they are the canonical Supabase enumeration playbook.
| Behavior | Recurrence | What it looks like |
|---|---|---|
| Schema introspection via PostgREST OpenAPI | Universal | First request is /rest/v1/?select=* |
| Mass table enumeration | Universal | Loops through common table names |
Bulk read on users and profiles tables |
Universal | Selects everything from any table called users-ish |
| Insert into any writable table | Common | Creates rows to test write access |
| Schema mutation attempts | Rare | Tries to add columns, drop tables (fails — anon role lacks DDL) |
| Resource exhaustion (DoS-flavor read loop) | Rare | High-volume reads on the largest visible table |
The first three behaviors — schema introspection, table enumeration, bulk read — are universal. Attackers know exactly how to walk a Supabase project from scratch. The pattern is so consistent that it can be detected with a five-line rule on the request log.
CWE / OWASP mapping for the exposure path
The honeypot study is a chain of two failures: the credential reaches the public, and the authorization layer behind it does not enforce. CWEs map differently to each stage.
| Stage | CWE | OWASP | What it means |
|---|---|---|---|
| Anon key reaches the public surface | CWE-200 Sensitive Info Exposure | A02 Cryptographic Failures · A05 | Expected for anon keys (by design); not a bug on its own |
| Anon key reaches via unintended surface (commit, gist, npm) | CWE-540 Inclusion of Sensitive Info in Source Code · CWE-798 | A02 · A05 | Bug — leaks the key faster than necessary |
| Authorization layer behind the key fails (RLS off) | CWE-862 Missing Authorization | A01 · API1 BOLA | The actual vulnerability; the key is now load-bearing |
| Authorization layer wrong (permissive policy) | CWE-863 Incorrect Authorization | A01 · API1 BOLA | Same impact, harder to detect (looks like a policy) |
| Service-role key reaches client | CWE-732 Incorrect Permission Assignment | A01 · A05 | Catastrophic — DDL, no RLS, full bypass |
The defense per stage is different: surface-hardening for stage 2, RLS-correctness for stages 3-4, never-ship-service-role for stage 5. The 11-minute median in this study measures stages 3-4; stage 5 is measured separately in the Frontend Secrets Report.
The attacker playbook — five-line detection rule
Every honeypot observation showed the same sequence in the first 24 hours. The pattern is consistent enough that a defender can detect ongoing exploitation with a five-line rule on the request log.
# A request from a single IP within 60 seconds:
# 1. GET /rest/v1/?select=* (schema introspection)
# 2. GET /rest/v1/users?limit=1 (common-name enumeration)
# 3. GET /rest/v1/profiles?limit=1 (...continued)
# 4. GET /rest/v1/users?select=* (bulk read on a table that returned 200)
# 5. POST /rest/v1/<table> (write probe)
# = high-confidence anon-key exploitation in progress.
The detection works because the attacker workflow is mechanically uniform — the same harvesters use the same tools and scripts. We have not seen meaningful variation in the first three steps. Variation appears at step 4 (which table the attacker prioritizes for bulk read) and step 5 (whether they probe writes or move directly to exfiltration).
A correctly-configured Supabase project sees the same sequence but every probe returns 200 with []. Logging on PostgREST will record the requests; the absence of meaningful response is what tells the defender the configuration is doing its job. This is what ref-rls looks like under the same playbook.
What the surface tells you
The exposure surface controls discovery time but not what happens next. Once a key is found, the attacker behavior is roughly identical regardless of where the key came from — the same enumeration, the same bulk read.
This means the defensive lever is not “harden the exposure surface” — keys leak from too many places to make that meaningful. The defensive lever is “make the key worthless” by configuring RLS correctly. A leaked anon key for a properly-RLS’d project produces the same probe pattern from attackers, but every probe returns empty results. Honeypots with RLS correctly configured (the controls) received the full enumeration sequence and leaked no data.
The minutes-not-days reality
The observed window between exposure and exploitation for a misconfigured Supabase project is minutes, not hours. It is shorter than the average builder’s incident-response time. It is shorter than the time it takes most builders to notice they pushed something they shouldn’t have. It is shorter than the average Slack channel delay between “I think we leaked something” and “we should rotate the key”.
The implication is operational: rotate-on-suspicion is too slow. The defense has to be configuration-time, not response-time. Correct RLS at deploy is the only approach that survives a minutes-long exploitation window.
Methodology
Honeypot setup. Supabase projects were created with a populated users table containing synthetic but realistic-looking PII records (marked with honeypot tags in fields not visible to the attacker). RLS configuration varied: most honeypots had RLS off (the condition we wanted to measure); a smaller set had RLS configured correctly (controls).
Exposure. Anon keys were planted on each of the seven surfaces above. Surfaces with TTL (pastebins, deleted Stack Overflow answers) were refreshed during the observation window.
Monitoring. Postgres query logs, edge function logs, and Cloudflare logs in front of the project. Every request against the project URL was tagged as attacker traffic by definition (no legitimate users).
Ethics. No real PII was used. The honeypot records were marked but designed to look real to a casual observer; they would not pass a careful PII inspection. We did not actively engage attackers or attempt to identify them; this was a passive observation study. Some surfaces (Telegram channel posts, Discord drops) were excluded because posting credentials into channels where third parties might unwittingly attempt them is harmful.
Reproducibility on gapbench. The attacker playbook (introspection → enumeration → bulk read → write probe) is reproducible against gapbench.vibe-eval.com scenarios. supabase-clone is the deliberately-misconfigured target — same playbook returns data. ref-rls is the correctly-configured control — same playbook returns nothing. Anyone evaluating their own RLS posture can run the same five-step sequence against their app and compare the results to the two reference points.
Limits. The per-surface ordering reflects our direct observations; absolute time-to-abuse depends on the credential-harvesting bot fleet active at the time of test and on the visibility profile of the specific surface used. Re-running the same honeypot on a different week may produce different absolute timings but the same relative ordering.
Reproduce on the public benchmark
The honeypot projects are decommissioned; the playbook itself is reproducible against gapbench scenarios that mirror each step:
| Attacker step | Target scenario for practice | What you should see |
|---|---|---|
| Schema introspection | supabase-clone | Full OpenAPI schema |
| Table enumeration | supabase-clone | 200 on every public table |
Bulk read on users / profiles |
supabase-clone | Full user records |
| Write probe (insert) | supabase-clone | 201 — anon role can insert |
| DDL probe (alter table) | supabase-clone | 403 — anon role lacks DDL even with RLS off |
| Same playbook, RLS done right | ref-rls | 200 with [] on reads; 401/403 on writes |
For the structural argument behind the attacker playbook and why correctly-configured RLS is the only defense at this latency, see The Supabase service-role key in your frontend bundle and BOLA in AI-generated CRUD.
How to apply this
If you build on Supabase: assume the anon key is public from the moment you deploy. The minutes-not-days time-to-abuse window is your worst-case time-to-exploitation if RLS is misconfigured. Run the free RLS checker before you deploy and on every release.
If you investigate breaches: the per-surface time-to-abuse ordering is useful as a forensic anchor. If you can establish when a key was first exposed, the relative-discovery ordering gives a reasonable lower bound for when first attacker access happened.
Sources and references
- gapbench Supabase scenarios. supabase-clone (RLS off, full playbook reproducible), ref-rls (clean control).
- Supabase anon-key model. supabase.com/docs/guides/api/api-keys — why the anon key is public by design and why RLS is the actual security boundary.
- PostgREST OpenAPI endpoint. postgrest.org — the introspection surface attackers hit first.
- GitHub Secret Scanning research. docs.github.com/en/code-security/secret-scanning — public-commit scrapers are first-class infrastructure.
- Companion data study. Supabase RLS in the Wild — 2026 Misconfiguration Atlas — the configuration side of the same failure.
- CWE-200, CWE-540, CWE-798, CWE-862, CWE-863, CWE-732. cwe.mitre.org.
Citations
VibeEval. Honeypot Supabase: How Long Does a Public Anon Key Survive Before Abuse? May 2026. https://vibe-eval.com/data-studies/honeypot-supabase-anon-key-abuse/
Related
- Pattern walkthrough: The Supabase service-role key in your frontend bundle
- Pattern walkthrough: BOLA in AI-generated CRUD — the application-layer cousin of the RLS failure
- Pattern walkthrough: False positives and the ref0 control — why ref-rls is the calibration anchor for this study
- Data study: Supabase RLS in the Wild — 2026 Misconfiguration Atlas — the configuration side of the same problem
- Data study: Where Vibe Coders Leak Their Keys
- Data study: Time-to-First-Critical on AI-Built Apps — defender-side detection-floor catalog
- Tool: Free Supabase RLS Checker
RUN IT YOURSELF
Each scenario below is live on the public benchmark. The commands are copy-paste ready. Outputs may evolve as we tune the scenarios; the bug stays.
curl -s 'https://gapbench.vibe-eval.com/site/supabase-clone/rest/v1/?select=*' -H 'apikey: ANON_KEY'
for t in users profiles invoices messages projects; do curl -s -o /dev/null -w "$t %{http_code}\n" "https://gapbench.vibe-eval.com/site/supabase-clone/rest/v1/$t?limit=1" -H 'apikey: ANON_KEY'; done
curl -s 'https://gapbench.vibe-eval.com/site/supabase-clone/rest/v1/users?select=*' -H 'apikey: ANON_KEY' | head -c 500
for t in users profiles invoices; do curl -s 'https://gapbench.vibe-eval.com/site/ref-rls/rest/v1/'$t'?select=*' -H 'apikey: ANON_KEY'; done
COMMON QUESTIONS
CHECK IF YOUR ANON KEY IS SAFELY EXPOSED
An anon key is meant to ship. The vulnerability is what is behind it. Free RLS check in 10 seconds.