Repository navigation
Conversation
The page carries the raw output name, for example /my img-24bdys0j.png. A browser percent-encodes the path before the request, so it asks for /my%20img-24bdys0j.png. The static route was keyed on the raw bytes and the request got a 404. The production build of an HTML import and the manifest of a compiled executable now register each output under the WHATWG path percent-encoded form of its path.
|
Warning Review limit reached
On-demand reviews are free for the next 14 days. After that, they cost $0.25 per reviewed file. Or wait 3 minutes for your next included review. View limit detailsLimit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (5)
Comment |
|
Updated 8:21 AM PT - Sep 6th, 2026
❌ @robobun, your commit e69e210 has 2 failures in
The baseline build contains instructions not available on Static scan violations
|
|
Status: reproduced on bun 1.4.3 with the commands in the Notes block. Fixed and tested on this branch. Waiting for CI. |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline finding, I checked needs_escape against the WHATWG URL path percent-encode set — it matches exactly (C0 controls via < 0x20, >U+007E via >= 0x7F, plus space " # < > ? ` { }), and both registration sites (HTMLBundle.rs and server_body.rs) are covered.
Extended reasoning...
The core correctness question is whether the escape set matches what browsers actually send, and it does — the WHATWG path percent-encode set is the C0-control set (U+0000–U+001F and >U+007E) plus space, ", #, <, >, ?, `, {, }; the byte < 0x20 || byte >= 0x7F || matches!(...) predicate is byte-for-byte that set. Both call sites that register bundler outputs as static routes now go through the helper, and the Cow::Borrowed fast path means ASCII-clean names (the overwhelming common case) allocate nothing. The tests cover both the runtime Bun.serve production path and the compiled-executable path across the existing backend loop, and assert the encoded pathname the browser would produce.
| let mut out = Vec::with_capacity(path.len() + 16); | ||
| for &byte in path { | ||
| if needs_escape(byte) { | ||
| let hex = bun_core::fmt::hex2_upper(byte); |
There was a problem hiding this comment.
🟡 nit (optional): bun_core::fmt::hex2_upper is a #[doc(hidden)] compat alias (fmt.rs:2858 "compat aliases (pre-dedup names)"); the canonical helper is hex_byte_upper. Fix: call bun_core::fmt::hex_byte_upper(byte) so new code doesn't add a caller to the alias being phased out.
Extended reasoning...
src/bun_core/fmt.rs:2858-2863 marks hex2_upper as #[doc(hidden)] under a "compat aliases (pre-dedup names)" banner and delegates to hex_byte_upper at line 2851 (whose doc even says "Used by percent-encoders"). New percent-encoders in the tree (src/md/html_renderer.rs:500, src/resolver/data_url.rs:274) already use hex_byte_upper; the remaining hex2_upper sites carry comments like js_printer/lib.rs:877 "remaining \xHH site below" indicating they are leftovers, not the name to reach for. No behaviour difference — purely which name new code should use.
Verification: nit — The new code at src/runtime/server/mod.rs:184 calls bun_core::fmt::hex2_upper(byte). In src/bun_core/fmt.rs:2858-2863, hex2_upper sits under the banner // ── compat aliases (pre-dedup names) ──, is #[doc(hidden)], and just delegates to hex_byte_upper(b) (line 2862). The canonical helper is hex_byte_upper at fmt.rs:2851, whose doc comment explicitly says "Used by… | nit — the…
|
Heads up for whoever lands second: #41793 makes the bundler percent-encode the output paths it writes into an HTML entry point's For the route key registered here to equal the URL the page then carries for every name, |
|
Closing in favor of #41793. That PR now registers the |
Problem
Bun.serveHTML import (and a compiled executable) serves an asset whose name has a space or a non-ASCII character as a 404. The page sayssrc="/my img-24bdys0j.png". The browser requests/my%20img-24bdys0j.png. Only the raw bytes/my img-24bdys0j.pngmatch. The dev server is not affected, it serves/_bun/asset/<hash>.png.HTMLBundle.rs:613andserver_body.rs:620key the static route on the rawdest_path. The router compares request-target bytes as-is.Fix
percent_encode_route_path(src/runtime/server/mod.rs) encodes a path the way a browser does before a request: the WHATWG URL path percent-encode set (C0 controls, space,",#,<,>,?,`,{,}, DEL and non-ASCII), upper-case hex. Both registration sites use it.Cow::Borrowed).test/js/bun/http/bun-serve-html.test.ts(production build, space andünï) andtest/bundler/bundler_html_server.test.ts(HTMLServerEncodedAssetRoute, compiled, both backends). Both fail on 1.4.3 with 404. Also ranbun-serve-routes.test.ts,bun-serve-static.test.ts,bundler_html.test.ts.Background
bun build --compile) embeds the same outputs and registers them from the HTML import manifest at server start. That is the second site.Notes
Repro on 1.4.3:
With this change both return 200.
bun build --compile serve.mjshad the same two 404s and returns 200 with this change.bun_core::percent_encode_writewas not reused on purpose. Its escape set is the one for source map URLs: it does not escape a space and it escapes%,~,[,],|,^,\. A browser leaves those as they are in a path.#and?in an output name are escaped like the rest of the set, but a browser parses them as the fragment and the query, so such a name stays unreachable either way. That is a naming problem, not a route problem.The PRs for the router side (#35249, #33096) normalize every request target. This change is independent of them: it only changes the keys bun registers for its own outputs.
Found by a fuzz pass over HTML import reference kinds. No user report.