{
  "id": 16519194,
  "title": "Your Supabase users can probably give themselves your paid plan. Here's the 30-second test",
  "url": "https://urgent.news/2026/10/25/your-supabase-users-can-probably-give-themselves-your-paid-plan-heres",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-10-25T20:52:33.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/leakpatrol/your-supabase-users-can-probably-give-themselves-your-paid-plan-heres-the-30-second-test-7gl"
  },
  "original_language": "en",
  "account": "Supabase users may unintentionally gain access to a paid plan by following a simple 30-second procedure. This can be demonstrated by executing code in the browser's Developer Tools console, which grants the user the paid features. The usual method of restricting user updates to their own profile using PostgreSQL Row Level Security (RLS) is ineffective in this situation because it does not cover specific columns. To prevent unauthorized access, there are three main solutions: granting updates per column, implementing a trigger to guard billing columns, or separating billing information into a distinct table with proper permissions. It is crucial to ensure that only the payment webhook can modify billing-related data, using a secure server-side key. Disabling Row Level Security is not recommended, even if issues arise.",
  "summary": "A lot of apps built with Lovable, Bolt or Cursor handle \"upgrade to Pro\" with one line in the browser: await supabase . from ( \" profiles \" ). update ({ is_pro : true }). eq ( \" id \" , user . id ); If that line works for your code, it works for anyone. Open DevTools, run the same thing in the console, and the paid plan is theirs. Same for plan , credits , role or trial_ends_at . Why RLS doesn't…",
  "key_points": [
    "Users can gain access to paid plan in 30 seconds via console",
    "PostgreSQL Row Level Security ineffective for specific columns",
    "Solutions: column-permission updates, trigger for billing, separate billing table"
  ],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}