Start with permissions, then test the actual download paths
To test that a SaaS user cannot download another company's private files, use separate customer identities and a known file belonging to the other company. Exercise the application's download and signed-link endpoints as well as the underlying storage interface. A useful result records whether protected bytes, metadata or a reusable link escaped. It also includes a successful authorised download, so a broken download service cannot make every negative test appear to pass.
The pack below is a proposed acceptance procedure for a product using private object storage. The tenant names, file contents and example time limits are hypothetical. Adapt the permission matrix and expected responses to your application before running it. These are tests to perform, not claims that a particular Symaxx implementation has passed them.
Define what each request is supposed to allow
Choose two fictional organisations, Alpha and Beta. Create an ordinary user and an organisation administrator for each, plus an account with no membership. Give each organisation a disposable private document containing a distinct marker, such as "Beta test document". Record its object identifier and a hash of its bytes.
An organisation administrator is a customer role scoped to that organisation. It should not silently become the platform's unrestricted operator. Write down which roles may list files, read metadata, download bytes or create a share link. Include any deliberately shared documents as separate allow cases.
RLS applies the policies configured on database rows; it does not create a correct tenant policy automatically. Storage has its own object operations and policies to check. Privileged Storage service-key requests bypass RLS, so a successful administrative download says nothing about a customer's permissions. Supabase's Storage access-control guide explains this bypass, while its RLS guide explains how grants and policies combine.
Run customer tests with the actual customer session. If a backend endpoint uses a privileged client internally, test that endpoint too: it must authorise the caller before fetching bytes or creating a link. Keep privileged credentials out of browsers, request captures and the test report.
Separate authenticated downloads from signed links
Three URLs that look like file URLs can have different security properties. A public object URL exposes a public asset. An authenticated private download sends the user's access token and evaluates the applicable access policies. A signed download URL carries a bearer capability: possession of the URL can grant access during its usable lifetime.
Use the authenticated private endpoint for the cross-tenant permission test. Then test the application's signing endpoint separately. Alpha must not be able to ask the server to create a signed URL for Beta's private object, even if the signing server has unrestricted storage access.
Do not expect a valid signed URL to be bound to the browser that requested it. In a disposable test, copy a legitimately issued Beta link into Alpha's browser or an unauthenticated request. If it works while usable, that demonstrates the bearer-link behaviour. The authorisation failure to prevent is issuing or disclosing that link to Alpha without permission.
Supabase signs Storage URLs with a key separate from its Auth JWT signing key. Changing Auth keys does not revoke those links. Its download guidance describes authenticated downloads and signed URLs. Account logout or membership removal therefore requires a separate decision about links already issued.
If the product promises access checks for every download or immediate withdrawal of an individual's access, a transferable signed link may not meet that promise. Specify an authenticated delivery path and examine its cache behaviour rather than assuming a short expiry solves the requirement.
Test known identifiers and check for information leaks
Use Beta's exact test object identifier in Alpha's requests. Guessing is unnecessary: a file must remain protected even when its identifier is known. Hard-to-guess names can reduce discovery, but they cannot replace authorisation.
Try the same identifier in the download, metadata, listing and signed-link routes. Change only the organisation or object parameter while keeping Alpha's session. Include batch-download and export paths if the product offers them.
Before execution, establish the documented deny response for each endpoint. Depending on its design, that may be 401, 403 or a deliberately masked 404. An expired session and a logged-in user lacking permission are different cases. Do not require one universal status code or count a redirect to login as proof of tenant isolation.
Check the body as well as the status. A denied request must not expose the private file's bytes, filename, size, owner, preview, signed link or other protected metadata. Inspect relevant response headers too. An error page that includes a storage URL can defeat the intended denial.
OWASP's authorisation guidance recommends denying access by default and validating permissions on each request. Apply that principle across delivery paths, not only to the visible download button.
Measure expiry with warm and cold caches
Supabase's current Smart CDN guidance distinguishes signed-token expiry from response cache duration. A cached signed response can continue to be served after token expiry until its cache duration ends. Expiry alone therefore does not establish an immediate cutoff.
For a cold-cache case, issue a new signed URL and leave that exact URL unfetched until after expiry. For a warm-cache case, issue another URL, fetch it before expiry, and request the same URL again afterwards. Record issuance time, token expiry, object cache settings, request time, response status and whether the test marker was returned.
Use a fresh test client without a local cached copy to examine the CDN response. Test browser caching separately. Where available, record cache and age headers; a repeated request alone does not prove a particular CDN edge was warm. Do not alter the query string during the warm-cache test because that can change the cache key.
For deletion, use only disposable objects. Supabase documents deletion as invalidating cached object responses, with propagation potentially taking up to a minute. Measure the observed cutoff rather than promising immediate revocation. Deleting an organisation's document is not a normal method for removing one user's access while retaining the document for authorised colleagues.
A concrete private-file access test pack
The expected results below are proposed acceptance criteria. Replace "documented deny" with the endpoint's agreed response before the test run.
| ID | Setup and action | Expected result and evidence |
|---|---|---|
| TP1: Positive control | Beta's authorised user downloads Beta's document through the authenticated path. | Correct bytes and marker are returned. Record the hash and successful user context. |
| TP2: Cross-tenant download | Alpha requests Beta's known identifier through the same authenticated path. | Documented deny response; no private bytes, link or protected metadata. |
| TP3: Link creation | Alpha asks the application's signing endpoint to sign Beta's identifier. | Documented deny; no signed URL is created or returned to Alpha. Include backend paths using privileged clients. |
| TP4: Metadata and listing | Alpha requests Beta's metadata, listing, preview or batch-export entries. | No protected Beta entry or metadata is disclosed. Check response bodies and headers. |
| TP5: Organisation administrator | Alpha's customer administrator requests Beta's object. | Denied unless an explicit authorised sharing rule applies. Customer administration remains scoped. |
| TP6: No membership | A signed-in account with no organisation membership requests a private object. Repeat without a session. | Each receives its documented deny response. Keep the two cases distinct. |
| TP7: Bearer-link control | Beta creates an allowed signed URL; use it from Alpha or without a login before expiry. | It may return bytes as a bearer capability. Confirm the product's sharing policy permits this exposure. |
| TP8: Cold expired link | First fetch a previously unused signed URL after its expiry. | Origin rejects the expired capability; no protected bytes are returned. Record that it was not warmed. |
| TP9: Warm expired link | Fetch a signed URL before expiry, then repeat the exact URL afterwards. | Record whether cached bytes persist and when access ends. Compare with the cache configuration and product promise. |
| TP10: Removed membership | Remove Alpha's access, then test authenticated downloads and new link requests with the existing token and after refresh. | Behaviour matches the documented revocation design. Record stale-token access separately; no new unauthorised link is issued. |
| TP11: Disposable deletion | Warm a signed URL, delete its test object through Storage, then repeat requests. | Record invalidation propagation and final denial; do not require an instantaneous cutoff. |
| TP12: Session and role isolation | Repeat key cases in separate browsers or clients with only the intended customer token. | Evidence identifies the actual caller. No service key or platform-admin token substitutes for customer permissions. |
Run the pack as a repeatable procedure
Start with TP1 and stop if the authorised path is broken. Otherwise, a blanket service failure can hide an access-control defect. Run the deny cases with that same healthy path, then test signing and alternative delivery routes.
Use a result sheet with test ID, user role, active organisation, object identifier, endpoint, request time, expected result, actual status, returned-content check and evidence reference. Redact access tokens and signed URLs from shared reports. Keep a private evidence record only where necessary for troubleshooting.
When a case fails, preserve the request context and identify the missing check before changing policies. Retest the failed case and its positive control after repair. A change that blocks every tenant's downloads is not an acceptable isolation fix.
Test membership removal and privileged backend paths
A fresh login is not enough to test revocation. Supabase documents that authorisation information held in JWT claims can remain out of date until the token is refreshed. User-editable metadata is also unsuitable as trusted authorisation data. These qualifications appear in the RLS documentation.
For TP10, record when membership changed and test both the existing token and a refreshed session. If the application requires earlier revocation, define how its server checks current membership. Test already issued signed URLs separately; refreshing a login token does not remove their bearer capability.
Inspect routes that use administrative database or storage access. Include background exports, preview generation and support downloads where relevant. RLS alone cannot protect a route that bypasses it and trusts an organisation identifier supplied by the caller.
Worked hypothetical example
Beta owns a disposable PDF. Its ordinary user downloads it successfully and the returned hash matches the fixture. Alpha then requests the same object identifier with Alpha's token. The endpoint's agreed response is a masked 404, and inspection confirms no PDF bytes, filename or storage URL appeared.
Next, Alpha calls the signing endpoint. A 200 response containing a signed URL is a failure even if the authenticated download was correctly denied. The backend must check Alpha's permission before signing.
Finally, Beta creates a permitted link. A warm-cache request still returns the test marker after token expiry. Record this as observed cache behaviour, then compare it with the product's declared access promise. Do not relabel it as a successful immediate-revocation test.
Turn the results into a clear acceptance decision
Accept the file-access design only when the intended allow cases work, forbidden paths expose no protected content, and the measured link lifetime matches the product's stated policy. Keep unresolved cache or revocation gaps visible in the acceptance record.
If your business needs help specifying these checks, our SaaS development service can support the implementation discussion. For a scoped review of your private-file workflow, get in touch with the permission matrix and failed test evidence.
For wider planning, compare CMS versus custom development and include recurring verification in your website maintenance costs. The User Journey glossary can help describe the customer path being tested.
Frequently asked questions
Should another user be unable to open my signed URL?
A signed URL is a bearer capability, so another holder may be able to use it while usable. Test who can obtain or create it. For user-bound access, specify an authenticated download design rather than assuming the URL checks current membership.
Must every denied request return 403?
No. Agree the response for each endpoint before testing. A 401, 403 or intentionally masked 404 may be appropriate. Verify that protected bytes, links and metadata are absent; the status alone is insufficient evidence.
Does logout or membership removal revoke existing links?
Not automatically. Test authenticated access with existing and refreshed tokens, and test previously issued links separately. JWT claims may be stale, and Storage signing keys are separate from Auth keys.
Can deleting a file establish immediate revocation?
Do not promise that. Supabase documents cache invalidation propagation of up to a minute. Measure it with disposable objects. Deletion also removes the shared object for other users, so it is unsuitable as a routine per-user revocation mechanism.

