From 3a2b8a4af07cd643f10b08083073c8ce5e8c9601 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Fri, 4 Sep 2026 12:59:11 +0700 Subject: [PATCH] =?UTF-8?q?Features:=20#305=20=E2=80=94=20one=20definition?= =?UTF-8?q?=20of=20loopback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 11-Features.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/11-Features.md b/11-Features.md index 4c22cc2..99424dc 100644 --- a/11-Features.md +++ b/11-Features.md @@ -3691,3 +3691,27 @@ failure — a transport error, or a code like `herdr_busy`. No new tests were ad covered these paths and asserted 500, and neither was about the status code (one guards that a failed spawn still closes its tab, the other that a failed teardown is not reported as 204). Their expectations moved to 502 and gained a body check. + +## One definition of loopback, so a worker cannot become the lead + +**What.** `ConnectionIdentity` and `CallerResolver` each kept their own `isLoopback`, and the two +disagreed: the identity resolver accepted only `127.0.0.1`, the authorization check accepted all of +`127.0.0.0/8`. There is now one predicate, on `ConnectionIdentity`, that the other calls. + +**On.** Always on. It matters most in `auth.mode: loopback-trust`, which is the default — `auth:` is +commented out in `fleetd.example.yaml`, and the daemon logs the mode at every boot. + +**Why it exists.** The disagreement was a privilege escalation. A caller from `127.0.0.2` had its +identity resolution skipped, so it carried no terminal; `CallerResolver` reads a missing terminal as +"not a worker", and a same-host non-worker is the primary. A worker could take spawn, stop, send and +drain. The skip also happens before the PID ancestry walk, so the defence that stopped a member's +`curl` child being read as the primary was bypassed as well. + +**One thing to know for maintenance.** **Do not "tighten" `ConnectionIdentity.isLoopback` back to +`127.0.0.1`.** It reads like the safe direction and it is the opposite. That predicate does not +decide whether a caller is trusted — it decides whether a caller's identity is resolved at all, and +resolution is what *demotes* a worker. Every address excluded there is an address on which a worker +becomes the lead. The range matters because on Linux the whole `/8` is bound to `lo`, so a source of +`127.0.0.2` is bindable; measured on the fleet host with `curl --interface 127.0.0.2` returning exit +7 (connect refused) rather than 45 (bind failed). On macOS the same command fails at the bind, which +is why no test binds a real `127.0.0.2` source — it would fail for every developer on a Mac.