Problem
keepNode.statusDisplay.showMeshPeers was implemented in #101 and removed before merge because it cannot work as built.
The status collector runs as root with CapabilityBoundingSet = [ ], so it holds no CAP_DAC_READ_SEARCH. nixos/mesh.nix:278 creates the mesh state dir as 0700 keep-node-mesh keep-node-mesh. Root without DAC override cannot traverse it.
Confirmed under the collector's exact serviceConfig:
Error: failed to inspect config /var/lib/keep-node-mesh/.config/nvpn/config.toml
Caused by: Permission denied (os error 13)
Dropping RestrictAddressFamilies or MemoryDenyWriteExecute changes nothing — it is DAC, not sockets. Plain root works only because it retains DAC override, which is why the probe looked fine in ad-hoc testing.
The failure is closed (peers stays null, the row reads unknown), so it was never a safety problem — just a permanently non-functional option.
Why it was removed rather than fixed
Every in-place fix changes who may read a directory holding a Nostr private key, which is a security design decision rather than a display change:
SupplementaryGroups = [ "keep-node-mesh" ] + 0750 state dir — widens group readability on that directory, and makes a security-critical module loosen permissions to serve a display feature
- re-admit
CAP_DAC_READ_SEARCH — no file-mode change, but lets the collector read every file on the box
Removing it also tightened the collector sandbox: RestrictAddressFamilies no longer widens to AF_INET/AF_INET6 for a probe that died before opening a socket.
Proposed approach
A separate unit running as the keep-node-mesh user that publishes a peer count the collector reads. No capability grant, no permission change — the probe runs as the user who already owns the directory.
Needs a cross-unit freshness contract: the count carries its own cached_at, and the display must blank it independently of the main snapshot's staleness rather than inheriting it.
Acceptance criteria
Notes
The cadence assertion and its five-case eval guard existed in #101 and were removed with the option. Recoverable from that branch's history rather than rewritten.
Problem
keepNode.statusDisplay.showMeshPeerswas implemented in #101 and removed before merge because it cannot work as built.The status collector runs as root with
CapabilityBoundingSet = [ ], so it holds noCAP_DAC_READ_SEARCH.nixos/mesh.nix:278creates the mesh state dir as0700 keep-node-mesh keep-node-mesh. Root without DAC override cannot traverse it.Confirmed under the collector's exact
serviceConfig:Dropping
RestrictAddressFamiliesorMemoryDenyWriteExecutechanges nothing — it is DAC, not sockets. Plain root works only because it retains DAC override, which is why the probe looked fine in ad-hoc testing.The failure is closed (
peersstaysnull, the row readsunknown), so it was never a safety problem — just a permanently non-functional option.Why it was removed rather than fixed
Every in-place fix changes who may read a directory holding a Nostr private key, which is a security design decision rather than a display change:
SupplementaryGroups = [ "keep-node-mesh" ]+0750state dir — widens group readability on that directory, and makes a security-critical module loosen permissions to serve a display featureCAP_DAC_READ_SEARCH— no file-mode change, but lets the collector read every file on the boxRemoving it also tightened the collector sandbox:
RestrictAddressFamiliesno longer widens toAF_INET/AF_INET6for a probe that died before opening a socket.Proposed approach
A separate unit running as the
keep-node-meshuser that publishes a peer count the collector reads. No capability grant, no permission change — the probe runs as the user who already owns the directory.Needs a cross-unit freshness contract: the count carries its own
cached_at, and the display must blank it independently of the main snapshot's staleness rather than inheriting it.Acceptance criteria
keepNode.mesh.stateDirunknown, never a fabricated or last-known numberstaleSeconds, sostaleSeconds > 2 * refreshSecondsmust hold or the probe runs every roundshowMeshAddress(which works today, viaip -o -4 addr show) is unaffectedNotes
The cadence assertion and its five-case eval guard existed in #101 and were removed with the option. Recoverable from that branch's history rather than rewritten.