Skip to content

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 (where ANTHROPIC_API_KEY, gh credentials, etc. live), and the user account kew runs 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, and pi. 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.

  1. 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.

  2. 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] network defaults to "allowlist", joining the container to the pre-configured kew-allowlist network (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 into network = "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.

  1. 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 a policy_hash of the governance policy in force), so any after-the-fact edit breaks the chain and is caught by kew 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.

  2. 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.

  3. 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).

  • 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 the kew-allowlist network 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 gh token, a project-scoped API key) — never long-lived org-wide secrets in the agent's reach.
  • Keep --dangerously-skip-permissions for 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_patterns in kew.toml.
  • Review the governance record with kew audit tail / kew audit export, and check its integrity with kew 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.