Supabase Storage accepted a signed JWT with role none and bypassed RLS
On November 6, 2026, Supabase published GHSA-fgvp-cqg6-q8h2 for Supabase Storage 1.89.8 and earlier. Exploitation requires a trusted token issuer, for example a custom access-token hook, to emit a valid signed JWT containing role: "none". A request authenticated with that token could run database operations as the Storage database connection user and bypass row-level security when that user owns the Storage tables and row-level security is not forced on the owner. That could allow unauthorized reads from private buckets, object uploads, and bucket creation. The advisory rates it Critical, CVSS 3.1 score 9.8, lists 1.89.9 as the fix, and says the hosted platform is already patched.
What was reported
GHSA-fgvp-cqg6-q8h2, published November 6, 2026, says exploitation requires a trusted token issuer to emit a valid signed JWT containing role: "none", for example through a custom access-token hook. In Supabase Storage, a request authenticated with that token could run database operations as the Storage database connection user. When that user owns the Storage tables and row-level security is not forced on the owner, those operations bypass row-level security policies. The advisory says that could allow unauthorized reads from private buckets, object uploads, and bucket creation within an affected project.
Prerequisites and impact
Exploitation requires a trusted token issuer to issue a token that contains this role, for example through a custom access-token hook. The advisory says modifying the role in an existing token without a valid signature does not enable the bypass.
Storage verifies the JWT signature and uses the token role claim to set the database authorization context. It passes the value to PostgreSQL through the parameterized call set_config('role', $1, true). PostgreSQL gives the exact value none special meaning: it resets the effective database role to the session user. Where Storage connects as supabase_storage_admin, that user owns the Storage tables. Table ownership exempts it from RLS unless FORCE ROW LEVEL SECURITY is enabled. The advisory says superuser privileges or the BYPASSRLS attribute are not required for this owner exemption. Request logs can still show role: none and the caller user ID because those fields come from JWT claims rather than the PostgreSQL effective role. An arbitrary nonexistent role is rejected by PostgreSQL. Empty or missing role claims fall back to anon.
The advisory rates the issue Critical, CVSS 3.1 score 9.8, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, assigns CWE-270, Privilege Context Switching Error, and CWE-863, Incorrect Authorization, and lists no CVE id. The advisory's version data lists <= 1.89.8 as affected and 1.89.9 as patched. It does not name an npm package in that version data.
Fixed versions
Upgrade to 1.89.9 or later. The advisory says the hosted platform is already patched. The fix rejects none before applying the database authorization context and returns AccessDenied, and it preserves legitimate service_role access and custom database roles.
If upgrading a self-hosted instance is not immediately possible, the advisory says to correct any custom access-token hook or other trusted token issuer that emits role: "none" and to use an existing database role with the intended permissions. Changing token issuance alone does not neutralize previously issued tokens. Until the fix is deployed, it says to enforce rejection of role: "none" across all exposed Storage entry points or restrict access to the affected service until those tokens expire, and that any gateway mitigation must also prevent direct access to an unprotected Storage origin.
Our advice, not the advisory's: if you also self-host Storage at or below 1.89.9 for other reasons, check GHSA-3m3f-pjv4-x2fp separately. That advisory still lists 1.89.10 as the fix for a different issue (presigned PUT to cross-bucket read). This item's fix version remains 1.89.9.
How to prevent it
If you self-host Supabase Storage, upgrade to 1.89.9 or later. Until you upgrade, stop any custom access-token hook or other trusted issuer from emitting role: "none". Changing token issuance alone does not neutralize tokens already issued with that role, so also reject role: "none" at every exposed Storage entry point, including direct access to the Storage origin, or restrict access to the affected service until those tokens expire. The advisory says the hosted platform is already patched.
Sources
Related
Incidents on the same topics
- Oct 30: what broke, and the 2-minute fix
- A Supabase Storage anon key could read and overwrite Iceberg analytics files
- Lovable-generated sites with missing RLS let unauthenticated users read or write tables
- A Supabase Storage presigned PUT URL for one object could be used to read objects from other buckets
The weekly fix
The weekly fix: 3 real security mistakes in vibe-coded apps and how to fix them, every Tuesday.