Govern Kiro (AWS)
Surfaces used: coding assistant hooks
Modes supported: Local Hosted Hybrid
Tiers: Free (individual) Teams (org-wide enrollment)
SDK: Python (pip install controlzero)
Kiro is AWS's agentic IDE, paired with kiro-cli. Control Zero deterministically evaluates Kiro CLI's preToolUse events before shell commands, file operations, MCP tools, and AWS API calls execute. A deny rule stops the call, and every evaluation is recorded in the audit trail.
-
Kiro CLI (
kiro-cli chat): 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). A deny rule stops the call before it runs, across every tool family, including argument-level deny rules. Validate any install withcontrolzero kiro verify.Two things are true at once here, and they are different kinds of fact. Reach is a vendor fact and is stated as scope on the tag. Maturity is a claim about our adapter, and it is the only thing the tier describes.
What is gated, driven against
kiro-cli2.18.1 with a nonce side-effect proving each call actually ran:- A direct tool call from the parent reaches
preToolUsewith full arguments, and exit 2 stops it before it executes. - A sub-agent's own tool calls reach
preToolUseand a child hook exiting 2 blocks them -- when that sub-agent runs an agent config that carries our hooks.
Denying the
use_subagentspawn itself also works when driven (exit 2 blocks the spawn, and the sub-agent is never created), butcontrolzero install kiroonly wiresuse_subagentas apreToolUsematcher from Python SDK 1.13.12 or later. On any earlier SDK the hook is never invoked for that tool, so adenyrule naming it silently never fires -- it is a no-op that looks exactly like a rule that never matched. Checkcontrolzero --versionbefore relying on it. It is documented here rather than presented as available: a mitigation you cannot switch on is not coverage.What is not gated, and why: Kiro resolves hooks from the sub-agent's own agent config and does not inherit them from the parent. A sub-agent spawned without an explicit
agent_nametherefore runs against an implicit config that carries no hooks, and nothing fires inside that sub-agent. The spawn itself is unaffected: it is a tool call by the parent, the parent is hooked, andpreToolUsefires for it either way. Settingchat.defaultAgentto a hooked agent does not close this: driven, the implicit agent does not resolve tochat.defaultAgent. Naming anagent_namewhose config has no hooks is ungated for the same reason. That is the host's limit (kirodotdev/Kiro#7755, open, no maintainer response), not an unbuilt feature on our side, and it is why the adapter still declares its coveragepartialrather than fully enforced.use_subagentis the only spawn tool this build exposes.delegateis experimental and disabled,agent_crewappears in the binary's alias table but is not exposed, andspawn_sessionis a command value inside a session tool rather than a tool name -- so do not treat them as four gateable spawn surfaces.Kiro matches hook matchers by alias family, not literal tool name: an
execute_bashmatcher fires ontool_name: "shell", andfs_writeon"write".Do not cite Kiro #7671Kiro #7671 -- "hooks do not trigger in sub-agents", filed against
kiro-cli0.11.130 -- is closed as completed. The vendor fixed it. Any statement that Kiro never runspreToolUsein a sub-agent is false on current builds; the live limitation is the config-scoping one above, tracked as #7755. - A direct tool call from the parent reaches
Kiro IDE is the opposite case and we state it plainly: current Kiro GA ships a deterministic hook engine that delivers tool identity and arguments and blocks on exit code 2, and Control Zero is not yet wired to it end to end. That gap is ours, not the vendor's.
Use the Python SDK for Kiro. That is the install path below, and it is the
path that carries prompt-surface scanning and correct kiro_cli audit
attribution. The published Node SDK wires Kiro CLI tool calls through the
deprecated controlzero kiro init, but it does not scan the prompt surface and
its audit rows are attributed to the Claude Code adapter, so Kiro coverage there
is narrower than it looks. controlzero install kiro is Python-only syntax.
requires_approval (HITL) policies are enforced on the CLI as a hard block (stop-and-review), because Kiro CLI has no native interactive approval state.
:::
Install
Install the Control Zero CLI (requires Python 3.9+):
pip install controlzero
Wire Kiro CLI the same way as every other agent:
controlzero install kiro --surface cli
The installer writes a default policy to ~/.controlzero/policy.yaml (ALLOW ALL by default, so Kiro keeps working out of the box) and registers the enforcing hook. Add a deny rule and the matching Kiro CLI call stops before execution. Remove the wiring with controlzero uninstall kiro.
The older controlzero kiro init (and controlzero kiro init --uninstall) still works as a deprecated alias, so existing scripts keep running -- but prefer controlzero install kiro / controlzero uninstall kiro.
How it works
Kiro CLI passes each event as snake_case JSON on stdin, byte-compatible with what controlzero hook-check consumes. The installer pins the hook to "<python>" -m controlzero hook-check --strict (the <python> path is machine-specific), so it cannot silently resolve to a different binary on PATH. Strict mode fails closed when it cannot establish coverage. Signed policy bundles are verified before evaluation, with tampering detected rather than silently accepted.
Every Kiro CLI hook evaluation writes an allow or deny result to ~/.controlzero/audit.log with the Kiro CLI audit source. Because coverage is tracked per event, “the hook did not run” remains distinguishable from “the hook ran and found no violation.”
Policy
Policies live at ~/.controlzero/policy.yaml. Rules are evaluated top to bottom, first match wins. A per-project controlzero.yaml overrides the global file.
version: '1'
rules:
# Block destructive shell on the CLI surface.
# Author shell rules against the canonical `bash:*` tool: Control Zero
# normalizes every agent's shell tool (Kiro's `execute_bash`, Codex's `shell`,
# Gemini's `run_shell_command`, ...) to `Bash`, so one canonical shell rule
# can be reused across enforcing hosts, including Kiro CLI.
- id: deny-shell
deny: 'bash:*'
reason: 'Kiro runs in read-only mode in this workspace'
# Block AWS mutations from the agent
- id: deny-aws-writes
deny: 'use_aws'
reason: 'AWS mutations go through CI, not the agent'
# Allow everything else (remove for deny-by-default)
- id: allow-everything-else
allow: '*'
reason: 'Default allow. Tighten by adding deny rules above this one.'
Common Kiro tool names
| Tool (CLI aliases) | What it does |
|---|---|
fs_read / read | File read |
fs_write / write | File write |
execute_bash / shell | Shell execution |
use_aws / aws | AWS API call |
@<server>/<tool> | MCP tools, namespaced |
Test a decision without running Kiro
echo '{"hook_event_name":"preToolUse","tool_name":"execute_bash","tool_input":{"command":"rm -rf /"}}' \
| controlzero hook-check
Kiro IDE
Kiro IDE does not enforce or audit through the currently installed hooks: Kiro 1.0.x loads *.json, while the installer writes *.kiro.hook. Use Kiro CLI or the Python SDK directly for enforcement.
Verify
# Confirm the CLI hook is wired AND blocks a synthetic deny (exit 0 = enforcing):
controlzero kiro verify
# CLI: reload your shell, then run kiro-cli chat -- every tool call shows
# "[Control Zero] Allowed: <tool>" or a branded deny reason.
kiro-cli chat
# Tail the audit log
tail -f ~/.controlzero/audit.log
See also
- Coding assistant hooks -- the full hook model and per-agent support table
- Govern your coding assistant -- the use-case walkthrough
- Python SDK -- for argument-level policy and in-process governance