Skip to main content

Enforcement Coverage by Tool

Supported modes: Hosted Hybrid Local Available in: Free Solo Teams

Control Zero provides deterministic enforcement on Claude Code, Gemini CLI, Cursor IDE, and Kiro CLI. On every covered event, a deny stops the tool call before it executes. Coverage is declared per event rather than per host, so the product never implies protection for an action the vendor does not route through an enforceable hook. Kiro IDE is not currently an enforcement surface.

How it works

Control Zero governs a coding agent by attaching to that agent's own official pre-execution hook -- the point the vendor provides for an external system to inspect, and approve or deny, an action before it runs:

  • Claude Code: PreToolUse
  • Gemini CLI: BeforeTool
  • Codex CLI, Kiro CLI, Antigravity, and Cursor IDE use vendor-specific pre-tool hooks.

Three facts follow from this design:

  1. Coverage is established for each event. If a vendor sends a tool call to its enforceable pre-execution hook, Control Zero sees it, decides on it, and enforces a deny. If an action never reaches that hook, the event is marked uncovered rather than inheriting a host-level claim.
  2. Vendors expose different surfaces, and they evolve them. Some tools route every action through one hook today; others are still widening their hook coverage. Both are normal points on the same trajectory, and the trajectory is toward more coverage.
  3. Control Zero fails closed by default. When it cannot establish a policy outcome, it denies rather than guesses. Bundle-resolution failures default to deny, and signed bundles expose tampering. One host-adapter boundary remains: on exit-code hosts, unreadable hook input or policy files currently proceed before policy evaluation; that pre-parse path is being hardened.

What Control Zero guarantees

For every tool a vendor exposes to its pre-execution hook and enforces a deny on:

  • A Control Zero deny blocks the action before it runs -- not after and not as a warning. This is hard enforcement on Claude Code, Gemini CLI, Cursor IDE, and direct Kiro CLI tool calls.
  • The decision path fails closed by default: missing bundles and ambiguous policy outcomes deny rather than allow.
  • The audit trail records the decision and coverage for every event. It distinguishes did not run from ran and found nothing, so operators can prove whether a control actually executed.
  • The capability matrix below is derived from the adapters' own CoverageDecl declarations, and this page is maintained by hand. Run controlzero coverage --json to export those declarations for your installed SDK -- the --json flag is what emits the machine-readable matrix; a bare controlzero coverage prints only a short per-host summary. It reads your actual installation, so it is the authority if the two ever disagree. Tell us when they do.

Coverage by tool

The matrix declares coverage for each event path. A vendor-set boundary is an action the vendor does not currently expose to an enforceable hook; it does not weaken enforcement on covered events.

