Skip to content

node:http: honor insecureHTTPParser on the server - #33637

Open
robobun wants to merge 7 commits into
mainfrom
farm/6d006722/http-server-insecure-parser
Open

node:http: honor insecureHTTPParser on the server#33637
robobun wants to merge 7 commits into
mainfrom
farm/6d006722/http-server-insecure-parser

Conversation

@robobun

@robobun robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What

The http.createServer({ insecureHTTPParser: true }) option was validated and stored on the Server instance but never reached the request parser, so the lenient behaviours it is meant to enable were still rejected with the same clientError as strict mode. The client side already plumbed it through to llhttp; the server side (which parses with uWS, not llhttp) dropped it on the floor.

import http from "node:http";
import net from "node:net";
const srv = http.createServer({ insecureHTTPParser: true }, (req, res) => {
  console.log("served", req.url, req.headers["x-a"]);
  res.end("ok");
});
srv.on("clientError", e => console.log("clientError", e.code));
srv.listen(0, () => {
  net.connect(srv.address().port).end(
    "GET / HTTP/1.1\\r\\nHost: x\\r\\nX-A: one\\r\\n two\\r\\nConnection: close\\r\\n\\r\\n",
  );
});
// node v26.3.0:  served / "one two"
// bun (before):  clientError HPE_INVALID_HEADER_TOKEN
// bun (after):   served / "one two"

Fix

Thread the option through setServerCustomOptions and the uws_app_set_flags chain into the uWS request parser, and implement the two canonical lenient cases it is expected to enable:

  • obs-fold header continuation lines (RFC 7230 3.2.4): when a header value CRLF is followed by SP/HTAB the parser keeps scanning, then compacts the CRLF pairs out of the value span in place. Bytes after the value are untouched so the caller's consumed-byte accounting is unchanged. The resulting value is byte-identical to Node/llhttp (one two, not one two).
  • Content-Length + Transfer-Encoding: chunked (llhttp lenient_chunked_length): both headers are accepted, chunked framing wins per RFC 9112 6.3, and both headers remain visible in req.headers like Node. A Transfer-Encoding without chunked is still rejected since request body framing would otherwise be undefined here.

The default (strict) parse path is unchanged; the new branches only execute when the option is explicitly set.

Verification

New insecureHTTPParser describe in test/js/node/http/node-http.test.ts covers obs-fold (SP + HTAB, multi-line), CL+TE:chunked, and asserts the strict parser still rejects both. All assertions verified byte-identical under Node v26.3.0.

USE_SYSTEM_BUN=1 bun test test/js/node/http/node-http.test.ts -t insecureHTTPParser
  # 2 fail (HPE_INVALID_HEADER_TOKEN / HPE_INVALID_TRANSFER_ENCODING), 1 pass
bun bd test test/js/node/http/node-http.test.ts -t insecureHTTPParser
  # 3 pass
bun bd test test/js/node/http/node-http.test.ts -t Security
  # 19 pass (existing smuggling rejections unaffected)

The http.createServer insecureHTTPParser option was validated and stored
on the Server instance but never reached the request parser, so the
lenient behaviours it is meant to enable (obs-fold header continuation
lines and Content-Length together with Transfer-Encoding: chunked) were
still rejected with the same clientError as strict mode.

Thread the option through setServerCustomOptions and the uws_app_set_flags
chain into the uWS request parser and implement those two lenient cases:
obs-fold CRLF is dropped in place inside the header value span (bytes
after the value are untouched so consumed-byte accounting is unchanged),
and when both Content-Length and Transfer-Encoding: chunked are present
the request is accepted and chunked framing wins per RFC 9112 6.3. The
default (strict) parse path is unchanged.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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 reviews.

How do review 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 refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bbf661fe-59ed-49a8-9fbc-ddda71be7fb2

📥 Commits

Reviewing files that changed from the base of the PR and between c361141 and cef740f.

📒 Files selected for processing (3)
  • packages/bun-uws/src/HttpParser.h
  • src/runtime/server/server_body.rs
  • test/js/node/http/node-http-insecure-parser.test.ts

