Not a privilege-escalation report. §6 already says the herdr socket equals arbitrary code execution, and herdr advertises agent socket access as a feature. The narrower point: herdr's socket and Collie's port have different reachable sets, and §6 doesn't distinguish them.
herdr's socket is a filesystem object — permissions bound callers to the owning uid. Collie binds 127.0.0.1:8787, reachable by every uid in the netns. §6 calls the loopback bind load-bearing; it is, against remote callers. Locally it bounds nothing, and the doc reads as though it does.
Concrete case. I run review agents on untrusted diffs under a separate uid — sudo -u agent-review claude inside the pane. That uid can't open my herdr socket, so a prompt injection in a diff can't reach my other panes. It can open 127.0.0.1:8787, so with Collie running it can:
curl -X POST http://127.0.0.1:8787/api/pane/<id>/keys \
-H 'content-type: application/json' -d '{"keys":["..."]}'
— and type into any pane in the herd. checkAccess passes (Host is loopback, so the no-Origin write path is allowed; COLLIE_PUBLIC_HOSTS is empty by default so the host allowlist never runs). deviceAuth returns authorized: true on the absent header. Installing Collie silently removes the uid boundary, which is the containment an operator running agents is most likely to have reached for.
The fix already exists in the code. deviceAuth() fails open:
if (!device) return { enforced: true, device: null, authorized: true };
Rationale given: an absent header is the on-host loopback operator. That's the same "local means trusted" assumption.
Deny writes on an absent header when COLLIE_DEVICE_HEADER is set, and treat allowlist entries as secrets rather than device names, and this becomes a bearer token — reaching the port stops being sufficient to write. Few lines, no new machinery, no Tailscale dependency, so it covers Variant C too.
Same change closes a doc/code gap: §6 says the owner login is asserted and other tailnet users rejected, but checkAccess only rejects a mismatching Tailscale-User-Login and passes an absent one. Fine under tailscale serve, which always injects it; not fine behind a proxy that stops.
If unix-socket listen was on the roadmap: Tailscale restricted Serve's unix-socket proxy targets to root in TS-2026-005, and collie-ctl.sh runs serve as the operator. That route costs root now.
If the fail-closed change isn't wanted, a sentence in §6 covers most of it: the loopback bind bounds remote reach only, and agents under a separate uid are not isolated from the bridge. Happy to PR the deviceAuth change.
Not a privilege-escalation report. §6 already says the herdr socket equals arbitrary code execution, and herdr advertises agent socket access as a feature. The narrower point: herdr's socket and Collie's port have different reachable sets, and §6 doesn't distinguish them.
herdr's socket is a filesystem object — permissions bound callers to the owning uid. Collie binds
127.0.0.1:8787, reachable by every uid in the netns. §6 calls the loopback bind load-bearing; it is, against remote callers. Locally it bounds nothing, and the doc reads as though it does.Concrete case. I run review agents on untrusted diffs under a separate uid —
sudo -u agent-review claudeinside the pane. That uid can't open my herdr socket, so a prompt injection in a diff can't reach my other panes. It can open127.0.0.1:8787, so with Collie running it can:— and type into any pane in the herd.
checkAccesspasses (Host is loopback, so the no-Origin write path is allowed;COLLIE_PUBLIC_HOSTSis empty by default so the host allowlist never runs).deviceAuthreturnsauthorized: trueon the absent header. Installing Collie silently removes the uid boundary, which is the containment an operator running agents is most likely to have reached for.The fix already exists in the code.
deviceAuth()fails open:Rationale given: an absent header is the on-host loopback operator. That's the same "local means trusted" assumption.
Deny writes on an absent header when
COLLIE_DEVICE_HEADERis set, and treat allowlist entries as secrets rather than device names, and this becomes a bearer token — reaching the port stops being sufficient to write. Few lines, no new machinery, no Tailscale dependency, so it covers Variant C too.Same change closes a doc/code gap: §6 says the owner login is asserted and other tailnet users rejected, but
checkAccessonly rejects a mismatchingTailscale-User-Loginand passes an absent one. Fine undertailscale serve, which always injects it; not fine behind a proxy that stops.If unix-socket listen was on the roadmap: Tailscale restricted Serve's unix-socket proxy targets to root in TS-2026-005, and
collie-ctl.shruns serve as the operator. That route costs root now.If the fail-closed change isn't wanted, a sentence in §6 covers most of it: the loopback bind bounds remote reach only, and agents under a separate uid are not isolated from the bridge. Happy to PR the
deviceAuthchange.