conversions
Convert a public RPC to service_role-only
A conversion playbook to revoke EXECUTE grants from public roles and expose RPC functionality only through backend endpoints. This page is written as a utility: clear from/to states, step-by-step logic, and examples so you can apply it without guessing.
From → To (what you’re converting)
From: A Postgres function can be executed by PUBLIC/anon/authenticated via Supabase RPC.
To: Function EXECUTE is restricted and only backend code can call it using service_role.
Safety notes for Convert a public RPC to service_role-only (order of operations)
Conversions are safest when you change application call paths before you revoke access.
- Add the backend endpoint that will replace the direct Supabase call.
- Switch the frontend to call your backend endpoint (feature flag if needed).
- Revoke direct client privileges (grants, bucket access, or RPC execute).
- Verify: direct access fails; backend endpoint succeeds.
Conversion logic (step-by-step)
- Inventory functions that perform sensitive reads/writes.
- Discover all function signatures (including overloads).
- Revoke EXECUTE from PUBLIC, anon, and authenticated per signature.
- Grant EXECUTE to service_role (server-only).
- Replace client supabase.rpc calls with backend endpoints.
- Add input validation, authorization, and rate limiting server-side.
- Verify direct client RPC calls fail and backend calls succeed.
Example conversions
Example 1
Input: admin_export RPC is callable by authenticated users.
Output: Direct calls fail; /api/admin/export triggers backend execution only for admins.
Notes: Endpoint audits export requests and limits frequency.
Example 2
Input: A helper function has multiple overloads; one remains callable.
Output: All overloads are revoked; the audit query is added to the checklist.
Notes: Prevents regression in future migrations.
Verification checklist for Convert a public RPC to service_role-only
- Reproduce the risky behavior once before changing anything (so you can prove the fix).
- After the conversion, the same direct access test fails (401/403).
- The user-facing feature still works through backend endpoints.
- A scan/checklist re-run no longer flags the exposure.
- You recorded the rule so future migrations don’t reintroduce direct access.
Related converters (what to do next)
- Convert a public table to backend-only access →
/conversions/public-table-to-backend-only— A conversion playbook to remove direct client table access and route everything through a secure backend layer. - Convert a public Storage bucket to private + signed URLs →
/conversions/public-bucket-to-private-signed-urls— A conversion playbook to stop public downloads and move to private buckets with backend-generated signed URLs.
Related templates
- Lock down RPC: revoke EXECUTE from public roles →
/templates/rpc-functions/lock-down-rpc-execute
Related glossary terms
- RPC EXECUTE Grants →
/glossary/rpc-execute-grants - Service Role Key →
/glossary/service-role-key
Common pitfalls during conversions
- Revoking access before the backend endpoint is deployed (causes outages and rollback pressure).
- Leaving one legacy client call path in place (exposure still exists).
- Assuming “authenticated users” are authorized without ownership/tenancy checks.
- Fixing only one environment and forgetting staging/prod (drift).
If you must keep some client access during Convert a public RPC to service_role-only
Sometimes you can’t move everything server-side immediately. If you keep any client access temporarily, make it an explicit, reviewed exception.
- Keep the exception narrow: one operation, one resource, one documented reason.
- Add an expiration date and a follow-up task to remove the exception once the backend path is ready.
- Add extra verification: tests for cross-user/tenant access and direct access attempts that must fail outside the exception.
- Prefer least privilege (specific grants/policies) over broad authenticated access.
This keeps “temporary” from becoming permanent drift.
What to document after the conversion (prevents regressions)
- Which resource you converted (table/bucket/function) and the exact change in access model.
- Which direct access test proves the conversion worked.
- Which backend endpoint now owns the privileged operation and what authorization it enforces.
- Which drift guard you will re-run after migrations.
FAQ
Why is backend-only access recommended in this conversion?
Because it removes direct client access to sensitive resources. That prevents scraping, reduces policy complexity, and makes authorization easier to test and monitor.
Can I keep some client access and still be safe?
Sometimes. But you should only do that intentionally, with carefully scoped policies and strong verification. If you’re unsure, backend-only is the safer default.
What’s the best verification step after this conversion?
Try direct access using client credentials and confirm it fails, then confirm your backend endpoints still work for authorized users.
Next step
Want to know if your current state is actually exposed? Run a Mockly scan first, then apply the conversion that matches the finding.
Explore related pages
sibling
Convert a public Storage bucket to private + signed URLs/conversions/public-bucket-to-private-signed-urls