From 447b6744bc12acd05c8fd228d8c0e7198c2e715b Mon Sep 17 00:00:00 2001 From: Paavo Pokkinen Date: Wed, 26 Aug 2026 09:04:04 +0300 Subject: [PATCH 1/3] docs(readme): reframe security model around trust zones The old section was a list of implementation details (RLIMIT_CORE, Aho-Corasick, SIGTERM timings) that told a reader how Airlock works but not why it matters. Recast it as three trust zones: the daemon running unsandboxed as the user, the agent in a sandbox shaped for an agent, and each tool in its own sandbox shaped for that tool. Two points were previously implicit and are now stated outright: the daemon deliberately runs outside any sandbox (it needs the user's real logins to mint scoped tokens), and per-tool sandboxing is the property most comparable setups lack. The low-level mitigations survive as a single paragraph; the full detail stays in SECURITY.md. --- README.md | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0f7139f..1db0f37 100644 --- a/README.md +++ b/README.md @@ -296,16 +296,31 @@ Profiles bundle sandbox rules for a known agent: ## Security model -The daemon is the trust boundary; the client (`airlock exec`) is unprivileged and never sees raw values. The Unix socket is the boundary's edge — created `0700` and verified after bind; the daemon refuses to start if the filesystem didn't honor it. +Airlock splits your machine into three zones with different levels of trust: -- Secret env vars are cleared from the daemon's process immediately after reading; values live in a `Secret` that zeroes on drop and refuses to `Debug`-print. -- No core dumps (`RLIMIT_CORE = 0`); on Linux, `PR_SET_DUMPABLE = 0` also blocks same-UID `ptrace`. -- Tools get a minimal environment: declared `env` plus `PATH`, `HOME`, `TERM`, `USER`, `TZ`, and `LANG`/`LC_*`. Nothing else leaks through. -- Output is redacted across four encodings by a streaming Aho-Corasick automaton that handles matches spanning chunk boundaries. -- Each tool runs in its own process group with timeout enforcement (SIGTERM, 5 s, SIGKILL) and cleanup on client disconnect. -- Sandboxing is a hard requirement: Landlock unavailable → the daemon refuses to start rather than degrade. +``` +┌─ your session (no sandbox) ─────────────────────────────────────────┐ +│ airlock daemon — runs as you, outside any sandbox │ +│ holds secrets in memory · uses your real logins to mint tokens │ +│ │ +│ ┌─ agent sandbox ───────────┐ ┌─ tool sandbox (per exec) ───┐ │ +│ │ claude / codex / … │ │ gh · gcloud · kubectl · … │ │ +│ │ sees: project files, │───▶│ sees: project files, its │ │ +│ │ redacted tool output │ │ own config, and only the │ │ +│ │ never sees: secrets │ │ secret it was declared for │ │ +│ └───────────────────────────┘ └─────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +- **The daemon is trusted and runs unsandboxed, as you.** That is deliberate: it needs your real `gcloud` login or `op` session to mint scoped tokens, and it is the one place raw secrets live. The agent can reach it only over the Unix socket, and all it can say there is "run tool X". +- **The agent gets a sandbox shaped for an agent.** Read/write to the project and its own state directory, nothing else — no `~/.ssh`, no keychain, no daemon memory. `airlock run` provides this; Claude Code's `--sandbox` or a container works too. +- **Each tool gets its own sandbox, shaped for that tool.** This is the part most setups skip. `gh` sees the repo and its own config dir but not `~/.config/gcloud`; `gcloud` gets the reverse. A compromised or misbehaving tool can expose at most the one secret it was handed — and even that is redacted before the agent reads it. + +Two sandboxes because the agent and the tools have different jobs: the agent needs wide read access to reason about code but no credentials; a tool needs one credential and almost nothing else. Giving both the same sandbox forces you to grant the union. + +Under the hood, the daemon clears secret env vars from its own process after reading them, keeps values in memory that is zeroed on drop, disables core dumps, hands each tool a minimal environment with a timeout, and refuses to start if the OS sandbox is unavailable rather than run without it. Redaction is streaming and covers raw, base64, URL-encoded, and hex forms. -Known limits: redaction is best-effort (a shell could reverse a string), tools have unrestricted network access, and a local root user can read daemon memory. See [SECURITY.md](SECURITY.md) for the full analysis and mitigations. +Known limits: redaction is best-effort (a tool can transform a secret in ways the redactor doesn't recognize), tools have unrestricted network access, and a local root user can read daemon memory. See [SECURITY.md](SECURITY.md) for the full threat model and mitigations. ## Troubleshooting From f6ef892ab21aefb6e5b69b5dcfe283ee62b7a4b8 Mon Sep 17 00:00:00 2001 From: Paavo Pokkinen Date: Wed, 26 Aug 2026 09:06:18 +0300 Subject: [PATCH 2/3] docs(readme): note tools can be cut off from global config too The "different jobs" paragraph implied a tool needs only a credential, which glosses over tool config files. Spell out that those can be redirected to a project-local path (CLOUDSDK_CONFIG, KUBECONFIG) so a sandboxed gcloud or kubectl never sees the user's privileged global login, only the minted token. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1db0f37..ae4ace0 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ Airlock splits your machine into three zones with different levels of trust: - **The agent gets a sandbox shaped for an agent.** Read/write to the project and its own state directory, nothing else — no `~/.ssh`, no keychain, no daemon memory. `airlock run` provides this; Claude Code's `--sandbox` or a container works too. - **Each tool gets its own sandbox, shaped for that tool.** This is the part most setups skip. `gh` sees the repo and its own config dir but not `~/.config/gcloud`; `gcloud` gets the reverse. A compromised or misbehaving tool can expose at most the one secret it was handed — and even that is redacted before the agent reads it. -Two sandboxes because the agent and the tools have different jobs: the agent needs wide read access to reason about code but no credentials; a tool needs one credential and almost nothing else. Giving both the same sandbox forces you to grant the union. +Two sandboxes because the agent and the tools have different jobs: the agent needs wide read access to reason about code but no credentials; a tool needs one credential plus its own config files, and nothing else. Even the config files can be kept out of your real home directory — point `CLOUDSDK_CONFIG` or `KUBECONFIG` at a project-local path (as in [Minting scoped credentials](#minting-scoped-credentials)) and `gcloud` or `kubectl` runs with only the minted token, never your privileged global login. Giving agent and tools the same sandbox would force you to grant the union. Under the hood, the daemon clears secret env vars from its own process after reading them, keeps values in memory that is zeroed on drop, disables core dumps, hands each tool a minimal environment with a timeout, and refuses to start if the OS sandbox is unavailable rather than run without it. Redaction is streaming and covers raw, base64, URL-encoded, and hex forms. From 2852d07e353ab0b517adb94e524f2aabcf436bae Mon Sep 17 00:00:00 2001 From: Paavo Pokkinen Date: Wed, 26 Aug 2026 09:08:45 +0300 Subject: [PATCH 3/3] docs(readme): tighten security model wording Drop the "run tool X" aside and the closing "union" sentence, which overstated the point; soften "nothing else" to "usually" since tools vary in what they need. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae4ace0..ef81df9 100644 --- a/README.md +++ b/README.md @@ -312,11 +312,11 @@ Airlock splits your machine into three zones with different levels of trust: └─────────────────────────────────────────────────────────────────────┘ ``` -- **The daemon is trusted and runs unsandboxed, as you.** That is deliberate: it needs your real `gcloud` login or `op` session to mint scoped tokens, and it is the one place raw secrets live. The agent can reach it only over the Unix socket, and all it can say there is "run tool X". -- **The agent gets a sandbox shaped for an agent.** Read/write to the project and its own state directory, nothing else — no `~/.ssh`, no keychain, no daemon memory. `airlock run` provides this; Claude Code's `--sandbox` or a container works too. +- **The daemon is trusted and runs unsandboxed, as you.** That is deliberate: it needs your real `gcloud` login or `op` session to mint scoped tokens, and it is the one place raw secrets live. The agent can reach it only over the Unix socket. +- **The agent gets a sandbox shaped for an agent.** Read/write to the project and its own state directory, nothing else — no `~/.ssh`, no keychain, no daemon memory. `airlock run` provides this; Claude Code's own `--sandbox` or a container works too. - **Each tool gets its own sandbox, shaped for that tool.** This is the part most setups skip. `gh` sees the repo and its own config dir but not `~/.config/gcloud`; `gcloud` gets the reverse. A compromised or misbehaving tool can expose at most the one secret it was handed — and even that is redacted before the agent reads it. -Two sandboxes because the agent and the tools have different jobs: the agent needs wide read access to reason about code but no credentials; a tool needs one credential plus its own config files, and nothing else. Even the config files can be kept out of your real home directory — point `CLOUDSDK_CONFIG` or `KUBECONFIG` at a project-local path (as in [Minting scoped credentials](#minting-scoped-credentials)) and `gcloud` or `kubectl` runs with only the minted token, never your privileged global login. Giving agent and tools the same sandbox would force you to grant the union. +Two sandboxes because the agent and the tools have different jobs: the agent needs wide read access to reason about code but no credentials; a tool usually needs one credential plus its own config files. Even the config files can be kept out of your real home directory — point `CLOUDSDK_CONFIG` or `KUBECONFIG` at a project-local path (as in [Minting scoped credentials](#minting-scoped-credentials)) and `gcloud` or `kubectl` runs with only the minted token, never your privileged global login. Under the hood, the daemon clears secret env vars from its own process after reading them, keeps values in memory that is zeroed on drop, disables core dumps, hands each tool a minimal environment with a timeout, and refuses to start if the OS sandbox is unavailable rather than run without it. Redaction is streaming and covers raw, base64, URL-encoded, and hex forms.