Skip to content

Mesh peer count on the status display cannot read the mesh state dir #102

Description

@wksantiago

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

  • Peer count reaches the display on a node with a real mesh, verified in a VM test (not reasoned)
  • The probe does not run as root, and the collector gains no capabilities
  • No permission or ownership change to keepNode.mesh.stateDir
  • The count blanks on its own staleness, not only on the main snapshot's
  • A stale or unavailable count renders unknown, never a fabricated or last-known number
  • Cadence assertion restored with its guard: the cache TTL is clamped below staleSeconds, so staleSeconds > 2 * refreshSeconds must hold or the probe runs every round
  • showMeshAddress (which works today, via ip -o -4 addr show) is unaffected

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions