Clover for Kiro
Clover security review inside Kiro, AWS's spec-driven
agentic IDE (and the Kiro CLI). Same binary and same /Hooks/* backend as the
Claude Code and Cursor surfaces — only the event plumbing differs, and it lives
in cmd/clover-hook/agent_kiro.go, not in these scripts.
Why Kiro fits
Kiro writes its plan to disk as a spec —
.kiro/specs/<feature>/{requirements,bugfix,design,tasks}.md, or the whole spec
in a single spec.md in its newer single-file flow — and runs tasks
from it, firing a blocking PreTaskExec immediately before implementation.
That gives Clover a real plan artifact plus the closest analogue to Claude's
ExitPlanMode gate on any non-Claude agent.
| Trigger | Subcommand | Blocks |
|---|---|---|
SessionStart | kiro-check-update | no |
UserPromptSubmit | kiro-log-prompt | no |
PostFileSave on .kiro/specs/**/*.md | kiro-capture-spec | no |
PreTaskExec | kiro-pre-task | yes |
The review loop
PostFileSave drives the loop and PreTaskExec enforces it:
- Kiro finishes writing a spec. Capture reviews it and writes the security
requirements Clover returns to
.kiro/steering/clover-requirements-<spec>.md(inclusion: always), which Kiro includes in every subsequent interaction — alongside the.clover-requirements.mdsidecar beside the spec. The developer's next prompt reprints them too, onUserPromptSubmit. - The agent folds them into the spec and saves. That save is a genuine revision, so capture sends it for judgement; Clover approves only when the requirements are actually covered — a cosmetic "we take security seriously" edit comes back denied with the musts still standing.
- Starting a task re-reviews the spec and blocks while any must stands.
Capture reviews a spec only when it is finished and settled, and only one review at a time per spec:
- Finished — the file Kiro writes last is present (
tasks.md, orspec.mdin the single-file spec flow). - Settled — the spec content is unchanged across a short window
(
CLOVER_KIRO_SPEC_SETTLE_SECONDS, default 4), so the events for the earlier files of one generation drop out instead of each consuming a review round. - One at a time — a lock file per spec directory, since two saves can settle together.
Those three guards are the fix for the surface's worst bug: capture used to
review on every save, so it reviewed half-written specs, and the next save of
the same generation arrived as a JudgePlan round — which tells the backend the
developer revised the plan to address the requirements. The approval that came
back deleted the requirements sidecar and recorded an approved-plan hash, which
then short-circuits the gate. Requirements found, silently discarded, gate off.
Delivery channels are not interchangeable. Kiro's own contract: exit 0
stdout is forwarded only for SessionStart, UserPromptSubmit and
PreToolUse; exit 2 stderr is forwarded for PreToolUse,
UserPromptSubmit and PreTaskExec; anything else is a silent failure. A
PostFileSave hook therefore cannot speak to the agent at all — its stdout
is dropped on the floor. That is why a spec review delivers through steering and
the prompt channel, and why the gate's own verdict rides exit 2 on
PreTaskExec.
Kiro's decision contract is the exit code, not JSON: exit 0 proceeds (stdout is appended to the model's context), a non-zero exit blocks and hands stderr to the model. There is no "allow" to emit, so the "decision only when blocking" invariant holds by silence. Because any non-zero exit blocks, every error path exits 0.
Install
macOS and Linux only for now — Windows is not supported yet. The hook
commands and installer are bash; Kiro spawns Windows hook commands through
cmd.exe, so they never run there even though Windows binaries ship in bin/.
Windows support means: hook commands rendered per-OS by the installer to
invoke the binary directly (no shell), the binary loading the credentials
file itself, an install.ps1, a [\\/] path matcher, and validation against
Kiro's own open Windows hook issues (kirodotdev/Kiro#8264).
One command, once per machine — it covers every repository via Kiro's global
~/.kiro/hooks/ path, downloads only the binary for the current platform
(checksum-verified), and prompts for the two credential values:
curl -fsSL https://raw.githubusercontent.com/clover-security-public/agentic-security-marketplace/main/kiro/scripts/install.sh | bash
To install into a single repository instead (the drop-in a team commits to git so cloning developers get the hooks with no install step):
curl -fsSL .../kiro/scripts/install.sh | bash -s -- /path/to/repo
Pick one scope per machine. Kiro loads a repository's .kiro/hooks/ and the
machine-wide ~/.kiro/hooks/ together, so installing both runs every Clover
hook twice — and the repository copy starts with FILL_ME credentials, so one
of each pair fails open. The installer therefore refuses the second scope and
exits 0 with an already installed line: a repository install when
~/.kiro/hooks/clover.json exists, or a machine-wide install run from inside a
repository that has its own copy. CLOVER_ALLOW_DUPLICATE_INSTALL=1 overrides
it, for a team lead with Clover machine-wide who is deliberately creating the
drop-in to commit. It cannot catch the other case: a teammate who has Clover
machine-wide and clones a repository carrying the committed drop-in.
The script also runs from a local checkout of the marketplace tree, copying
instead of downloading. CLOVER_MARKETPLACE_URL overrides the download base
(the beta ring's raw URL, or a mirror). Layout after a machine-wide install:
~/.kiro/
hooks/clover.json # the three hooks above, absolute-path commands
clover/
scripts/run-hook.sh # resolves the platform binary, loads env.sh, exec
bin/clover-hook-<os>-<arch>
env.sh # credentials — prompted, 0600, never committed
Credentials can also be pre-provisioned by dropping env.sh in place (MDM,
dotfiles) — the installer never overwrites an existing one.
The installer asks for four values and writes them to env.sh: client id,
client secret, and the two Clover hosts — API https://api.cloversec.io and
auth https://auth.cloversec.io — offered as defaults, so press enter unless
this is a non-production tenant. CLOVER_SERVER_URL / CLOVER_AUTH_URL preset
them for an unattended install, and the pair is echoed on the last line of the
install output. Wrong hosts are the one failure that looks like success: auth
fails, every hook fails open, and nothing reaches Clover.
Then open a repo in Kiro and run through Verify it works below.
Verify it works
Five minutes, end to end: the install is only proven once a spec review has actually blocked a task. Run it in a scratch repo, not a real one.
1. Hooks loaded. Open the repo in Kiro, then Output panel (⌘⇧U) → the Kiro
agent log channel:
[KiroAgent] v2 hooks loaded 4 standalone hooks from .kiro/hooks/
0 means the loader found no valid file — check the same channel for a schema
warning. Nothing at all, or hooks that load but never fire, is almost always
workspace trust; see Troubleshooting.
2. The hooks run. Send any prompt. You should see one Clover Security card
in the chat, and a fresh line in the hook log:
tail -5 ~/.kiro/clover/.clover-hook.log # or <repo>/.kiro/clover/... for a repo install
3. A spec gets reviewed. Ask Kiro for something with obvious security surface, so the review has something to find:
Create a spec for a login endpoint that accepts an email and password and returns a session token.
Let it finish writing the spec. Capture reviews it once the spec is finished
(tasks.md — or spec.md in the single-file flow) and settled (unchanged
for CLOVER_KIRO_SPEC_SETTLE_SECONDS, default 4), so expect a few seconds of
quiet before anything happens. Then:
ls .kiro/steering/clover-requirements-*.md # requirements Kiro will now always see
ls .kiro/specs/*/.clover-requirements.md # the sidecar beside the spec
Either file appearing means the round trip to Clover worked: the spec was sent, reviewed, and requirements came back. The plan also shows up in the Clover app.
4. The gate blocks. Open the spec's tasks.md and start task 1. While any
must stands, PreTaskExec stops it and hands the requirements to the agent —
you'll see them in the chat rather than the task running.
5. The gate clears. Have the agent fold the requirements into the spec and save, then start the task again. Capture re-judges the revised spec, and once the musts are genuinely covered the task proceeds. A cosmetic edit comes back denied with the same musts — that is the intended behaviour, not a failure.
If steps 1–2 pass but 3 never produces requirements, the spec may simply have
passed review; re-run with a deliberately thin spec, or set CLOVER_DEBUG=1 and
read the hook log to see which spec was resolved and what the backend returned.
When a new Kiro build changes its payload
Field naming has changed between builds and PreTaskExec's shape is
undocumented, so the adapter accepts every spelling seen so far (file_path,
filePath, path, spec_path, specPath, tool_input.specPath) and falls
back to the most recently modified spec directory when an event names none. A
rename is therefore additive rather than breaking.
To see what a new build actually sends, run with CLOVER_DEBUG=1 and read the
diagnostics log — the adapter records the event name and the spec it resolved.
Known constraints
- The IDE delivers an empty
prompt(kirodotdev/Kiro#7500), so prompt audit is a no-op there and posts nothing rather than a blank row; the CLI delivers the real text. Spec review is unaffected — the plan is read from disk. - Kiro does not close the hook's stdin. A read to EOF hangs the turn until
the hook times out, so Kiro's subcommands set
readsOneJSONValueand stop at the end of the payload. - Every hook renders a chat card. Kiro emits it at the engine level before
the command runs; no setting or schema field suppresses it. All hooks are
named
Clover Security, the command and its output are never rendered, and the card collapses to a one-line summary when the turn ends. - Spec tasks can run concurrently, so
PreTaskExecmay fire in parallel. The approved-plan-hash short-circuit inrunPlanReviewdedupes the common case. - Activity is recorded as
CodingAgentType.Kiro. The backend's enum rejects a string it does not know, so a client talking to a backend that predates that member has to name one it has:CLOVER_KIRO_CODING_AGENT=Other. - A power cannot run hooks. Kiro's hook engine reads only
.kiro/hooks/(plus the user-level global hooks dir), never a power's directory — verified on 1.1.70, whose hook registry knows two sources, standalone files and agent profiles. So the hooks install throughinstall.sh, and the power carries guidance only. Kiro's marketing page says powers "bundle MCP tools, steering files, and hooks"; the docs, the official powers (no hook files) and the installer code all disagree. The hook-running plugin loader in 1.1.70 belongs to VS Code's own agent host, which Kiro's agent does not use.
The power
This whole directory is the Kiro power, in the Agent Plugins format:
plugin.json at the root, documentation in dev.kiro/INSTRUCTIONS.md, and
steering in dev.kiro/steering/. Kiro copies the whole directory when it
installs the power, so hooks/ and scripts/ travel along, inert — the hooks
are installed by the one-liner above, run in the developer's own terminal so it
can prompt for credentials. There is no clover-power/ subdirectory and no
POWER.md: with a plugin.json present Kiro ignores the legacy manifest, so
keeping one would only be a second source of truth.
Install it from a Git URL — Powers panel → Add Custom Power → Import power from GitHub — pasting this directory's URL:
https://github.com/clover-security-public/agentic-security-marketplace/tree/main/kiro
Kiro names a GitHub import after the last URL segment, so it appears as kiro.
The beta ring's URL is the same path under clover-security-marketplace-beta;
that repo is private, so the clone authenticates through your local git
credentials.
What the power adds: steering and instructions that teach the agent to treat Clover's requirements as part of the spec, and to hand the developer the install command when asked. It enforces nothing — the hooks do.
It ships no MCP server. Kiro's remote-MCP auth against the Clover streaming endpoint is untested, and a failing MCP entry would make the whole power look broken.
Privacy and support
- Privacy policy: https://clover.security/privacy-policy/
- Terms of use: https://clover.security/terms-of-use/
- Support: https://clover.security/support/
- Product documentation: https://docs.cloversec.io/product-guides/kura-securing-agentic-development/about-kura
The hooks send the developer's prompt, the spec under review and the repository
identity to the Clover tenant configured in env.sh. Nothing else leaves the
machine, and credentials never leave it at all.
Auto-update
Kiro has no plugin manager, so a SessionStart hook (kiro-check-update) keeps
the install current: when the channel's marketplace advertises a newer version,
the binary downloads its own replacement, verifies it against the tree's
checksums.sha256, and swaps it in with an atomic rename. Every failure path
fails open — the session always starts — and an install whose version cannot be
read is never replaced.
- Public installs update automatically from the public marketplace.
- Beta installs cannot self-download (the beta repo is private): they update
by rerunning the installer, or by pointing at an internal mirror with
CLOVER_UPDATE_MANIFEST_URL/CLOVER_UPDATE_TREE_URL. - Only the binary and version manifest are refreshed. The hook config is path-rewritten at install time, so a new or changed trigger still needs a rerun of the installer.
- Cursor runs the same refresh at
sessionStart, on by default on macOS and Linux (CLOVER_CURSOR_SELF_UPDATE=0opts a machine or fleet out). Windows is skipped before any download — a running executable cannot be renamed over there — so Windows Cursor installs update via Cursor's own marketplace flow.
Troubleshooting
Hooks never fire — workspace trust
Most installs need nothing here: accepting Kiro's trust banner when you open a repo is enough. This section is for the case where the hooks load but never run.
Kiro turns every hook into a no-op in an untrusted folder, logging the
suppression only at debug level — valid files, no card, no error, nothing in
Clover. The tell in Kiro's own log is
hooks.v2.executionDisabledUntrustedWorkspace. Every folder starts untrusted,
so this is the most common reason a working install looks dead. Any of these
fixes it:
-
Trust a single folder — open it in Kiro; when the trust banner appears, choose to trust it. Or command palette → Workspaces: Manage Workspace Trust. This is the safest option and the one to demo.
-
Trust a parent folder once — in Manage Workspace Trust, add the folder that contains your repos (e.g.
~/Code) to Trusted Folders. Everything under it is trusted automatically, so new repos need no per-folder step, while a repo cloned elsewhere still prompts. -
Trust every folder (turn the gate off) — add to Kiro's user
settings.json(command palette → Preferences: Open User Settings (JSON)):{ "security.workspace.trust.enabled": false }Convenient, but it also lets hooks committed inside any repo you open run automatically — a deliberate reduction of Kiro's own protection. Prefer the parent-folder option unless you accept that trade-off. For a fleet, push this (or a trusted-folders list) through managed settings / MDM so the org owns the decision rather than each developer.
Diagnostics
CLOVER_DEBUG=1 in env.sh for diagnostics, CLOVER_KIRO_OBSERVE=1 to
downgrade the PreTaskExec gate to observe-only during a staged rollout, and
CLOVER_HOOK_BIN to point at a locally built binary.
The hook log is <install dir>/.clover-hook.log. Two auth failures come from a
wrong host in env.sh — both make every hook fail open, so the symptom is
silence in Clover rather than an error in Kiro:
| In the log | Cause | Fix |
|---|---|---|
auth returned 404 ... Failed to find vendor for host | auth URL is not a Frontegg vendor host (e.g. clover.frontegg.com) | CLOVER_SECURITY_KURA_PLUGIN_AUTH_URL=https://auth.cloversec.io |
| auth or post failures, or no rows in Clover | API URL points at the webapp (app.cloversec.io), which redirects | CLOVER_SECURITY_KURA_PLUGIN_SERVER_URL=https://api.cloversec.io |