Skip to content

fix(storage): avoid double-encoding download filename in signed URLs#2468

Open
i-anubhav-anand wants to merge 1 commit into
supabase:masterfrom
i-anubhav-anand:fix/storage-signed-url-download-encoding
Open

fix(storage): avoid double-encoding download filename in signed URLs#2468
i-anubhav-anand wants to merge 1 commit into
supabase:masterfrom
i-anubhav-anand:fix/storage-signed-url-download-encoding

Conversation

@i-anubhav-anand

@i-anubhav-anand i-anubhav-anand commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

🔍 Description

What changed?

createSignedUrl and createSignedUrls build the download / cacheNonce query with URLSearchParams (which already percent-encodes), then wrapped the entire URL — including that query — in encodeURI(...). encodeURI does not skip an existing %, so every %XX produced by URLSearchParams was re-encoded to %25XX, double-encoding the download filename.

This PR encodeURIs only the path/token portion and appends the already-encoded query string raw — matching the correct sibling methods getPublicUrl and download.

- const signedUrl = encodeURI(
-   `${this.url}${data.signedURL}${queryString ? `&${queryString}` : ''}`
- )
+ const base = encodeURI(`${this.url}${data.signedURL}`)
+ const signedUrl = queryString ? `${base}&${queryString}` : base

(and the analogous change in createSignedUrls).

Why was this change needed?

The double-encoding corrupts any download filename containing characters that URLSearchParams percent-encodes — &, non-ASCII, +, #, ,, etc.

download value query before (buggy) server decodes to query after (fixed) server decodes to
a&b.png download=a%2526b.png a%26b.png download=a%26b.png a&b.png

The storage server route src/http/routes/object/getSignedObject.ts URL-decodes the download query value once (Fastify) and uses it verbatim as the Content-Disposition filename, so the client must send exactly one encoding layer — which URLSearchParams already provides. The sibling getPublicUrl (and download) already do this correctly; createSignedUrl/createSignedUrls were the inconsistent ones.

🔄 Breaking changes

  • This PR contains no breaking changes

📋 Checklist

📝 Additional notes

Fail-before / pass-after — added signed-url-download-encoding.test.ts (Docker-free, custom-fetch capture) covering both signed-url methods, plus a contrast assertion that getPublicUrl already single-encodes the same filename:

  • Before fix:Expected substring "download=a%26b.png", Received "...&download=a%2526b.png" (both createSignedUrl and createSignedUrls)
  • After fix: ✅ all 3 pass; existing Docker-free storage unit suites stay green (66 passed)

@i-anubhav-anand i-anubhav-anand requested review from a team as code owners June 23, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant