Threat model¶
This document states, honestly, what kew defends against and what it does not. kew runs autonomous coding agents against your code; that is inherently a position of trust, and the value of a control plane is bounding the blast radius when something the agent does goes wrong. Knowing the edges of that boundary is part of using kew safely.
For how to report a vulnerability, see SECURITY.md. For the concrete command-blocking rules, see docs/security.md.
Trust model¶
- The control-plane host is the trust boundary. The machine running
kew— its OS, its filesystem, its environment (whereANTHROPIC_API_KEY,ghcredentials, etc. live), and the user accountkewruns as — is trusted. kew does not defend the host against itself. - The operator is trusted. kew is a tool you run on your own machine to govern
agents, not to defend against yourself. (One consequence — see the audit section —
is that the governance log is hash-chained and externally anchorable, so tampering is
detectable (
kew audit verify); but because the host and the GitHub Actions token are trusted, a host-level actor can still rewrite history and re-anchor it. So the log is tamper-evident within stated limits, not tamper-proof — kew is not an HSM.) - The runner CLIs are trusted components. kew shells out to
claude,opencode, andpi. A malicious or compromised runner binary is outside kew's boundary — kew governs what it asks the runner to do and what the runner's process is allowed to do, not the integrity of the runner itself.
Primary adversary: the agent kew governs¶
The main thing kew defends against is the autonomous agent itself misbehaving — not
an external network attacker. A coding agent is semi-trusted: it does useful work, but
it can be prompt-injected (via issue text, repository content, dependency READMEs, or
web content it reads), misaligned, or simply buggy and reckless — especially when
run with --dangerously-skip-permissions. kew's job is to make a misbehaving agent's
blast radius small, bounded, and observable.
What kew defends against (mechanism → threat)¶
| Mechanism | Threat it bounds |
|---|---|
Security hook (PreToolUse on Bash/Write/Edit, fires even with permissions skipped) — see security.md for the exact patterns |
An agent running destructive commands (rm -rf /, git push --force, sudo), exfiltrating credentials (echo $ANTHROPIC_API_KEY, printenv), piping the network to a shell (curl … \| bash), or editing its own guardrails under .claude/. This is a denylist — see non-goal #1 |
| Worktree isolation | Work happens in an isolated git worktree, never on your main checkout — limiting accidental damage to the working tree |
| Verification gate + diff-guard (anti-proxy) | An agent claiming success it didn't achieve, or editing its own tests / verify command / CI config to make a red task look green. achieved requires a real gate pass on a diff that did not touch the guarded paths |
| Per-run budgets + mid-flight watchdog (#291) | Runaway spend — a loop is killed when cumulative cost crosses its cap |
| Wall-clock + step caps | A loop that never terminates or thrashes — bounded regardless of cost signal |
Fleet kill switch (kew stop) |
A fleet of agents collectively going wrong — one command terminates the process groups |
| Audit envelope (#246) | Unreviewable activity — dispatches, gate verdicts, budget events, and pause/kill are recorded to a local hash-chained log (kew audit tail / export), on a best-effort basis (a failed write is logged and dropped, never blocks the agent). Integrity is checkable with kew audit verify and the chain head is externally anchorable, so it is tamper-evident within stated limits (not tamper-proof — see non-goal #3) |
What kew does not defend against (explicit non-goals)¶
These are real limitations, stated plainly so they are footnotes rather than surprises.
-
kew is a guardrail, not a sandbox. The security hook is a denylist of known-bad command shapes, not OS-level isolation. A sufficiently clever or determined agent running with skipped permissions may find an egress or destruction path the denylist does not cover. For true containment, run kew (and the agent) inside an OS-level sandbox — a container, VM, or a dedicated unprivileged user with network egress controls. kew reduces risk; the OS provides isolation.
-
Egress control depends on the sandbox; the guardrail hook alone does not bound it. The security hook blocks common exfiltration command shapes, but an agent with shell access and skipped permissions has many possible network paths, so the hook alone does not contain egress. The Docker sandbox does:
[sandbox] networkdefaults to"allowlist", joining the container to the pre-configuredkew-allowlistnetwork (the operator adds iptables rules restricting egress to the inference endpoint + GitHub) and failing closed if that network is absent — see docs/security.md. That restriction is only as tight as the operator's iptables rules, though, and an unsandboxed run — or one that opts intonetwork = "open"— has no such control. Treat any data an unsandboxed agent can read (your repo, your local filesystem within reach, environment it can enumerate despite the hook) as potentially reachable by the model provider and beyond. Don't point an unsandboxed agent at secrets you can't rotate.
What a sandboxed agent can reach in the first place is bounded by the per-runner credential env allowlist (see docs/security.md): a contained run is handed only the credentials its own runner declares, by name, so a compromised or prompt-injected opencode container has no Anthropic token to exfiltrate and a claude container has no OpenRouter key. This narrows the blast radius of a successful injection inside the sandbox; it does not prevent the injection, and it does not protect the one credential the runner legitimately needs.
-
The operator is not an adversary kew defends against — and the audit log is tamper-evident, not tamper-proof. The audit envelope is a local, hash-chained SQLite log: every event carries
prev_hash(and apolicy_hashof the governance policy in force), so any after-the-fact edit breaks the chain and is caught bykew audit verify. The chain head can be anchored outside the host (#249, #250 — shipped in M3), which is what makes tampering detectable to a third party rather than only to you. But a host-level actor who controls the GitHub Actions token can still rewrite the log and re-anchor it — kew is not an HSM — and a failed write is still dropped rather than blocking work. So it is tamper-evident within the limits stated in docs/audit.md (no MAC; the witness is the Actions run log within its retention window), not cryptographic proof against the host itself. -
The control-plane host and the runner CLIs are trusted. kew does not defend against a compromised host, a malicious local user on that host, a tampered runner binary, or theft of the credentials in the host environment. Host hardening and credential hygiene are the operator's responsibility.
-
Prompt injection is not prevented. kew does not sanitize issue text, repository content, or anything else the agent reads. A crafted payload can steer the agent; the security hook and the anti-proxy diff-guard bound some downstream consequences (it can't run blocked commands, and editing its own tests/CI won't fake a pass), but the injection itself is not stopped. This is another reason to sandbox (non-goal #1).
Recommended hardening¶
- Run kew and its agents in an isolated environment (container / VM / dedicated user).
The Docker sandbox ships egress restricted by default —
[sandbox] network = "allowlist"joins thekew-allowlistnetwork and fails closed if it is absent — but you must create that network with iptables rules restricting egress to the inference endpoint(s) and GitHub; kew enforces the fail-closed requirement, not the rules themselves. - Use scoped, rotatable credentials (a fine-grained
ghtoken, a project-scoped API key) — never long-lived org-wide secrets in the agent's reach. - Keep
--dangerously-skip-permissionsfor sandboxed contexts only; the security hook is a backstop, not a substitute for isolation. - Add project-specific denials via
[security] blocked_prefix_patterns/blocked_exact_patternsinkew.toml. - Review the governance record with
kew audit tail/kew audit export, and check its integrity withkew audit verify. Anchor the chain head outside the host (kew audit anchor/ the anchoring workflow) so tampering is detectable to a third party — within the limits in docs/audit.md (kew is not an HSM; a host actor with the anchor token can still rewrite and re-anchor).
Scope and evolution¶
This threat model reflects kew's current posture. M3 (tamper-evidence) has shipped — the
hash chain, kew audit verify, policy binding, and external anchoring moved the audit row
from "auditable" to "tamper-evident within stated limits." The longer-horizon air-gapped /
verifiable-egress work (docs/roadmap.md) is what moves the remaining "does
not defend" rows into the "defends" column; it ships before it is marketed.