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.
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:
- 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. - 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 viaunix_chkpwd. No bypass verb. - Fail-closed default. Default behavior on start is to lock;
--no-initial-lockis 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_readyhas no caller attestation. Any same-uid process that can reach the socket can callshell_readyand induce a handoff, or calllock. 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 honoringshell_ready.- Inhibit / step-up verbs (§6/§10) are stubs. sessiond is the natural
home for the
souveraine-stepupPAM 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_PEERCREDnot yet read. machined logs uid/gid/pid on every request; sessiond does not yet. Same-user-only today (server.rscomment: "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.SHA256shape 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.