Mockly

glossary

Signed URLs

Signed URLs give temporary, scoped access to private storage objects, but poor signing still leaks data. This page explains it in plain English, then goes deeper into how it works in Supabase/Postgres, what commonly goes wrong, and how to fix it without relying on fragile client-side rules.

What “Signed URLs” means (plain English)

The backend signs a URL so the browser can download a file just once before the link expires. Signed URLs is a practical security issue for teams using Supabase because exposed tables, storage, or RPC endpoints can return or mutate data beyond intended account boundaries.

How Signed URLs works in Supabase/Postgres (technical)

Signed URLs embed a cryptographic signature and TTL; the signing endpoint must verify object ownership and limit lifetime to stop enumeration.

Attack paths & failure modes for Signed URLs

  • Signed URL invoice download: The billing page lists invoices and users need secure PDF downloads.
  • Signed URL abuse + rate limiting: Someone guessed object paths and repeatedly requested signed URLs.
  • Signed URL invoice download: The bucket used to be public and URLs were guessable, so anyone could download invoices.
  • Signed URL abuse + rate limiting: The endpoint signed URLs without strict authorization or rate limiting.
  • Signed URLs are generated client-side or logged, turning private files into shareable public links.
  • TTL is too long, so a leaked URL stays useful for days or weeks.
  • Buckets are public anyway, making signed URLs meaningless as a control.
  • Signed URLs are reused across users (no per-user authorization boundary).
  • Signed URLs are cached by CDNs in ways that extend exposure.

Why Signed URLs matters for Supabase security

Without proper signing controls, attackers can generate or reuse links and grab any file stored in Supabase Storage. If Signed URLs remains unresolved, attackers can automate enumeration and unauthorized writes at API speed. Treat it as a production reliability risk as well as a data security risk, because incidents spread quickly once clients discover weak access boundaries.

Common Signed URLs mistakes that lead to leaks

  • Issuing signed URLs directly from client code without server-side checks.
  • Giving links infinite TTL or skipping ownership validation.
  • Not rate-limiting signing requests so brute-force attacks succeed.
  • Signed URL invoice download: The bucket used to be public and URLs were guessable, so anyone could download invoices.
  • Signed URL abuse + rate limiting: The endpoint signed URLs without strict authorization or rate limiting.

Where to look for Signed URLs in Supabase

  • Where signed URLs are generated (must be server-side for private resources).
  • TTL defaults and whether you rotate URLs frequently.
  • Bucket privacy settings (signed URLs assume private buckets).
  • Logs/analytics: are you accidentally storing signed URLs?

How to detect Signed URLs issues (signals + checks)

Use this as a quick checklist to validate your current state:

  • Try the same queries your frontend can run (anon/authenticated). If sensitive rows come back, you have exposure.
  • Verify RLS is enabled and (for sensitive tables) forced.
  • List policies and look for conditions that don’t bind rows to a user or tenant.
  • Audit grants to anon / authenticated on sensitive tables and functions.
  • Signed URL invoice download: Private buckets reduce accidental exposure.
  • Signed URL invoice download: Ownership checks should happen before signing.
  • Signed URL invoice download: Short TTLs reduce link abuse.
  • Re-test after every migration that touches security-critical tables or functions.

How to fix Signed URLs (backend-only + zero-policy posture)

Mockly’s safest default is backend-only access: the browser should not query tables, call RPC, or access Storage directly.

  1. Decide which operations must remain client-side (often: none for sensitive resources).
  2. Create server endpoints (API routes or server actions) for required reads/writes.
  3. Apply hardening SQL: enable+force RLS where relevant, remove broad policies, and revoke grants from client roles.
  4. Generate signed URLs for private Storage downloads on the server only.
  5. Re-run a scan and confirm the issue disappears.
  6. Add a regression check to your release process so drift doesn’t reintroduce exposure. Fixes that worked in linked incidents:
  • Signed URL invoice download: Make the bucket private and issue signed URLs server-side after verifying invoice ownership.
  • Signed URL abuse + rate limiting: Validate ownership, deny unknown paths, and rate-limit signing requests per user and IP.

Verification checklist for Signed URLs

  1. Confirm the bucket is private and listing is disabled for sensitive files.
  2. Generate signed URLs only on the server after authorization checks.
  3. Keep TTL short and avoid sharing links outside intended contexts.
  4. Test that direct object access fails without a signed URL.
  5. Verify that expired URLs no longer work and that new URLs are generated per request.
  6. Signed URL invoice download: Private buckets reduce accidental exposure.
  7. Signed URL invoice download: Ownership checks should happen before signing.
  8. Signed URL invoice download: Short TTLs reduce link abuse.

SQL sanity checks for Signed URLs (optional, but high signal)

If you prefer evidence over intuition, run a small set of SQL checks after each fix.

The goal is not to memorize catalog tables — it’s to make sure the access boundary you intended is the one Postgres actually enforces:

  • Confirm RLS is enabled (and forced where appropriate) for tables tied to this term.
  • List policies and read them as plain language: who can do what, under what condition?
  • Audit grants for anon/authenticated and PUBLIC on the tables, views, and functions involved.
  • If Storage is involved: review bucket privacy and policies for listing/reads.
  • If RPC is involved: review EXECUTE grants for functions and whether privileged functions are server-only.

Pair these checks with a direct API access test using client credentials. When both agree, you can ship the fix with confidence.

Over time, keep a small “query pack” for the checks you trust and run it after every migration. That’s how you prevent quiet regressions.

Prevent Signed URLs drift (so it doesn’t come back)

  • Avoid storing signed URLs in databases or logs; store object keys and generate URLs on demand.
  • Add a monitoring check for public buckets or long-lived signed URL patterns.
  • Treat download flows like auth flows: review changes carefully and verify end-to-end.
  • Keep one reusable verification test for “Signed URL invoice download” and rerun it after every migration that touches this surface.
  • Keep one reusable verification test for “Signed URL abuse + rate limiting” and rerun it after every migration that touches this surface.

Rollout plan for Signed URLs fixes (without breaking production)

Many hardening changes fail because teams revoke direct access first and only later discover missing backend paths.

Use this sequence to reduce both risk and outage pressure:

  1. Implement and verify the backend endpoint or server action before permission changes.
  2. Switch clients to that backend path behind a feature flag when possible.
  3. Then revoke direct client access (broad grants, permissive policies, public bucket reads, or broad EXECUTE).
  4. Run direct-access denial tests and confirm authorized backend flows still succeed.
  5. Re-scan after deployment and again after the next migration.

This turns security fixes into repeatable rollout mechanics instead of one-off emergency changes.

Incident breakdowns for Signed URLs (real scenarios)

Signed URL invoice download

Scenario: The billing page lists invoices and users need secure PDF downloads.

What failed: The bucket used to be public and URLs were guessable, so anyone could download invoices.

What fixed it: Make the bucket private and issue signed URLs server-side after verifying invoice ownership.

Why the fix worked: The backend gate enforces authorization and the signed URL expires quickly, so accidental sharing has limited impact.

Key takeaways:

  • Private buckets reduce accidental exposure.
  • Ownership checks should happen before signing.
  • Short TTLs reduce link abuse.
  • Add rate limiting for signing.

Read full example: Signed URL invoice download

Signed URL abuse + rate limiting

Scenario: Someone guessed object paths and repeatedly requested signed URLs.

What failed: The endpoint signed URLs without strict authorization or rate limiting.

What fixed it: Validate ownership, deny unknown paths, and rate-limit signing requests per user and IP.

Why the fix worked: Authorization blocks access to objects you don’t own and rate limiting makes brute force impractical.

Key takeaways:

  • Signed URLs are only as safe as the signing endpoint.
  • Reject unknown paths early.
  • Rate-limit both by user and by IP.
  • Monitor and alert on spikes.

Read full example: Signed URL abuse + rate limiting

Real-world examples of Signed URLs (and why they work)

Related terms

  • Supabase Storage Bucket Privacy → /glossary/supabase-storage-bucket-privacy
  • Service Role Key → /glossary/service-role-key

FAQ

Is Signed URLs enough to secure my Supabase app?

It’s necessary, but not sufficient. You also need correct grants, secure Storage/RPC settings, and a backend-only access model for sensitive operations.

What’s the quickest way to reduce risk with Signed URLs?

Remove direct client access to sensitive resources, enable/force RLS where appropriate, and verify via a repeatable checklist that anon/authenticated cannot query what they shouldn’t.

How do I verify the fix is real (not just a UI change)?

Attempt direct API queries using the same client credentials your app ships. If the database denies access (401/403) and your backend endpoints still work, your fix is effective.

Next step

Want a quick exposure report for your own project? Run a scan in Mockly to find public tables, storage buckets, and RPC functions — then apply fixes with verification steps.

Explore related pages

parent

Glossary

/glossary

sibling

Service Role Key

/glossary/service-role-key

sibling

Supabase Storage Bucket Privacy

/glossary/supabase-storage-bucket-privacy

cross

Supabase Storage signed URLs

/integrations/supabase-storage-signed-urls

cross

Signed URL invoice download

/examples/signed-urls/signed-url-invoice-download

cross

Pricing

/pricing