Walkthrough

Introduces an insecureHTTPParser option threaded from JS HTTP server configuration through native C++ bindings, Rust server runtime, uWS FFI, and into the uWS HTTP parser. When enabled, it permits obs-fold header continuations and relaxes Transfer-Encoding/Content-Length validation, defaulting via isLenient() when unset, with new tests.

Changes

insecureHTTPParser end-to-end plumbing

Layer / File(s) Summary
uWS flag storage and propagation
packages/bun-uws/src/HttpContextData.h, packages/bun-uws/src/App.h, packages/bun-uws/src/HttpContext.h
Adds insecureHTTPParser to HttpFlags, extends TemplatedApp::setFlags to set it, and forwards it into consumePostPadded.
HttpParser leniency logic
packages/bun-uws/src/HttpParser.h
Threads insecureHTTPParser through getHeaders, fenceAndConsumePostPadded, and consumePostPadded; implements obs-fold header continuation handling and conditional Transfer-Encoding/Content-Length validation.
C++/Rust FFI binding updates
src/uws_sys/libuwsockets.cpp, src/uws_sys/App.rs, src/runtime/server/server_body.rs, src/runtime/server/mod.rs, src/jsc/bindings/NodeHTTP.cpp
Extends uws_app_set_flags, App::set_flags, server_set_app_flags_/shim, and Server__setAppFlags/jsHTTPSetCustomOptions to carry insecure_http_parser through each binding layer.
JS server option defaulting and tests
src/js/internal/http.ts, src/js/node/_http_server.ts, test/js/node/http/node-http-insecure-parser.test.ts
Updates the internal binding signature, defaults insecureHTTPParser via isLenient() when unset, and adds tests covering obs-fold and Content-Length/Transfer-Encoding leniency behavior.

Possibly related PRs

  • oven-sh/bun#29427: Both PRs modify packages/bun-uws/src/HttpParser.h parsing logic in/around fenceAndConsumePostPadded().
  • oven-sh/bun#33072: Both PRs modify the same uWS flag-plumbing path—TemplatedApp::setFlags/HttpContextData::HttpFlags and HttpContext.h's consumePostPadded call—adding different boolean options.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main server-side insecureHTTPParser change.
Description check ✅ Passed The description includes the required purpose and verification details, though the section headings differ from the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@github-actions github-actions Bot added the claude label Jul 7, 2026
@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 10:00 AM PT - Jul 7th, 2026

@robobun, your commit cef740f has some failures in Build #69853 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 33637

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

bun-33637 --bun

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. node:http/https/http2: raise Node v26.3.0 compat to ~94%, sync the upstream suites, and fix the Windows/macOS transport-layer teardown bugs they exposed #32488 - Also threads insecureHTTPParser through the uWS server-side parser chain (setFlags, HttpContextData, getHeaders, consumePostPadded) and wires _http_server.ts to pass the flag at listen time; the main non-overlapping piece in this PR is obs-fold header continuation support

🤖 Generated with Claude Code

@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Re the duplicate flag: #32488 threads the same insecureHTTPParser flag through the setFlags/HttpContextData/getHeaders chain, so the plumbing here conflicts textually with that branch.

The lenient behaviors implemented do not overlap though:

Happy to rebase on top of #32488, fold the obs-fold + CL+TE pieces into that branch, or leave this as the focused fix that lands first and gets reconciled when the omnibus merges, whichever is preferred.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bun-uws/src/HttpParser.h (1)

787-816: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return shortRead() for an ambiguous obs-fold boundary

