Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/souveraine-components/sessiond.md
Fimeg 9815185caa docs: put agent identity behind a real uid
Join the older storage and authority audits to the SAF principal contract. Passwd entries are not isolation while one human-owned process still executes every agent, and the existing souveraine account is machined's—not Souvie's.\n\nKeep the readiness task honest: its current health fields inventory paths, but do not yet prove admission or the UID handling a live turn.
2026-08-17 12:36:45 -04:00

5.3 KiB

souveraine-sessiond

User-tier session authority daemon. Owns the ext-session-lock-v1 surface and the unlock verb — the one thing in the system that can release the session lock. The shell is a client of this authority, not the authority itself.

Source: src/bin/souveraine-sessiond.rs, src/sessiond/{protocol,server,lock,draw,auth}.rs. Companion design: docs/session-authority-boot-order.md (Phase C), docs/SESSION-AUTHORITY-DOCTRINE.md §11.

What it owns

  • The ext-session-lock-v1 client that holds the session locked at boot, before the shell exists, and after the shell dies.
  • The PAM conversation for unlock — hand-rolled libpam FFI (auth.rs), no wrapper crate (fewer deps for on-device builds).
  • A Unix socket at $XDG_RUNTIME_DIR/souveraine/sessiond.sock, same-user today (0600), machined-shaped JSON-lines protocol.

The zero-gap handoff (load-bearing)

A lock client that drops its Wayland connection without unlocking leaves the compositor holding the session locked. With Hyprland's misc:allow_session_lock_restore, the next client inherits that locked state. sessiond uses this for two transitions, both fail-closed:

  1. Boot handoff. sessiond takes the lock first (splash dissolves onto a locked panel), quickshell starts ~3s later, the bridge calls shell_ready, sessiond drops its connection, the shell's lock inherits — no unlocked instant between them.
  2. Crash retake. The bridge connection is the shell's heartbeat. EOF on that connection = shell dead. sessiond retakes the lock (allow_session_lock_restore) and renders its own spartan PIN surface. A shell crash while locked is no longer an ssh-only recovery.

The retake is self-correcting: if sessiond tries to lock against a live shell lock, the compositor yields Finished/Denied — no deadlock.

What crosses the boundary

JSON-lines over the socket (protocol.rs). Request ops: status, shell_ready, locked_ack, lock. Phases: holding, awaiting_shell_lock, released, idle. The shell_ready connection is the heartbeat; its EOF is the retake trigger. Responses are guarded ok/reason shaped, same convention as machined.

Unlock is never an RPC verb. The only path to unlock is the PAM conversation on sessiond's own lock surface.

Today's security posture

  • Lock-before-shell. No desktop surface (NM dialog, bar, background) can precede the lock at boot — the boot fail-open hole that motivated this daemon.
  • Lock-past-shell-death. Shell crash while locked retakes to a PIN, not a locked brick.
  • PAM is the unlock path. auth include login (same credentials as console login), unprivileged via unix_chkpwd. No bypass verb.
  • Fail-closed default. Default behavior on start is to lock; --no-initial-lock is the explicit opt-out for dev.

Audit gaps (toward the RedFlag bar)

sessiond is the component that most directly maps onto RedFlag's "capability token over a resolved closure, signed by an authority, verified by the executor" shape (SESSION-AUTHORITY-DOCTRINE §10), because unlock is exactly the kind of verb that should require a token. Today it does not:

  • shell_ready has no caller attestation. Any same-uid process that can reach the socket can call shell_ready and induce a handoff, or call lock. RedFlag's analog: the privileged executor verifies the token's signature, AgentID, Operation, expiry, and closure hash before acting — it does not trust "a process asked." sessiond trusts the heartbeat's mere existence. The fix shape: the shell presents a machined-signed capability token (operation=session_handoff, expiry short, closure = the shell binary hash) that sessiond verifies against the pinned machine pubkey before honoring shell_ready.
  • Inhibit / step-up verbs (§6/§10) are stubs. sessiond is the natural home for the souveraine-stepup PAM service and the portal Inhibit backend, and for verifying capability tokens on session verbs. These are named in the doctrine but not implemented; the socket protocol reserves the space.
  • SO_PEERCRED not yet read. machined logs uid/gid/pid on every request; sessiond does not yet. Same-user-only today (server.rs comment: "widens to a group grant deliberately when agents get accounts"). Per-agent UIDs and worker processes turn this into kernel-backed caller identity — the substrate of the capability gate. The account, admission, and health contract is ../../../souveraine/saf/identity/02-agent-principal.md; creating accounts without moving execution out of the human-owned server is not sufficient.
  • Binary integrity. As with the other two and RedFlag SEC-022: no self/peer hash check. DAC is the standing guarantee. When the capability token lands, the token's closure should carry sessiond's own expected hash so a swapped binary cannot present a valid old token — RedFlag's ClosureEntry.SHA256 shape pointed inward.
  • eBPF (KERN-001 shape). The lock gate's kernel-level stop is ext-session-lock-v1 itself (a Wayland protocol the compositor enforces), which is already a kernel-adjacent guarantee stronger than RedFlag's eBPF-scaffold-for-execve. So sessiond's "defense-in-depth kernel stop" is largely already present via the compositor; the gap is the token gate on top, not the kernel enforcement underneath.

See audit-status.md for priority.