Skip to content

docs: design the runtime dir move, config layering and trust - #14

Open
paveq wants to merge 2 commits into
mainfrom
docs/runtime-and-config-design
Open

paveq wants to merge 2 commits into
mainfrom
docs/runtime-and-config-design

Conversation

@paveq

@paveq paveq commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Design proposal only, no code: docs/runtime-and-config-design.md.

What it proposes

  • Runtime dir: move airlock.sock, airlock.pid and airlock-ca.pem out of the project into $XDG_RUNTIME_DIR/airlock/<id>/ (Linux) or $TMPDIR/airlock/<id>/ (macOS). The directory is per-user, mode 0700, and not writable from any sandbox. On macOS the proposal adds a Seatbelt deny rule, because the baseline grants all of $TMPDIR.
  • Config layers: global ~/.config/airlock/airlock.toml < repo airlock.toml < local airlock.local.toml.
    • A tool defined in two layers is an error.
    • A secret defined in two layers takes the higher layer's spec whole.
    • Lists are unioned; maps and scalars take the higher layer's value.
  • Trust: the repo and local files must match a byte-exact copy approved with airlock trust. When they don't, startup refuses and prints an escaped diff. The global file is not approved; it is protected by where it lives.
  • Anchors: XDG variables are honored, but the trust store, the global config and the runtime dir are refused if they are inside the project or under any sandbox write grant. This blocks redirection through repo-level env tooling such as mise [env] or .envrc.
  • CLI:
    • airlock list asks the daemon instead of reading config files.
    • --no-config becomes --no-project-config (global layer only).
    • trust, daemon start/stop/restart and run refuse inside the sandbox.

The Decisions section lists each choice with the alternatives it rejected, and there is a comparison with mise trust.

🤖 Generated with Claude Code

paveq added a commit that referenced this pull request Sep 23, 2026
CI fails now and then with "socket ... has insecure permissions 0o755".
It hit run_embedded_exits_on_cancel on #14 and
synchronous_startup_explicit_path_uses_parent_as_sandbox_root on #13.

The socket gets its mode from the umask at bind time, and the umask is
process-wide. synchronous_startup and four socket tests each did
umask(077) / bind / umask(old) with no lock, and cargo test runs them on
parallel threads. If one thread restores 022 between another thread's
swap and its bind, that socket comes out 0755. The post-bind check then
rightly refuses it.

The swap now lives in bind_owner_only, behind a static mutex, and
synchronous_startup and the tests both go through it. The lock is in
production code, not a test-only mutex, so the integration test
binaries that call synchronous_startup in parallel are covered too. The
real daemon binds before any other thread exists, so it never waits on
the lock.

Stress run of `daemon::` lib tests, 200 iterations at 16 threads: 5
failures before, 0 after.
The daemon's socket, PID file and proxy CA certificate sit in the sandbox
root, which the agent and every tool can write, and they clutter the repo.
Separately, a single checked-in airlock.toml forces personal choices like
where GH_TOKEN comes from into the shared file.

The proposal moves runtime files to a per-user 0700 directory, layers a
global and a gitignored local config over the repo file, and gates the
repo and local files behind byte-exact approval (`airlock trust`) with a
diff on refusal. It records each design choice with the rejected
alternatives, and compares the trust model with `mise trust`.
@paveq
paveq force-pushed the docs/runtime-and-config-design branch from 40b391b to 3504f2c Compare September 23, 2026 12:32
A review of the proposal against the code found gaps that would keep it
from delivering its stated goals, or from working at all: project files
give the agent deferred code execution outside the sandbox, approval does
not cover the programs the config runs, the repo layer can reach personal
secrets, any daemon is reachable from any sandbox, and the socket location
is not stable (Linux agent env lacks XDG_RUNTIME_DIR, macOS $TMPDIR varies
per shell).

Record them as blocking items with a proposed direction each, list the
non-blocking follow-ups, and capture one-daemon-per-user with session
tokens as an open question. The Seatbelt rule-order check moves out of
"to verify": sandbox-exec confirms the last matching rule wins, so the
deny must be emitted after every allow.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant