when row level security is optional: a supabase misconfiguration story
tl;dr
while reviewing several production websites built on supabase, i identified multiple critical issues:
- unrestricted read access to user profiles
- unauthorized privilege escalation (pro accounts)
- full read/write/delete access to paid content
- misleading account deletion logic
- missing mandatory legal information for paid services
all technical issues were caused by missing or incorrect row level security (rls) rules. no advanced exploitation was required — only direct api requests using the public anon key.
the issues were responsibly disclosed to the site owner.
context
this started with a simple curiosity check.
after interacting with a newly launched website, i noticed that supabase was used directly from the frontend. since supabase relies heavily on row level security for access control, i decided to verify whether those rules were correctly enforced.
all testing was performed using accounts i created myself. no third-party data was modified, deleted, stored or shared.
public access to user profiles
the first issue appeared immediately.
sending a simple request to the profiles endpoint returned all user profiles, without authentication or filtering:
GET /rest/v1/profiles
the response contained:
- user ids
- email addresses
- profile metadata
- internal fields such as
customer_id
this becomes particularly sensitive when customer_id is present, as it can be linked to billing information and personal identity data.
this strongly suggests that rls was either disabled or missing entirely on the profiles table.
unauthorized account modification
the second issue had a direct financial impact.
by sending a patch request to my own profile, i was able to modify restricted fields such as is_pro:
PATCH /rest/v1/profiles?id=eq.user_id
{
"is_pro": true
}
the request succeeded using the public anon key.
this means any authenticated user could grant themselves paid or privileged status without payment or validation.
broken account deletion logic
while reviewing user-facing features, i also checked the "delete account" flow.
the frontend claimed that accounts would be deleted after a delay, but no backend request supported this behavior. the action only logged the user out.
this creates a misleading privacy expectation for users who believe their data is scheduled for deletion.
second site, same issues
out of curiosity, i checked another production website operated by the same owner.
the findings were similar, but the impact was higher:
- unrestricted read access to
profiles - full read, write and delete access to
courses - read access and self-modification on
user_courses
this allowed any user to access paid content for free with only a few api calls.
legal and compliance concerns
beyond technical vulnerabilities, i also noticed several legal compliance issues on a paid service offered by the same owner.
in particular:
- a lifetime license was sold without accessible terms and conditions
- no mandatory legal notice (company name, address, legal status) was available
- refund and withdrawal policies were unclear or incomplete
- applicable law was stated as japanese law, without addressing european consumer protections
this is problematic when selling digital services to users located in the european union.
i contacted the site owner to highlight these points, referencing:
- directive 2011/83/eu
- french consumer code (articles l111-1 and following)
- the lcEN (article 6-iii)
the intent was not to threaten or escalate, but to prevent avoidable legal exposure and protect users.
root cause
all issues - technical and legal - shared the same underlying pattern:
- over-reliance on frontend logic
- missing enforcement on the backend
- assumptions left unchecked before going to production
security and compliance are not optional layers. they must be part of the system design from the start.
responsible disclosure
i contacted the site owner directly with detailed explanations and reproduction steps.
testing was limited to my own accounts and passive observation of responses. no third-party data was altered.
the goal was solely to prevent abuse, data exposure, and legal issues.
lessons learned
for developers and indie founders:
- always enable and audit rls policies
- deny by default, allow explicitly
- treat public api keys as public
- never rely on frontend checks for security
- do not ship paid services without basic legal compliance
most security failures are not caused by sophisticated attackers, but by missing fundamentals.