Repository navigation
Conversation
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.
paveq
force-pushed
the
docs/runtime-and-config-design
branch
from
September 23, 2026 12:32
40b391b to
3504f2c
Compare
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`.
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.
paveq
force-pushed
the
docs/runtime-and-config-design
branch
from
October 6, 2026 11:10
8ff4d0e to
8d9cae0
Compare
Work through the review findings in the runtime/config design so it can move to implementation. - Narrow the anchor goal: code the user runs outside the sandbox already defeats secret isolation, so it is a documented non-goal (B1). - Filter PATH and refuse binaries that resolve into agent-writable paths, since approving config bytes does not approve the programs they name (B2). - Repo labels reach personal secrets only through an approved local `from = "global"` opt-in (B3, F4). - Authenticate clients with sessions registered via an admin credential no sandbox can read; uid alone cannot tell the agent from the user (B4, B5). - Take the runtime base from confstr / /run/user, not the environment (B6); emit Seatbelt denies last (B7); key trust copies by file name (B8). - One multi-tenant daemon per user (Q1): one listener per host keeps a network transport open, and approved config applies to new sessions without a restart. Isolation rules are written as design constraints; moving the proxy out of process is tracked as F10.
Sessions and the single per-user daemon change the client protocol, the daemon lifecycle and the trust boundary, not only where runtime files live. The old name undersold the scope.
The v2 design decides the mechanism but not what users and agents see. This companion doc covers the surface: user journeys as terminal transcripts, help text for every command, the error catalog, and drafts of the README, SKILL.md and examples changes. It proposes changes U1-U14 to the design, among them trust prompts in `airlock run`, folding `session exec` into `run --no-sandbox`, `session reload`, distinct exit codes, and `airlock check` / `airlock hook claude-code`. The hook keeps the agent from depending on the skill alone to learn which tools go through Airlock.
`airlock completions <bash|zsh>` emits a dynamic completion script, so `airlock exec -- <TAB>` can offer the session's tools and `session revoke <TAB>` the running sessions. Only the daemon knows either, and a script that calls back into the binary does not go stale on upgrade. The PreToolUse hook that redirected direct tool calls is left out. The SessionStart context should be enough, and a hook on every shell command costs a daemon round trip and needs a command parser that is never complete. The completion proposal takes its number, U14.
Agent-facing commands now sit apart from the user's. `list` becomes `tools list`, which can also show a running session's tools from the user's terminal with `--session`. `check` and `hook` move under `airlock agent`, and `status` stays the user's view (U5). `daemon run` becomes `daemon start --foreground`, so "run" always means starting an agent, and `logs` moves to `daemon logs` (U15). Inside the sandbox, help hides the commands that need the user's terminal and says which ones it hid, so a user in a sandboxed shell is not left guessing (U16). airlock-v2-questions.md lists 30 questions, both how-to and why, that the v2 docs should answer.
The UX doc proposed U1-U16 against the design, so the two docs gave
different answers to the same questions. The design now describes all of
them, and the UX table stays as the record of what changed.
Answering the developer questions against the docs surfaced gaps, each
decided here: a lease on the Register connection so a session cannot
outlive its launcher, tokens bound to the agent's process tree, global
tools yielding to project tools, removing `run --no-sandbox` in favour of
`session start`, `{tool_state}`, `session renew`, `trust --expect-sha256`,
and exit 125 when approval is declined. The questions doc records each
decision and where it lives.
The design chose one daemon per user by comparing it with one daemon per project and a relay router, all on local criteria. The intended direction is a central Airlock daemon with daemon-side shared secrets and tools that run centrally or locally, and none of the local options had been judged against that. docs/airlock-v2-topology.md records the direction, what it requires, compares four topologies against it, and proposes the changes that keep v2 on the path. Its proposals are not folded in yet. Decisions from the review, applied to the design, UX and questions docs: - Dynamic shell completion moves to a "Planned for v2.1" section. It pins clap_complete's unstable dynamic feature, sends a daemon request on every TAB and needs hand-written shell delegation, and nothing in the daemon, the sessions or the trust model depends on it. - `agent check` gets the paths it probes from the daemon. Computing them in the sandbox from XDG variables that the agent's environment may not carry would test the wrong paths and pass. - The per-session output rate limit is dropped; throttling a tool's stdout is undefined and solves nothing the design states. - The UX doc loses its verbatim help text and its README and SKILL.md drafts, which would drift during implementation. An Options table keeps the flags and decisions that only existed in the help text.
The daemon starts on demand and outlives the binary that started it, so the launcher-to-daemon link is a compatibility boundary from the first release. The topology doc also records a central daemon as the direction after v2. Both argue for choosing a few shapes deliberately while writing v2's protocol and session model, but v2 should not implement anything it does not use. docs/airlock-v2-technical-guidance.md separates the two. Shapes to use now are things v2 needs anyway or that cost nothing beyond how a type is written: two message families, a principal resolved by the listener, one protocol version that covers the Register payload, wire config that fails closed, error kinds as an enum, compiled session policy, a frame limit for admin requests, and a session id on log entries. Additions the central daemon will need but v2 would not use are postponed to v2.1 or later, each with a note on why adding it later is safe. The design doc's intro and the topology doc's constraints list point to it.
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design proposal only, no code. Five documents:
What it proposes
airlock run,airlock session start) discovers and approves the project's config, resolves its secrets, and registers a session.AIRLOCK_ADDRandAIRLOCK_SESSION.exec,tools listandagent checkare served only with a session token. Only a process holdingadmin.tokencan register a session, and no sandbox can read that file.runsession ends when the launcher's connection closes (the lease). Asession startsession ends at its TTL, 12h by default, whichsession renewrestarts. Any session ends on revoke or when the daemon stops.airlock daemon installsets it up as an optional launchd/systemd service. A version handshake handles a daemon that outlives its binary.airlock runno longer embeds a daemon, so several agents can run in one project.AIRLOCK_ADDRis a URI (unix://for now). Authorization never relies on uid alone, and registration stays on the daemon's host.airlock.sock,airlock.pid,admin.tokenand each session's CA certificate move out of the project:/run/user/<uid>/airlock/(fallback/tmp/airlock-<uid>) on Linux, the per-user temp dir fromconfstron macOS. Neither is read from the environment. No sandbox can write there; on macOS the Seatbelt denies are the last rules in every profile.~/.config/airlock/airlock.toml< repoairlock.toml< localairlock.local.toml. A project tool replaces a global one, visibly; repo against local is an error unless the local tool saysoverride = true. Repo items use only labels the repo declares; the local file binds them, andfrom = "global"reuses a global binding.airlock init --localwrites the stubs.{tool_state}keeps tool state outside the project.--configfiles must match a byte-exact copy approved withairlock trust, or at the promptairlock runshows on a terminal; on a non-terminal it refuses. Diffs are escaped against ANSI and bidi tricks.trust --yesand--expect-sha256serve scripts and CI. Running sessions keep their config untilairlock session reload.PATHwithout relative or agent-writable entries, and refuses tool binaries and secret commands that resolve into the project or a write grant.airlock agent checkverifies the session and self-tests the sandbox, with the paths to probe supplied by the daemon.airlock agent hook claude-codeanswers Claude Code's SessionStart event, and--profile claudeinstalls it.execexits 125 when Airlock could not run the tool, 126 when the tool's binary cannot be used, 127 when no such tool is declared, and otherwise with the tool's status..envrc, build scripts) is a documented non-goal.airlock trust,airlock config,airlock session start/renew/list/reload/revoke,airlock daemon install/uninstall,airlock agent check/hook.airlock listbecomesairlock tools listand asks the daemon.--no-configbecomes--no-project-config;run --no-daemonbecomesrun --no-session.run,trust,status,sessionanddaemonrefuse inside the sandbox. Shell completion is planned for v2.1.Direction and guidance
The topology doc records a central daemon as the longer direction: daemon-side shared secrets, tools that run centrally or locally, and the local daemon as the agent's single entry point. It compares one daemon per session, this design as written, a per-user daemon with a daemon-side layer, and the central server against that direction, and proposes five changes that would keep v2 on the path. They are not folded in.
The technical guidance doc lists the protocol and data model shapes to use while implementing this design (two message families, a principal per request, one protocol version, wire config that fails closed, error kinds as an enum, compiled session policy, a frame limit for admin requests, session ids on log entries) and postpones to v2.1 or later the additions the central daemon will need but v2 would not use.
The Decisions section of the design doc lists each choice with the alternatives it rejected. The Open questions section records how each review finding was resolved. There is also a comparison with
mise trust.🤖 Generated with Claude Code