Tool / SurfaceWhat Control Zero enforcesVendor-set boundary (and why)Tip to maximize coverage
Claude CodeComplete within Claude Code's hook surface: every tool via PreToolUse. A deny is delivered as a top-level decision: "block" (a deprecated-but-honored alias Claude Code still accepts) plus exit code 2 -- the exit code is the reliable floor.None material -- Claude Code routes its tools through PreToolUse.Already complete; keep the hook installed. Use Claude Code >= v2.1.90 for the most reliable hook-decision handling.
Gemini CLIComplete within Gemini CLI's hook surface: every tool via BeforeTool. A matched rule deterministically denies via a stdout decision: "deny" and/or exit code 2 before the tool runs.None material -- Gemini CLI routes its tools through BeforeTool.Keep the hook installed. Gemini's own BeforeTool decision: "ask" HITL support landed in PR #21146 (merged 2026-03-21); pin a Gemini CLI build that includes it before relying on ask-style confirmation.
Codex CLIIn the interactive TUI, Bash commands are reliably enforced via Codex's PreToolUse hook (deny blocks before the command runs).Several limits are set by Codex (vendor-attributed, with the upstream issue): (1) apply_patch / MCP deny is not enforced by Codex today -- the hook fires but the write still proceeds (upstream Codex #27833, open); (2) headless codex exec dispatches no hooks at all (upstream Codex #26452, open) -- it fails open and silently, so unattended / CI Codex runs are ungated; (3) Codex does not expose unified_exec or WebSearch to its hook.Govern Codex in the interactive TUI (where Bash is enforced), not via codex exec, until upstream fixes the headless and apply_patch/MCP deny paths. Add Codex's own command-allowlist as a complementary layer.
Kiro CLI -- GA (direct tool calls gated, and sub-agent spawns from SDK 1.13.12; a sub-agent's own tool calls only when it runs an agent config carrying our hooks)Deterministic enforcement for direct tool calls via preToolUse; a deny blocks via exit code 2 before the tool runs. When a sub-agent runs an agent config that carries our hooks, preToolUse fires inside that sub-agent and a child hook exiting 2 blocks the sub-agent's own tool call. Driven against kiro-cli 2.18.1.Kiro resolves hooks from the sub-agent's own agent config and does not inherit the parent's. A sub-agent spawned without an explicit agent_name therefore runs against an implicit config carrying no hooks, so its own tool calls are not gated once it starts (the spawn itself still is -- the parent's hook fires either way) -- pointing chat.defaultAgent at a hooked config does not change that (driven; the implicit agent does not resolve to it). Naming an agent config that has no hooks is ungated for the same reason. That is the host's limit (upstream Kiro #7755, open), and it is why the adapter still declares coverage partial. use_subagent is the only spawn tool this build exposes: delegate is experimental and disabled, agent_crew is in the alias table but not exposed, and spawn_session is a command value inside a session tool rather than a tool name.Give any sub-agent you allow its own agent config carrying the Control Zero hooks -- that is what makes its tool calls reach preToolUse at all. Restrict toolsSettings.subagent.availableAgents / .trustedAgents, and narrow tools in the sub-agent's own config, as complementary vendor-side layers. (Denying the use_subagent spawn also blocks when driven, but the matcher only ships from Python SDK 1.13.12 or later -- on any earlier SDK such a rule silently never fires, so check controlzero --version before relying on it.)
Kiro IDE -- COMING SOONControl Zero does not enforce or record tool calls on Kiro IDE today. Absence from your audit log is not evidence a call did not happen on this surface.This gap is ours, not Kiro's. Current Kiro GA ships a deterministic hook engine: it delivers {tool_name, tool_input} on stdin for PreToolUse / UserPromptSubmit / PreTaskExec and blocks on exit code 2. The capability is real and reachable; our hooks are not yet wired to it end to end.Use Kiro CLI for enforceable direct tool calls; mask rules become denies.
Antigravity IDE -- BETAAll nine tool families -- including execute (shell) and file_write -- are delivered to Antigravity's PreToolUse hook, evaluated against your policy, recorded, and DLP-scanned, and a matched deny rule raises a deny. Our adapter fails closed: empty output is read as a deny. Separately, and this is the host: Antigravity treats that deny as advisory in its headless and always-proceed modes, so the call may still run. Recorded is not stopped, and the host does not expose which mode it is in, so we cannot tell you per call which happened. The IDE and the agy CLI share the same ~/.gemini/config/hooks.json contract; the audit log tags each surface (Antigravity IDE / Antigravity CLI).Whether a raised deny is imposed is decided by Antigravity's run mode, which the host does not make observable to a hook. That is the boundary -- not the set of tools delivered, which is complete.Treat Antigravity as observe-and-advise, not a hard block: use Claude Code, Gemini CLI or Cursor IDE when you need a deny that is guaranteed to stop the call. Install with controlzero install antigravity --surface ide to tag IDE actions.
Antigravity CLI (agy) -- BETASame reach as the IDE: all nine tool families reach PreToolUse, are evaluated, recorded and DLP-scanned, and a matched rule raises a deny. agy decides from the stdout JSON decision (the exit code stays 0 -- agy's documented contract is the stdout token, not the exit code); Control Zero always emits an explicit decision (agy reads an empty {} as invalid_args). As on the IDE, the host may treat that deny as advisory in headless / always-proceed modes, so the call may still run. Shares the IDE's hook contract; the audit source distinguishes the two surfaces.Same as the IDE: the host's run mode decides whether a deny is imposed, and it is not observable from a hook.Use Claude Code, Gemini CLI or Cursor IDE for a deny that is guaranteed to stop the call. Pin a recent agy build. Install with controlzero install antigravity --surface cli to tag agy actions.
Cursor IDE -- BETAThe shipped Control Zero adapter deterministically enforces denies across all nine tool families, on six deciding events: beforeShellExecution, beforeMCPExecution, beforeReadFile, preToolUse, subagentStart and beforeSubmitPrompt. A deny rule stops the call before it runs. Unlike Kiro CLI, sub-agent starts ARE gated here. The install sets failClosed: true, which governs how Cursor treats a hook that fails to run; it is a separate setting from the deny path, and an empty hook response on this surface means proceed.Cursor supports deny as its blocking hook outcome; it does not support cooperative ask enforcement, so Control Zero converts ask to deny. Cursor cannot accept rewritten tool input, so a DLP mask rule escalates to a deny rather than letting the raw value through. afterFileEdit is a post-execution audit event, not a blocking event.Install with controlzero install cursor; audit rows carry source = "cursor_ide". The BETA is about our adapter, not this reach: it has not yet been revalidated against a current Cursor build.
Cursor CLI (cursor-agent) -- BETAThe Control Zero Cursor adapter is shipped; the same controlzero install cursor writes the .cursor/hooks.json that the cursor-agent CLI also reads. A deny rule stops an execute (shell) call before it runs, with failClosed: true. Shell only, because that is the family this surface delivers to a hook -- the other eight are never delivered, so their absence from your audit log is not evidence they did not happen.Same vendor limits as Cursor IDE: only deny is enforced; hooks fail open by default unless installed with failClosed: true (the adapter sets it). The exact CURSOR_* env marker on a live cursor-agent run is still being validated.Install with controlzero install cursor; it installs failClosed: true. Audit rows carry source = "cursor_cli". The BETA is about our adapter, not the shell-only reach.

What to expect

  • Coverage grows automatically. As vendors expand the actions they route through their hooks, and as we ship the matching adapter updates, coverage widens with no policy changes on your side.
  • Coverage stays event-specific. Kiro IDE remains unenforced, while direct Kiro CLI calls are deterministically enforced. A Kiro CLI sub-agent's own tool calls do reach preToolUse, and a child hook exiting 2 blocks them -- when that sub-agent runs an agent config carrying our hooks. The uncovered case is narrower than "sub-agent calls": a sub-agent spawned without an explicit agent_name runs an implicit config that carries no hooks, so its own calls are ungated once it starts (kirodotdev/Kiro#7755, open).
  • The dashboard tells you exactly where you stand. Each audit event carries its source and event-level coverage, driven by the same CoverageDecl declarations this page is written from. This page is maintained by hand; controlzero coverage --json reads the declarations in your own installation and is the authority if the two disagree.