From d3ccdd4a0897e5d606921ca65f1a7d37b0c7e613 Mon Sep 17 00:00:00 2001 From: Kyle June Date: Fri, 25 Sep 2026 01:45:33 -0400 Subject: [PATCH] docs: a Host carrying a slash is not refused The path check compares the raw path in request.url with URL.pathname. When Host carries "/", Deno.serve builds a URL such as http://localhost/docs/identity whose raw and parsed paths agree, so the request is not refused and both routers route the same path. Only a Host carrying "?" or "\" shifts the path and is refused. Co-Authored-By: Claude Opus 5.5 --- docs/middleware.md | 7 ++++--- src/server.tsx | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/middleware.md b/docs/middleware.md index 110277f..74b001c 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -127,9 +127,10 @@ middleware in Hono while React Router, which matches the resolved path, ran the `/admin` loader, so `app.use("/admin/*", requireAdmin)` would never see it. The check compares the raw path with `URL.pathname`, so it also refuses, fail closed, bytes the parser percent-encodes rather than resolves — raw UTF-8 such -as `/café`, `"`, `<`, `>`, `` ` ``, `{`, `}` — and a `Host` header carrying `/`, -`?` or `\`. Browsers resolve and encode such paths before sending them; only a -hand-built request is refused. +as `/café`, `"`, `<`, `>`, `` ` ``, `{`, `}` — and a `Host` header carrying `?` +or `\`. A `Host` carrying `/` is not refused: the raw and parsed paths then +agree, so both routers route the same path. Browsers resolve and encode such +paths before sending them; only a hand-built request is refused. ### Common Patterns diff --git a/src/server.tsx b/src/server.tsx index 1708b79..8230590 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -59,7 +59,7 @@ function rawRequestPath(url: string): string { * can match one route's middleware while running another route's loader. The * raw string is compared with `URL.pathname`, so bytes the parser * percent-encodes rather than resolves (raw UTF-8, `"`, `<`, `>`, `` ` ``, `{`, - * `}`) and a `Host` carrying `/`, `?` or `\` are flagged too, fail closed. + * `}`) and a `Host` carrying `?` or `\` are flagged too, fail closed. */ function isUnresolvedPath(request: Request): boolean { return rawRequestPath(request.url) !== new URL(request.url).pathname; @@ -79,7 +79,8 @@ function isUnresolvedPath(request: Request): boolean { * middleware and run another route's loader. The same comparison also refuses, * fail closed, bytes the parser percent-encodes rather than resolves — raw * UTF-8 such as `/café`, `"`, `<`, `>`, `` ` ``, `{`, `}` — and a `Host` - * header carrying `/`, `?` or `\`. Browsers resolve and encode such paths + * header carrying `?` or `\`. A `Host` carrying `/` is not refused: the raw + * and parsed paths then agree, so both routers route the same path. Browsers resolve and encode such paths * before sending them, so only a hand-built request sees the refusal. Responses vary by `Accept` and * `X-Juniper-Route-Id` while retaining application cache variation. Route data * responses and redirects sent to data requests default to `Cache-Control: