Skip to content

http: accept asterisk-form request targets - #32811

Closed
robobun wants to merge 3 commits into
mainfrom
farm/b7ae6fb8/http-asterisk-form-target
Closed

robobun wants to merge 3 commits into
mainfrom
farm/b7ae6fb8/http-asterisk-form-target

Conversation

@robobun

@robobun robobun commented Jun 27, 2026 •

Copy link
Copy Markdown
Collaborator

Repro

OPTIONS * HTTP/1.1 (the asterisk-form request target, RFC 9112 3.2.4) is answered with 400 Bad Request by the parser and never reaches the request handler. Node.js delivers it with req.url === "*".

const http = require("http"), net = require("net");
const server = http.createServer((req, res) => {
  console.log("handler:", req.method, JSON.stringify(req.url));
  res.end("ok");
});
server.listen(0, () => {
  const c = net.connect(server.address().port);
  c.on("data", d => console.log("client saw:", JSON.stringify(d.toString().split("\r\n")[0])));
  c.write("OPTIONS * HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n");
});
node: handler: OPTIONS "*"
      client saw: "HTTP/1.1 200 OK"
bun:  client saw: "HTTP/1.1 400 Bad Request"

Bun.serve rejects it the same way (same parser).

Cause

consumeRequestLine in packages/bun-uws/src/HttpParser.h only admits three target shapes: origin-form (a target beginning with /), CONNECT's authority-form, and proxy-style absolute URLs (http://... / https://...). Anything else falls through to HTTP_HEADER_PARSER_ERROR_INVALID_REQUEST and a 400, so asterisk-form never parses.

Fix

Two parts.

consumeRequestLine accepts * as the first character of a request-target, as a fourth form in the existing cascade, checked after CONNECT so that CONNECT * HTTP/1.1 keeps its tunnel classification. Like Node (llhttp), any method may carry an asterisk-form target and it is delivered verbatim. The origin-form fast path is unchanged.

getUrlForRouting() normalizes asterisk-form targets to / before routing. HttpRouter::getUrlSegment assumes an origin-form URL and unconditionally strips the leading byte, so routing a verbatim *admin would segment it identically to /admin and match a static Bun.serve route registered as /admin, a route-confusion vector. That aliasing was verified against an intermediate build carrying only the parser change (GET *admin invoked the /admin handler, where it was 400-rejected before); with the normalization an asterisk-form target can only route as the root. Nothing else reads getUrlForRouting(): both node:http's req.url and Bun.serve's Request.url come from getFullUrl(), so they stay verbatim. Credit to review for catching this: an earlier revision routed the target verbatim.

GET /admin, absolute-form, CONNECT authority-form, and non-asterisk invalid targets (GET badtarget) are all unchanged.

Verification

test/js/node/http/node-http-asterisk-form-target.test.ts:

  • OPTIONS * and GET * on a node:http server assert req.url === "*" and the 200 OK status line on the wire; *?a=1 is delivered verbatim.
  • CONNECT * HTTP/1.1 asserts the connect event fires with req.url === "*" and that bytes written after the handshake arrive as opaque tunnel data rather than being parsed as a second request.
  • OPTIONS * reaches a Bun.serve fetch() handler.
  • Route-confusion regression: GET *, GET *admin, and GET */admin against a server with a static /admin route all reach the /* fallback, never /admin.

On an unfixed build 5 of 6 fail immediately with Received: "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"; the CONNECT * test guards the cascade ordering and already passes on main. All 6 pass with the fix. The existing proxy-style, CONNECT, malformed-method, and header-overflow parser tests are unaffected.

The request-line parser only accepted targets beginning with "/"
(origin-form), plus CONNECT's authority-form and proxy-style absolute
URLs, so `OPTIONS * HTTP/1.1` was rejected with 400 Bad Request before
it ever reached a handler.

Accept "*" as the start of a request-target alongside "/". RFC 9112
3.2.4 defines asterisk-form for server-wide OPTIONS requests, and
Node.js (llhttp) delivers such requests verbatim (`req.url === "*"`)
for any method, so Bun does the same. Routing and Request.url
construction already handle non-slash targets the same way they do
for CONNECT's authority-form.
@coderabbitai

coderabbitai Bot commented Jun 27, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 23 minutes and 24 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 89d53a89-4a62-4447-b218-0b5fdd97efb8

📥 Commits

Reviewing files that changed from the base of the PR and between df92f8f and 924e633.

📒 Files selected for processing (2)
  • packages/bun-uws/src/HttpParser.h
  • test/js/node/http/node-http-asterisk-form-target.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jun 27, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:29 PM PT - Jun 26th, 2026

❌ @robobun, your commit 924e633 has some failures in Build #65298 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 32811

That installs a local version of the PR into your bun-32811 executable, so you can run:

bun-32811 --bun

Comment thread test/js/node/http/node-http-asterisk-form-target.test.ts Outdated
Comment thread packages/bun-uws/src/HttpParser.h Outdated
Comment thread packages/bun-uws/src/HttpParser.h Outdated
HttpRouter assumes an origin-form target and strips the leading byte, so
routing a verbatim "*admin" target segmented it identically to "/admin"
and matched the static Bun.serve route. getUrlForRouting() now returns
"/" for asterisk-form targets; getFullUrl() still exposes them verbatim
to node:http.

Check isConnect before isAsteriskForm so `CONNECT * HTTP/1.1` is still
classified as a CONNECT tunnel rather than as asterisk-form.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — 924e633 addresses both issues I raised (asterisk-form now routes as / so it can't alias static routes, and CONNECT is detected before the asterisk-form check), with regression tests for each. I'd still like a human to sign off given this is the request-line parser.

Extended reasoning...

Overview

This PR teaches consumeRequestLine in packages/bun-uws/src/HttpParser.h to accept the RFC 9112 §3.2.4 asterisk-form request-target (OPTIONS * HTTP/1.1), which was previously rejected with 400 before reaching any handler. It also adds a guard in getUrlForRouting() so asterisk-form targets route as / (only the catch-all matches), and reorders the request-line classification so CONNECT is detected before asterisk-form. A new test file covers OPTIONS * / GET * / *?a=1 on node:http, CONNECT * tunnel-mode, and the Bun.serve static-route-aliasing case.

Prior feedback now addressed

My earlier review on 770b131 flagged (1) a route-confusion vector where GET *admin would alias a static /admin route in Bun.serve, and (2) a regression where CONNECT * lost tunnel mode. Commit 924e633 fixes both exactly as suggested — getUrlForRouting() returns "/" for any *-prefixed target, and isConnect is computed before isAsteriskForm so the short-circuit no longer mis-classifies CONNECT *. Both fixes have dedicated regression tests in the new test file. The bug-hunting pass on this revision found nothing further.

Security risks

The HTTP request-line parser is the front door for every request to node:http and Bun.serve; mistakes here become request-smuggling, route-confusion, or ACL-bypass vectors. The earlier revision of this very PR introduced a route-aliasing path that is now closed, which is exactly why I'd like human eyes on the final shape before it lands.

Level of scrutiny

High. packages/bun-uws/src/HttpParser.h is shared by every HTTP server path in Bun and the change widens the set of accepted request targets. The diff is small and the logic now reads cleanly (origin-form → CONNECT → asterisk-form → absolute-form, mutually exclusive), but parser changes of this kind warrant a maintainer sign-off rather than a bot approval.

Other factors

No CODEOWNERS entry covers this path. CI on the previous push (770b131) reported failures and I don't see a status for 924e633 yet, so it's worth confirming the suite is green before merge.

@robobun

robobun commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

CI status on 924e633: this PR's tests (test/js/node/http/node-http-asterisk-form-target.test.ts) pass on all 279 green lanes and do not appear in any failure. Every red lane is unrelated to the diff.

  • Both darwin 26 aarch64 - test-bun lanes: buildkite-agent artifact download timed out after 120s for step 'darwin-aarch64-build-bun', so they ran no tests.
  • test/js/bun/sourcemap/internal-sourcemap-roundtrip.test.ts fails on all three Windows lanes: line 444 asserts sources: ["../in.js"] and receives "..\in.js" on Windows. That assertion was introduced by 990be52 (Don't panic generating a sourcemap for a source ending in a truncated UTF-8 sequence #32774), which is already in this PR's merge base (df92f8f), so it fails identically for anything based on current main. A re-run cannot make it green.
  • test/cli/hot/hot.test.ts and test/napi/napi.test.ts on individual Windows lanes: flake; a different set of Windows tests failed on the previous run of this branch.

Not pushing a retrigger, since the sourcemap failure is deterministic on Windows rather than a flake.

@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-06-27, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main.

@robobun robobun closed this Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant