ADR-0008 — Security, auth & secrets
- Status: Proposed
- Date: 2026-07-18
Context and problem statement
A resident daemon that (a) can spawn arbitrary commands, (b) holds live terminals
into agent CLIs with broad permissions (--dangerously-skip-permissions,
--yolo), and (c) is attachable over the network (📝 ADR-0004) is a juicy target.
We need a security model that's strong-by-default without inventing crypto, and a
secrets story at least as safe as today's env_file.
Decision drivers
- Local access control with no config (just work, safely).
- Remote access must be authenticated and auditable, reusing existing credentials.
- Secrets (API keys in
env_file) must not leak into daemon state, logs we write, scrollback exports, or the protocol. - The daemon's power (spawn + attach) must not be reachable by other local users or unauthenticated network peers.
Decision outcome
Local access — filesystem permissions
- Control/data socket at
$XDG_RUNTIME_DIR/harnessd.sock, mode0600, owned by the user. On systems without a per-user runtime dir, fall back to$XDG_STATE_HOME/harnessd/harnessd.sockin a0700dir. Only the owning user can talk to the daemon. No auth beyond OS perms is needed locally — same trust model as the tmux socket today. - State/log files under
$XDG_STATE_HOME/harnessd/are0600/0700.
Remote access — SSH public keys via Wish (opt-in)
- The Wish SSH server is off by default; enabling it is a deliberate config
step (bind address +
authorized_keys). - Auth = SSH public keys only. An allowlist in daemon config
(
[server] authorized_keys = [...]or a path to anauthorized_keysfile). No passwords, no bearer tokens of our design. - The daemon has a stable host key (generated on first run via
keygen,0600), so clients get real host-key verification — no TOFU-blind connections. Optionally, back that key up as seed words withmeltso a re-provisioned host keeps its identity and clients don't trip host-key-changed warnings. - Wish apps are not shells: a remote peer gets the TUI, not
/bin/sh. - Bind narrowly by default (loopback / explicit address); document that exposing it wants a firewall or, better, reaching it over Tailscale/WireGuard/an SSH tunnel rather than the public internet.
- (Optional, later) per-key authorization scoping — e.g. a key that may attach read-only but not start/stop. Noted, not v1.
The irreducible risk: attach == terminal access
Attaching to a harness is getting that harness's terminal — and many harnesses are agent CLIs running with skip-permissions. So anyone who can attach can drive the agent. That's inherent to the product, not a bug. Consequences:
- Treat "can attach to this daemon" as equivalent to "can act as these agents." Guard the socket (local perms) and the key allowlist (remote) accordingly.
- Consider a per-harness read-only attach mode (stream output, ignore input) for "watch it work" without handing over the keyboard — pairs with the optional per-key scoping above. Useful and cheap; propose for v1.
Secrets
- Secrets stay exactly where they are today: in
env_file, sourced into the child process's environment at spawn. The daemon reads the file, sets the child env, and does not retain the values in its own long-lived state. - Never persist secrets in things we control: state.json, our rotating log
files, scrollback exports, or protocol frames must never include
env_filecontents. (The daemon can't stop a harnessed program from printing its own secrets to its terminal — that output lands in scrollback/logs like any other output. We document this; optionally offer per-harness "don't persist scrollback to disk" for sensitive ones — 📝 ADR-0007.) env_filepath and file perms are the user's responsibility (as today); we can warn in the TUI if anenv_fileis group/world-readable.- Fits Joe's setup:
env_filealready points at Vault/OpenBao-rendered static env files; the daemon never needs to know about the secret backend.
Consequences
Positive
- Local: zero-config, OS-enforced, same trust model as tmux today.
- Remote: authenticated by SSH keys with host-key verification, opt-in, non-shell, no home-grown auth.
- Secrets handling is no worse than today and explicitly fenced out of everything the daemon persists.
- Read-only attach + (later) per-key scoping give a real least-privilege story.
Negative / costs
- The daemon is a high-value target by nature (spawn + agent terminals); a socket
or key-allowlist misconfig is serious. Mitigate with safe defaults (remote off,
bind loopback,
0600) and TUI warnings for risky config. - We inherit SSH host-key +
authorized_keysmanagement ergonomics for the remote path (documented, not automated away). - We can't prevent a harnessed program from leaking its own secrets into its own output; only limit what we persist and offer opt-out.
Related
📝 ADR-0004 (Wish/SSH transport), 📝 ADR-0006 (env_file in config), 📝 ADR-0007 (what
persists, scrollback opt-out), spec-daemon-protocol.md (no secrets in frames).
Related Artifacts
Direct relationships declared in YAML frontmatter (per ADR-0023 / SPEC-0018). Run /sdd:graph chain ADR-0008 for the transitive view.