Repository navigation
Description
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
Summary
The [proxy] services table is, by the crate's own docs, the proxy's routing table ("the services map IS the routing table"). fleet-config loads it with no validation at all: two rows with the same port both load, a row with port 0 loads, and a row with an empty name loads. Downstream, both consumers build the advertised services map by collecting the rows into a BTreeMap keyed by port string, so a duplicate port silently collapses — the last row wins and the earlier service vanishes from the hello/routing map with no error anywhere.
That breaks the crate's stated config policy (crate docs, lib.rs:14-16): "a malformed file is a loud error, never a silent fallback" — unknown sections and keys are already loud errors for exactly this reason (#5460). A duplicated port is a misspelling-class error with a worse failure mode: instead of a wrong key being ignored, one live service is silently substituted for another in the routing table.
Evidence (file:line, origin/main @ 967fb22)
pkgs/fleet/crates/fleet-config/src/lib.rs:146-155—ServiceConfig { port: u16, name: String, version: Option<String> }withdeny_unknown_fieldsonly; no value validation anywhere.pkgs/fleet/crates/fleet-config/src/lib.rs:362-…—Config::services()returnsself.proxy.services.clone()verbatim when non-empty; no duplicate/zero/empty checks.pkgs/fleet/crates/fleet-proxy/src/serve.rs:370—Broker::services_map()collectsconfig.services()intoBTreeMap<String, ServiceInfo>keyed byport.to_string(): duplicate ports collapse, last row wins.pkgs/fleet/crates/fleet-proxy/src/client.rs:114—built_in_services()builds the legacy/shim services map the same way, so the collapse happens on both the broker and client paths.
Verification (this run, laptop, real fleet-config crate)
Probe ~/git/fleet/probe-fleet-config-xdg-rel (services bin; path-depends on the real fleet-config crate; the map step mirrors serve.rs:370's collect verbatim):
dup-port: load_ok rows=2 [4801:queue-broker,4801:impostor] map_len=1 map4801=Some("impostor")
port-zero-empty-name: load_ok rows=1 [0:] map_len=1
dup-name: load_ok rows=2 [4801:queue-broker,4999:queue-broker] map_len=2
valid-control: load_ok rows=1 [4801:queue-broker] map_len=1 map4801=Some("queue-broker")
So a config that names port 4801 twice advertises exactly one service on 4801 — whichever row came last — and a port = 0, name = "" row is advertised as "0": "".
Why it matters
- The services map is the routing table and the shim source: clients negotiate it in the hello and materialize PATH shims from it. A copy-paste duplicate in a host's
fleet.tomlsilently re-points a broker port at the wrong service (or drops a service from the map) on that host, with no parse error, no log, and a config file that reads as if both rows are in effect. - Every other misconfiguration class in this crate was deliberately made loud (fleet-config: misspelled fleet.toml section/key is silently ignored — a typo'd [domains] disable loads Ok and the domain stays enabled #5460 unknown keys, malformed TOML); this one slipped through because serde accepts the shape.
Acceptance criteria
Config::load_from(orservices()) rejects a[[proxy.services]]table with a duplicate port, a duplicate name, port 0, or an empty/blank name, with a loudConfigErrornaming the offending row — consistent with the crate's malformed-is-loud policy.- The duplicate-port case can never reach the
BTreeMapcollect in fleet-proxy: the collapse is impossible by construction, not just unlikely. - Pinning tests cover duplicate port, duplicate name, port 0, and empty name alongside the existing
services_*tests in fleet-config; the valid single-row and built-in-default cases stay green. - No change to the built-in default table or to valid configs.
Activity
developing-today commentedon Nov 5, 2026
CODE-1909
dezren39 commentedon Nov 5, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 5, 2026
🤖 Agent note · triaged — CONFIRMED
1dae17e457b01e149d550b5fc8f45ad7dc7a1e6d(laptop primary, verified fresh this shift). Static check:ServiceConfig(pkgs/fleet/crates/fleet-config/src/lib.rs:146-155) carriesdeny_unknown_fieldsonly;Config::load_from(lib.rs:271) parses TOML with no value validation;Config::services()returns declared rows verbatim;ConfigErrorhas only Parse/Read variants. Consumersfleet-proxy/src/serve.rs:370(services_map) andclient.rs:114(built_in_services) both collect rows into aBTreeMapkeyed by port string, so duplicate ports collapse last-wins.~/git/fleet/probe-fleet-config-xdg-relservices bin run this shift —dup-port: load_ok rows=2 [4801:queue-broker,4801:impostor] map_len=1 map4801=Some("impostor");port-zero-empty-name: load_ok rows=1 [0:];dup-name: load_ok rows=2;valid-control: load_ok rows=1 map4801=Some("queue-broker"). Matches the issue's evidence exactly.load_fromafter parse, or inservices()'s declared branch): reject duplicate port, duplicate name, port 0, and empty/blank name with a loudConfigErrornaming the offending row (a dedicated variant or Parse with the row named — fix task decides, crate's malformed-is-loud policy is the oracle). Pinning tests next to the existingservices_*tests (lib.rs:553/568) for all four invalid tables; valid single-row and built-in-default cases stay green. No change to the built-in default table or fleet-proxy's map construction.repo-health:v1:fix:developing-today/code:issue:6397).68 remaining items
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off