Repository navigation
Description
Summary
The fleet session broker's request-frame parser silently reroutes a request to the default laptop-agent port (4799) whenever the frame's port field is present but not a valid u16 — out of range, negative, a float, or a string. A request addressed somewhere else (or nowhere valid) is delivered to a service it was never addressed to, with a 200, instead of being refused. A present-but-invalid port is not an absent port; only an absent one should take the default.
Evidence
pkgs/fleet/crates/fleet-proxy/src/serve.rs (at 540d3bea3f):
parse_request(:610; the port chain at :636-639):let port = frame.get("port").and_then(|v| v.as_u64()).and_then(|p| u16::try_from(p).ok());— every failure mode of that chain (70000, -1, 4801.5,"4801") collapses toNone, indistinguishable from the field being absent.Broker::do_request(:507; the default at :511):let port = req.port.unwrap_or(fleet_config::PORT_LAPTOP_AGENT);— theNonebecomes port 4799, and the request (method, path, body, and the caller's token file) is forwarded there.- Contrast in the same function:
methodandpathare strictly validated and rejected when malformed (fleet-proxy(broker): method/path CRLF injection — one frame smuggles a second request onto the pooled tunnel #5750), andport: 0— which survives the chain asSome(0)— is dialled and fails. The invalid cases are not even consistent: 0 errors, 70000 silently reroutes.
The module's own contract says the lenient parsing "mirrors the Python's dict access" for defaults — the Python takes req.get("port", 4799) only when the key is absent; a present 70000 is used and the connect fails. The broker's hello also advertises a closed routing table (services_map, ports 4799/4800/4801 per fleet-config) with the proto rule that an unadvertised port is "not here" — a frame for port 70000 being served by 4799 breaks that contract from inside.
Verified (probe, laptop, real crates)
Probe ~/git/fleet/probe-broker-port (path-deps on the real fleet-proxy/fleet-config/fleet-proxy-proto crates at 540d3bea3f): stub HTTP services on 127.0.0.1:4799 and 127.0.0.1:4801 that answer with their own port; the real broker (serve::run, direct mode, temp socket/token) in front of them; legacy request frames over the broker socket:
port: 4801→ 200, served by 4801 (control)portabsent → 200, served by 4799 (control — the documented default)port: 70000→ 200, served by 4799 (bug)port: "4801"(string) → 200, served by 4799, not 4801 (bug)port: -1→ 200, served by 4799 (bug)port: 4801.5→ 200, served by 4799 (bug)port: 0→ 502broker: tunnel to 127.0.0.1:0: Connection refused(dial failure — the inconsistent contrast)
Why it matters
The broker is the fleet's local RPC router: a frame's port is its address. Silently delivering a request to the wrong service is worse than failing it — the wrong service acts on a request it was never sent (the laptop-agent daemon executes commands), the caller sees a plausible 200 from a service it did not address, and the misaddressing is invisible in logs. Client bugs that serialize a port as a string or compute an out-of-range port become silent misroutes instead of loud errors at the frame boundary, exactly where #5750 already established strict validation for method/path.
Acceptance criteria
parse_requestdistinguishes an absentport(→ default 4799) from a present-but-invalid one (non-integer, negative, float, string, or > 65535), and the broker answers the latter with the standard{status: 502, body: {error: "broker: …"}}frame naming the bad port value's shape — no connection is opened.port: 0gets the same frame-boundary refusal instead of a dial attempt (or is explicitly documented and tested as a dial error, consistently with the other invalid values).- Tests in
fleet-proxypin: valid port routes to that port, absent port routes to 4799, and each invalid shape (70000, -1, 4801.5,"4801", 0 per the chosen rule) is refused without touching any service. - Existing fleet-proxy tests keep passing (
cargo test -p fleet-proxyon the laptop or CI).
Scope notes
- Frame-boundary parsing in
serve.rsonly. Not the typedfleet-proxy-protoRequest(itsport: Option<u16>cannot express these shapes — the gap exists precisely because the broker parses raw frames leniently, which legacy/Python clients still use). - Not fleet-config's services-table validation (fleet-config: [[proxy.services]] is never validated — duplicate ports silently collapse last-wins in the broker's routing map, and port 0 / empty names load #6397) and not the
proxy connectCLI flag handling (fleet(proxy):proxy connectsilently overwrites duplicate flags and silently drops modes — repeated--host/--port/--request/--bodylast-wins,--stdioeats--request,--print-patheats--request,--host/--portwithout--stdioare no-ops #5929).
Activity
developing-today commentedon Nov 6, 2026
CODE-1974
dezren39 commentedon Nov 6, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
24 remaining items
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
opencode; handed off to the opencode claimant, which launches the bound session (attempt 2/4)dezren39 commentedon Nov 6, 2026
🤖 Agent note · design-architect pass 62 — adopted as Phase 1 of design #6582
files/designs/code-fleet-proxy-validated-bounded.mdin the keep-the-four-repos-healthy goal workspaceparse_requestframe fields. Phase 1 lands first: it is the only phase whose failure mode is silent wrong-service delivery / credential substitutiondezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off