Skip to content

Commit d21d93c

Browse files
pi0claude
andcommitted
fix(request): shadow parsed _url in requestWithURL proxy
The proxy returned by requestWithURL only overrode the url string, so the runtime-parsed req._url object (srvx node) leaked through to consumers. A mounted h3-based fetch handler then reused the original un-stripped URL instead of re-parsing the overridden url, returning 404 on Node while working on web runtimes. Co-Authored-By: Claude Fable 5 <[email protected]>
1 parent 8a380f1 commit d21d93c

2 files changed

Lines changed: 14 additions & 1 deletion

File tree

src/utils/request.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import type { ServerRequest } from "srvx";
1818
* Avoids cloning the original request (no `new Request()` allocation).
1919
*/
2020
export function requestWithURL(req: ServerRequest, url: string): ServerRequest {
21-
const cache: Record<string | symbol, unknown> = { url };
21+
// Shadow `_url` too: the runtime-parsed URL object reflects the original
22+
// request URL and consumers must re-parse the overridden `url` instead.
23+
const cache: Record<string | symbol, unknown> = { url, _url: undefined };
2224
return new Proxy(req, {
2325
get(target, prop) {
2426
if (prop in cache) return cache[prop];

test/mount.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ describeMatrix("mount", (t, { it, expect, describe }) => {
2929
expect(res2.status).toBe(403);
3030
});
3131

32+
it("strips base for h3-based fetch handlers when runtime provides req._url", async () => {
33+
const subApp = new H3();
34+
subApp.get("/hello", () => "sub");
35+
// Passing the bound fetch function takes the generic fetch-handler path,
36+
// so the sub-app re-parses the proxied request instead of sharing routes.
37+
t.app.mount("/sub", subApp.fetch);
38+
const res = await t.fetch("/sub/hello");
39+
expect(res.status).toBe(200);
40+
expect(await res.text()).toBe("sub");
41+
});
42+
3243
it("works with compat object", async () => {
3344
t.app.mount("/test", {
3445
fetch: (req: Request) => new Response(new URL(req.url).pathname),

0 commit comments

Comments
 (0)