In insecureHTTPParser mode, \r\n at the end of the current buffer is still ambiguous: the next byte may be SP/HTAB. Falling through here makes the same request parse differently depending on chunk boundaries, so this path should wait for one more byte instead of treating it as the header terminator.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bun-uws/src/HttpParser.h` around lines 787 - 816, The obs-fold
handling in HttpParser.h is treating a trailing CRLF at the end of the current
buffer as a completed header terminator in insecureHTTPParser mode, which makes
parsing depend on chunk boundaries. Update the header-value scan around
tryConsumeFieldValue and the obs-fold check so that when CRLF is seen but the
next SP/HTAB byte is not yet available, the parser returns shortRead() instead
of breaking as if the header ended; keep the existing maxHeaderSize and
invalid-token error behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/server/server_body.rs`:
- Around line 3693-3719: The error strings in the server flag-setting path are
stale and reference the wrong options, so update the messages in the `set_flags`
flow to match what this routine actually configures. In the `server_body.rs`
handler around the `handle!` macro and the final `Err(global.throw(...))`,
replace the `requireHostHeader` and `timeout` wording with a single consistent
message that reflects setting server flags such as
`use_strict_method_validation` and `insecure_http_parser`, keeping the wording
aligned across all early-return and fallback error paths.

In `@test/js/node/http/node-http.test.ts`:
- Around line 2047-2126: The new insecureHTTPParser test cases in the
`describe("insecureHTTPParser")` block should be made concurrent since each
`runServer` call uses its own ephemeral server and socket I/O, so convert the
individual `test(...)` cases to `test.concurrent(...)` to match repo
conventions. Also add a dedicated case in this block for the leniency claim
around `Transfer-Encoding` without `chunked` by exercising `runServer(true,
...)` with a non-chunked transfer-encoding value and asserting it is still
rejected, alongside the existing `clientError` expectations, to lock in the
`HttpParser` behavior.

---

Outside diff comments:
In `@packages/bun-uws/src/HttpParser.h`:
- Around line 787-816: The obs-fold handling in HttpParser.h is treating a
trailing CRLF at the end of the current buffer as a completed header terminator
in insecureHTTPParser mode, which makes parsing depend on chunk boundaries.
Update the header-value scan around tryConsumeFieldValue and the obs-fold check
so that when CRLF is seen but the next SP/HTAB byte is not yet available, the
parser returns shortRead() instead of breaking as if the header ended; keep the
existing maxHeaderSize and invalid-token error behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4baac2d7-dbe0-4efb-9358-99cc6801caf5

📥 Commits

Reviewing files that changed from the base of the PR and between 3f5d816 and c361141.

📒 Files selected for processing (12)
  • packages/bun-uws/src/App.h
  • packages/bun-uws/src/HttpContext.h
  • packages/bun-uws/src/HttpContextData.h
  • packages/bun-uws/src/HttpParser.h
  • src/js/internal/http.ts
  • src/js/node/_http_server.ts
  • src/jsc/bindings/NodeHTTP.cpp
  • src/runtime/server/mod.rs
  • src/runtime/server/server_body.rs
  • src/uws_sys/App.rs
  • src/uws_sys/libuwsockets.cpp
  • test/js/node/http/node-http.test.ts

Comment thread src/runtime/server/server_body.rs
Comment thread test/js/node/http/node-http.test.ts Outdated
@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Re the outside-diff HttpParser.h finding ("return shortRead() for an ambiguous obs-fold boundary"): not an issue in practice because getHeaders is stateless. It only commits when the full \r\n\r\n terminator has been seen; any prefix short of that returns shortRead() and is re-parsed from scratch over the accumulated fallback buffer. When the obs-fold check's postPaddedBuffer + 2 < end guard fails, the code falls through to the existing end-of-header check, which hits the fence bytes at end[0]='\r', end[1]='a' and returns shortRead() on every such boundary.

Verified by sending the same obs-folded request split at each of the four interesting boundaries (after the CR, after the LF, after the SP, and unsplit); all four parse to x-a = "one two".

robobun added 2 commits July 7, 2026 09:56
node-http.test.ts carries an unrelated non-hermetic proxy test that
fails in sandboxed environments; split the new coverage into its own
topic-specific file like the rest of this directory.
@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: the diff itself is green. The new node-http-insecure-parser.test.ts passes on every lane that has run it, and the existing HTTP security/smuggling suites pass unchanged.

