Repository navigation
fix(blob): validate and encode blob pathnames - #938
Merged
Merged
Conversation
Decode pathnames once in the storage layer and reject empty pathnames, control characters and `.`/`..` segments. Drivers no longer decode a second time. The fs driver validates paths and multipart upload IDs, and the s3 driver percent-encodes each key segment and escapes keys in batch delete requests. Amp-Thread-ID: https://ampcode.com/threads/T-01a131bf-0030-71a4-88a0-7d9184ccfdd4 Co-authored-by: Sebastien Chopin <[email protected]>
Includes the fix for unencoded storage keys in URL-based drivers (s3, http, cloudflare-kv-http, netlify-blobs, vercel-runtime-cache), used by hub:kv and hub:cache. Amp-Thread-ID: https://ampcode.com/threads/T-01a131bf-0030-71a4-88a0-7d9184ccfdd4 Co-authored-by: Sebastien Chopin <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
playground | 4112b49 | Commit Preview URL Branch Preview URL |
Oct 19 2026, 07:53 PM |
unstorage 1.17.6 rejects empty keys such as "/", which Nuxt 4.3 used for the prerender payload cache of the root page. Nuxt 4.6 uses "/.json" instead. Docs: migrate nuxt-og-image to v7 (required by unhead 3): rename the template with the .satori suffix, add satori and @resvg/resvg-js, use box-border to keep the text column width, pass title and description explicitly, and set the changelog post image with useSeoMeta. Amp-Thread-ID: https://ampcode.com/threads/T-01a131bf-0030-71a4-88a0-7d9184ccfdd4 Co-authored-by: Sebastien Chopin <[email protected]>
Nuxt 4.6 locks the dev server per project. Two test files start a dev server for the same fixture, and on Windows the first one can still be alive when the second starts. Amp-Thread-ID: https://ampcode.com/threads/T-01a131bf-0030-71a4-88a0-7d9184ccfdd4 Co-authored-by: Sebastien Chopin <[email protected]>
nuxt-og-image v7 reads fonts from @nuxt/fonts v1. @nuxt/ui still depends on @nuxt/fonts 0.x, so the docs register @nuxt/fonts explicitly to keep @nuxt/ui from installing its own copy. Amp-Thread-ID: https://ampcode.com/threads/T-01a131bf-0030-71a4-88a0-7d9184ccfdd4 Co-authored-by: Sebastien Chopin <[email protected]>
This was referenced Oct 20, 2026
vitehub-bot Bot
added a commit
to vite-hub/vitehub
that referenced
this pull request
Oct 20, 2026
* fix(blob): reject unsafe pathnames Port the pathname checks of nuxt-hub/core#938. Blob methods reject empty pathnames, control characters, and dot segments with BLOB_R0034 (statusCode 400). put() and multipart uploads check the final pathname after prefix and suffix, list() checks its prefix, handleUpload() stores file names without URL-decoding, and the native R2 signer rejects dot segments before new URL() can collapse them. * fix(blob): reject unpaired surrogate pathnames --------- Co-authored-by: vitehub-bot[bot] <320448255+vitehub-bot[bot]@users.noreply.github.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hardens blob pathname handling, bumps
unstorageto the release that fixes unencoded storage keys in URL-based drivers, and upgrades Nuxt to 4.6.2, which thatunstoragerelease requires for prerendering.Blob
Blob pathnames are often built from user input: route params passed to
blob.serve()/blob.get()/blob.del(), and uploaded file names inblob.handleUpload(). They were not validated, and each pathname was URL-decoded twice (once in the storage layer, once in the driver):fs(the default driver for Node hosting):..segments resolved outside the blob directory. This allowed reading, listing and writing arbitrary files, for exampleGET /api/blob/..%2F..%2Fpackage.json, or uploading a file named..%2Fevil.txt. A crafted multipartuploadIdcould also makeabort()recursively delete any directory.s3: keys were inserted into the request URL withencodeURI, so..,?and#could target another bucket (path-style endpoints), bucket-level APIs (del('?policy')sendsDELETE /bucket/?policy), or a different key. Batch delete also inserted keys into the XML body unescaped.Changes:
createBlobStoragedecodes each pathname exactly once and rejects empty pathnames, control characters, and./..segments (split on/and\) with400 Invalid blob pathname. The final pathname is checked afterprefixandaddRandomSuffixare applied, andlist()prefixes are checked too.fsvalidates pathnames itself and requires multipart upload IDs to be UUIDs.s3percent-encodes each key segment (RFC 3986, matching what aws4fetch signs), escapes keys in batch-delete XML, and no longer throws when listing keys that contain a literal%.unstorage
Bumps
unstorageto^1.17.6, which fixes the same class of issue in thes3,http,cloudflare-kv-http,netlify-blobsandvercel-runtime-cachedrivers.hub:kvauto-selectss3when theS3_*env vars are set, andhub:cachedefaults tovercel-runtime-cacheon Vercel. The lockfile change is limited tounstorageand its new[email protected]dependency (checked with[email protected] install --frozen-lockfile).Nuxt 4.6.2
[email protected]rejects keys that are empty after normalization, including"/". Nuxt 4.3 used"/"as the prerender payload cache key for the root page, so prerendering/failed with[unstorage] Invalid key: "/"(this broke the docs deployment). Nuxt 4.6 uses"/.json". Apps still on Nuxt 4.3.x that prerender/will hit the same error once they resolve[email protected].4.6.2, andvueis deduped to3.5.44(two Vue copies broke SSR).@nuxt/kitstays at^4.3.1, so the module's supported Nuxt range does not change.nuxt-og-imagev7. The template is renamed toOgImageDocs.satori.vue,satoriand@resvg/resvg-jsare added,box-borderkeeps the text column at its previous width, and pages passtitle/descriptionexplicitly because v7 no longer infers them. The changelog post image is set withuseSeoMeta({ ogImage }). The prerendered OG images for a docs page,/templatesand/changelogmatch production.@nuxt/uimoves to4.12.0(docs and playground) and the docs move to@nuxt/fontsv1, whichnuxt-og-imagev7 uses to read fonts.@nuxt/uistill depends on@nuxt/fonts0.x, so the docs register@nuxt/fontsexplicitly inmodules. OG images now use the site font (Public Sans) instead of the bundled Inter fallback.NUXT_APP_SECRET(at least 32 characters) in the Vercel project.Behavior changes
./..segments or control characters, or have invalid percent-encoding now throw a 400.s3: a pathname that still contains%XXafter the first decode is now stored under that literal key instead of being decoded again.+is now stored as+(it could previously be read as a space).fs/cloudflare-r2: pathnames containing%now read back the same object they were written to (putandgetpreviously disagreed).Tests
test/blob.test.ts: traversal over HTTP (read and upload) is rejected, and special characters (%,?,#, spaces) round-trip. These tests fail without the fix; the upload test writesevil.txtoutside the blob directory.test/blob.s3.test.ts: S3 request URLs stay inside the bucket's object path, and escaping pathnames are rejected before any request is sent.+,%,?,#,()!*, unicode and<&>. The old driver returned objects from another bucket there.