Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/souveraine-components/sessiond.md
Fimeg 341a2fe060 docs: take the lockscreen out of casey's uid
The current user unit wins the boot race but still runs as the human whose Personal key the lock is meant to evict. Record the target souveraine-session principal, its narrow Wayland/PAM/data reach, and the fact that the rich shell is presentation—not release authority.
2026-08-17 12:43:45 -04:00

5.7 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.

Sessiond is still packaged as a user unit with no User= directive. It therefore runs as the human graphical-session owner. That ordering closes the boot visibility hole but does not close ownership of the lockscreen. The target principal is souveraine-session, with only the Wayland lock, PAM, Ambient, and Revealable reach described in ../../../souveraine/saf/authority/01-session.md.

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.