The remaining red is unrelated to this change:

  • build 69802: darwin-26-aarch64-test-bun never ran any tests; the lane failed with "buildkite-agent artifact download timed out after 120s" before the binary arrived.
  • build 69818: debian-13-x64-asan hit a LeakSanitizer report in vendor/lsquic (lsquic_mm_get_packet_out) via test/js/web/fetch/fetch-http3-client.test.ts. That is the HTTP/3 QUIC client; this PR only touches the HTTP/1.1 server request parser in packages/bun-uws. The same test passed on the same lane in build 69802 and on recent main builds (69776, 69709).

Ready for review; needs a maintainer to merge past the unrelated flake (or rerun the x64-asan shard).

Comment thread packages/bun-uws/src/HttpParser.h
When getHeaders compacts an obs-fold value in place and then returns
shortRead (final CRLF-CRLF not yet received), the mutated bytes are
appended to the fallback buffer and re-parsed. The stale tail bytes
after the write cursor would then leak into the header value. Fill that
tail with SP so the re-parse sees the same value (the trailing SP is
removed by the existing whitespace trim).
Comment thread packages/bun-uws/src/HttpParser.h
When a folded header's value scan stops at a CRLF whose following byte
has not yet arrived, the parser cannot tell whether the fold continues.
Compacting there and then short-reading leaves SP-fill bytes that a
later continuation folds into the middle of the value. Return shortRead
before mutating the buffer in that case; the SP-fill is still needed for
re-parses triggered by a later header.

@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.

Both fragmentation issues I flagged are addressed in 57e71d3 and cef740f and I found nothing new this pass, but the in-place buffer mutation in getHeaders() plus the relaxed CL+TE guard are subtle enough that a maintainer familiar with the uWS parser should sign off.

Extended reasoning...

Overview

This PR threads insecureHTTPParser from http.createServer options through the JS→C++→Rust→uWS FFI chain into HttpParser.h, and implements two lenient behaviours there: RFC 7230 obs-fold header continuation (with in-place value compaction) and tolerating Content-Length alongside Transfer-Encoding: chunked. Twelve files touched across packages/bun-uws, src/uws_sys, src/runtime/server, src/jsc/bindings, src/js/node, plus a new dedicated test file.

Security risks

This is squarely security-sensitive: it modifies the HTTP/1.1 request parser's header-field scanning and the Transfer-Encoding/Content-Length conflict check — both canonical request-smuggling surfaces. The obs-fold implementation is the first place getHeaders() mutates its input buffer, breaking the prior invariant that a shortRead() return leaves the buffer untouched for the fallback re-parse. That invariant break already produced two client-controllable header-corruption bugs during review (both now fixed via the shortRead guard + SP-fill in cef740f). The lenient branches are gated on the flag and default off, and the strict path is byte-for-byte unchanged, so the default posture is unaffected — but the lenient path itself needs a careful human read.

Level of scrutiny

High. packages/bun-uws/src/HttpParser.h is the hot-path parser for every node:http/Bun.serve request; the fallback/re-parse machinery is subtle and this PR intentionally interacts with it in a new way. Two rounds of correctness fixes were needed to get the fragmentation semantics right. A maintainer with context on the fence-byte / fallback-buffer design should confirm the compaction is now fully idempotent under all shortRead orderings.

Other factors

  • Overlaps textually with open PR #32488 (same setFlags/HttpContextData/getHeaders plumbing, different lenient behaviour) — merge order needs a decision.
  • Test coverage is good: obs-fold (SP + HTAB), CL+TE:chunked, strict-mode rejection, and the two split-boundary regressions from my earlier reviews. The setTimeout(r, 20) between socket writes is load-sensitive but consistent with the repo's TCP-yield convention for this test class.
  • CI on the diff itself is green per the author; remaining red is an unrelated lsquic LSan flake.

@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Build 69853 at cef740f shows the same pattern: the only hard failure is darwin-26-aarch64-test-bun with "buildkite-agent artifact download timed out after 120s" before any test ran (third consecutive build on this branch hit by that lane; also 69802 and 69841). All other annotations are warning-flaky on unrelated Windows install / bake / zlib tests; nothing mentions node-http-insecure-parser.test.ts or the uWS parser.

Both review rounds on the obs-fold compaction are addressed in 57e71d3 and cef740f. Ready for maintainer review.

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