Disclosure Notice: This vulnerability was responsibly disclosed to the affected company. The issue has been fully remediated (RLS enforced, credentials rotated). All sensitive identifiers — company name, database URLs, API keys, and exact financial figures — have been redacted. This post is published with the company’s acknowledgement.
The Hunter’s Mindset: The Assumption Gap
In the rush to deploy cutting-edge AI, security often rests on a single point of failure: configuration. Developers frequently assume that because a Database-as-a-Service (DBaaS) like Supabase provides a “public” anon key, the data behind it is automatically shielded.
As a Threat Researcher, my mindset is different:
“If a key is public, I must assume every table it can reach is public — until I prove otherwise.”
This is the story of how a routine frontend audit uncovered a critical Material Non-Public Information (MNPI) leak at a high-growth AI company.
Step 1: Reconnaissance & JavaScript Static Analysis
The hunt began on the frontend of the target. Modern web applications — especially those using no-code builders like Webflow — rely on “glue code”: custom JavaScript bundles injected to handle dynamic data rendering, such as populating pricing pages.
I opened Chrome DevTools and navigated to the Sources tab. I identified a production npm package responsible for the company’s pricing page logic. Using the browser’s Pretty Print feature, I de-obfuscated the bundle and ran a regex search for supabase.
Step 2: Credential Extraction
Within the bundle, I found the initialization of a Supabase client. While the anon key is intended to be client-side safe, its presence shifts 100% of the security burden onto Row Level Security (RLS).
The Discovery:
import { createClient } from '@supabase/supabase-js';// Initialization discovered during static analysis
const SUPABASE_URL = "https://[REDACTED].supabase.co";
const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...[REDACTED]";const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);Step 3: The Logical Pivot — Investigative Enumeration
Having extracted a valid anon key, I had a working identity to present to the database. The question became: what could I reach with it?
My hypothesis: The engineering team likely enforced RLS on user-facing tables — the ones directly rendered in the UI. But internal “helper” tables — those used for caching pre-calculated metrics — are frequently an afterthought. Developers create them for performance reasons, never intending them to be API-accessible, and then forget to apply RLS during security reviews.
This is the Assumption Gap in practice.
Why arr_cache specifically? This required understanding how developers think, not just how code works. When a pricing page needs to display real-time business metrics (like growth rates), engineers rarely query raw transaction tables on every page load — that's expensive. Instead, they create a lightweight cache table that stores a pre-aggregated snapshot. The naming convention almost writes itself:
arr→ Annual Recurring Revenue, the north-star SaaS metric_cache→ a pre-computed aggregate, refreshed on a schedule
This wasn’t a lucky guess. It was researcher intuition built on understanding developer architecture patterns. In Supabase’s PostgREST model, every table is automatically exposed as an API endpoint at /rest/v1/<table_name>. Finding a table name is finding an attack surface.
Get Ashish Bogati’s stories in your inbox
Join Medium for free to get updates from this writer.
I moved from the browser entirely to the terminal — bypassing all frontend UI filters and React logic — to query the PostgREST API directly.
The Researcher’s Logic:
Evidence Inference The “Aha!” Moment Leaked anon key in JS bundle This key has a database role attached to it I have an identity — now test what doors it opens Hardcoded Supabase URL The database is directly exposed, not proxied I can bypass the frontend entirely with cURL Pricing page renders growth metrics A cache table almost certainly exists behind this Target arr_cache — the canonical naming pattern
The Attack Command:
curl -X GET "https://[REDACTED].supabase.co/rest/v1/arr_cache?select=*" \
-H "apikey: [REDACTED_ANON_KEY]" \
-H "Authorization: Bearer [REDACTED_ANON_KEY]"Step 4: The Impact
The server returned a 200 OK — bypassing all authentication. The payload exposed live, highly sensitive business metrics.
The Response (figures redacted):
[
{
"id": 1,
"current_arr": "[REDACTED — $XX.XM+]",
"pct_growth_30d": "[REDACTED — XX%]",
"updated_at": "2026-04-01T05:21:05.715Z"
}
]For any company — especially an AI firm — real-time ARR and growth rate data represents board-level, investor-sensitive information. This is a textbook Broken Access Control (BAC) vulnerability leading to direct disclosure of Material Non-Public Information (MNPI).
Triage Note: The company’s security team triaged this report as Critical severity and moved to remediation within 24 hours — confirming that “guessable internal tables with missing RLS” is a threat they take seriously, not a theoretical edge case. The speed of the fix is itself evidence of impact.
Press enter or click to view image in full size
Step 5: Remediation & Verification
Upon responsible disclosure, the security team acknowledged the severity immediately. Because the anon key had been exposed with broad permissions, the fix required two actions:
- Global Key Rotation — Invalidating the compromised key across all environments.
- RLS Enforcement — Applying a strict
DENY ALLpolicy to thearr_cachetable and auditing all internal tables.
Verification (24 hours later):
curl -X GET "https://[REDACTED].supabase.co/rest/v1/arr_cache?select=*" \
-H "apikey: [ROTATED_KEY]"{ "message": "Invalid API key" }Conclusion: Lessons for the AI Era
Lesson Takeaway Client-Side is Public Space If a key is in your JS bundle, treat it as a fully public credential. RLS is Not Optional In Supabase, every table should have a DENY ALL policy by default — including internal cache tables. Audit Your "Glue" Code Custom scripts connecting third-party tools (Webflow, Framer, etc.) are often the weakest link in your security chain. Naming Conventions Are Signals Tables named arr_cache, internal_metrics, or admin_data are prime targets for enumeration.
About the Author
Ashish Bogati is a Threat Researcher and Security Auditor specializing in cloud architecture, modern web infrastructure, and responsible disclosure.
Tags: #ThreatResearch #BugBounty #Supabase #CyberSecurity #InfoSec #ResponsibleDisclosure #BrokenAccessControl #MNPI