@vercel/blob: presign() decodes the delegation token with atob, so any non-ASCII pathname throws a false scope mismatch
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 68/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- nodejs, typescript
Research direction
Start with dist/chunk-YYMLUMXS.js and the base64UrlDecodeToString entry point, then run the supplied Node 22 reproduction. Update the delegation-token decoding so UTF-8 pathnames remain unchanged in the atob and Buffer environments, and verify ASCII and non-ASCII pathnames no longer produce a false scope mismatch.
Written by the indexing model from the issue text.
Description
Uploading a file whose name holds any non-ASCII character fails with a scope
mismatch that isn't real. The path passed in and the path in the token are
the same string. The token is only being read back wrong.
Danish, German, Spanish, French, Greek, Cyrillic, Japanese and emoji names
all hit it, so those users cannot upload at all.
Reproducing
No account, no store, no network. Against 2.8.0 on Node 22:
import { presignUrl } from '@vercel/blob';
const pathname = 'uploads/Skærmbillede.png';
const claims = { pathname, operations: ['put'], validUntil: Date.now() + 600_000 };
const delegationToken =
Buffer.from(JSON.stringify(claims), 'utf8').toString('base64url') + '.sig';
await presignUrl({ delegationToken, clientSigningToken: 'cst' }, { pathname, operation: 'put' });
uploads/plain-ascii.png accepted
uploads/Skærmbillede.png expected `uploads/Skærmbillede.png`, got `uploads/Skærmbillede.png`
uploads/Снимок.png expected `uploads/Снимок.png`, got `uploads/Снимок.png`
uploads/スクリーンショット.png expected `uploads/ã¹ã¯ãªã¼ã³ã·ã§ãã.png`, got `uploads/スクリーンショット.png`
Cause
presign() compares options.pathname against the pathname it decodes out
of the delegation token, and the decoder prefers atob:
// dist/chunk-YYMLUMXS.js, base64UrlDecodeToString
if (typeof atob === "function") {
return atob(base64);
}
if (typeof Buffer !== "undefined") {
return Buffer.from(base64, "base64").toString("utf8");
}
atob returns one character per byte, so UTF-8 comes back as mojibake. Node
18 and later define atob globally, so the correct Buffer branch never
runs on the server, which is where handleUploadPresigned calls this.
The store is fine. POST a non-ASCII pathname to /signed-token and the
delegation token comes back holding it exactly.
Suggested fix
Decode as UTF-8 in both branches, by preferring Buffer where it exists or
by running the atob result through TextDecoder:
new TextDecoder().decode(Uint8Array.from(atob(base64), c => c.charCodeAt(0)))
Affects 2.6.1 through 2.8.0, the current latest; base64UrlDecodeToString is
unchanged across them. The older handleUpload client token flow is fine,
since it never decodes a delegation token.
- Dominant language
- TypeScript
- Stars
- 595
- Forks
- 101
- Avg merge
- 2h 24m
- Merged PRs (30d)
- 1
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from vercel/storage
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 3/5 1-2 days Newbie friendliness 58/100
-
Difficulty 3/5 1-2 days Newbie friendliness 68/100
Similar issues
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100