Skip to content

Agent/daemon: hold the unlocked vault, serve just-in-time child-scoped secret injection #116

Description

@arimxyer

Summary

Add an optional background agent/daemon that holds the unlocked vault in memory and serves just-in-time, child-scoped secret injection to exec / export / run. This is the foundational capability under the ergonomics work in #115 — the piece that actually attacks secret lifetime / blast-radius, which none of the #115 flags fix on their own.

Split out of #115 deliberately: #115 is the ergonomic layer (how mappings are declared and delivered); this is the substrate (where the unlocked key lives and how long a materialized secret survives). They compose; this one is the dependency.

Problem

Two gaps in today's model:

  1. Lifetime. exec is well-scoped — the secret dies with the child. But the export-into-a-shell pattern proposed in Env-injection ergonomics: export/direnv, run --env-file/inject, transforms, mapping manifest #115 lives for the whole shell session (hours), /proc/<pid>/environ-readable the entire time. Shipping export alone trades a file leak for a long-lived in-memory leak.
  2. Unlock friction. Every exec / get opens and decrypts the vault, so just-in-time fetching either means repeated master-password/keychain unlocks, or pushing users toward keeping things unlocked some other ad-hoc way.

A daemon resolves both: unlock once, then every fetch is fast, just-in-time, and scoped to the consuming process.

Proposal

pass-cli agent — a background process that:

  • Holds the derived key / unlocked vault in memory only (never on disk).
  • Serves exec / export / run (and get) over a local socket so they fetch on demand instead of opening the vault themselves.
  • When the daemon isn't running, all commands fall back to today's direct-open behavior — the daemon is an optimization/safety layer, not a hard dependency.

Composed with #115: single declared source (manifest) → daemon serves JIT → materialization scoped to the child = one auditable source, zero persistent plaintext, minimal lifetime.

Design surface (the parts that need decisions)

  • IPC & auth. Unix domain socket (Windows named pipe), perms 0600, plus peer-credential checks (SO_PEERCRED / getpeereid) so only same-user processes can talk to it. Define the request/response protocol (request a mapping set, return values, never log them).
  • Lifecycle / auto-lock. Explicit pass-cli agent (and agent stop / lock), idle timeout and max-TTL re-lock, behavior across multiple shells, and what happens on logout/suspend.
  • Unlock & key handling. Unlock via master password or the existing OS keychain integration; cache the derived key in locked memory (mlock, disable core dumps) to keep it out of swap/crash dumps.
  • Honest security model. The daemon's own memory is /proc/<pid>/mem-readable by the same user — the same ceiling as ssh-agent / gpg-agent. Document it; don't oversell isolation. This is strictly better than long-lived shell env or plaintext files, not a sandbox.
  • Integration. How it interacts with keychain (could supply the unlock) and sync; whether exec/export/run auto-detect and prefer the socket.
  • Platform service. Optional systemd --user unit / launchd agent / Windows service, or pure run-on-demand.

Prior art

ssh-agent, gpg-agent, op daemon / op biometric session, aws-vault's server mode. The lifetime/scope model is well-trodden; this issue is about adopting it for pass-cli's vault.

Non-goals

Does not remove the consumer-side plaintext-at-boundary ceiling (an app reading process.env still gets a plaintext value at the moment of use — see #115's "hard ceiling"). This issue minimizes how long and how broadly that plaintext exists, not whether it exists at the boundary.

Relationship

Filed alongside #115 from a real session wiring OpenTelemetry/Langfuse export into an eve agent; a separate Claude Code session may pick up the implementation.

Metadata

Metadata

Assignees

Labels

help wantedExtra attention is needed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions