diff --git a/.changeset/fuzzy-flowers-kneel.md b/.changeset/fuzzy-flowers-kneel.md new file mode 100644 index 000000000000..8710851dda18 --- /dev/null +++ b/.changeset/fuzzy-flowers-kneel.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +fix: Prevent `wrangler pages dev` from serving asset files outside of the build output directory diff --git a/fixtures/pages-simple-assets/tests/index.test.ts b/fixtures/pages-simple-assets/tests/index.test.ts index b2b82baec7e7..c71e305fdeda 100644 --- a/fixtures/pages-simple-assets/tests/index.test.ts +++ b/fixtures/pages-simple-assets/tests/index.test.ts @@ -22,6 +22,18 @@ describe.concurrent("Pages Functions", () => { expect(text).toContain("Hello, world!"); }); + it("doesn't escape out of the build output directory", async ({ expect }) => { + let response = await fetch(`http://${ip}:${port}/..%2fpackage.json`); + let text = await response.text(); + expect(text).toContain("Hello, world!"); + + response = await fetch( + `http://${ip}:${port}/other-path%2f..%2f..%2fpackage.json` + ); + text = await response.text(); + expect(text).toContain("Hello, world!"); + }); + it("doesn't redirect to protocol-less URLs", async ({ expect }) => { { const response = await fetch( diff --git a/packages/wrangler/src/miniflare-cli/assets.ts b/packages/wrangler/src/miniflare-cli/assets.ts index a0c43eeae7fa..bdf86cbdeddb 100644 --- a/packages/wrangler/src/miniflare-cli/assets.ts +++ b/packages/wrangler/src/miniflare-cli/assets.ts @@ -1,5 +1,5 @@ import { existsSync, lstatSync, readFileSync } from "node:fs"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; import { createMetadataObject } from "@cloudflare/pages-shared/metadata-generator/createMetadataObject"; import { parseHeaders } from "@cloudflare/pages-shared/metadata-generator/parseHeaders"; import { parseRedirects } from "@cloudflare/pages-shared/metadata-generator/parseRedirects"; @@ -89,6 +89,7 @@ async function generateAssetsFetch( log: Logger, tre: boolean ): Promise { + directory = resolve(directory); // Defer importing miniflare until we really need it // NOTE: These dynamic imports bring in `global` type augmentations from @@ -174,7 +175,10 @@ async function generateAssetsFetch( xServerEnvHeader: "dev", logError: console.error, findAssetEntryForPath: async (path) => { - const filepath = join(directory, path); + const filepath = resolve(join(directory, path)); + if (!filepath.startsWith(directory)) { + return null; + } if ( existsSync(filepath) &&