ACP guard
Put DefenseClaw between an ACP editor client and coding agent to inspect prompts, streaming output, permissions, filesystem, terminal, and extension methods.
The ACP guard sits between an editor that speaks the Agent Client Protocol (ACP), such as Zed or JetBrains, and the coding agent it runs. The editor keeps its normal agent experience. Every message between the two passes through DefenseClaw policy first.
List view for small screens. Use the expand button to open the drawing.
- EditorZed or JetBrains
- starts, stdioACP guard
- if set up separatelyDirect agent entry
- Direct agent entrynot guarded
- ACP guarddefenseclaw-acp
- checked messagesACP agent
- asks, encryptedGateway on loopback
- starts, stdioEditor
- Gateway on loopbackchecks each message
- evaluatesPolicy
- recordsAudit and telemetry
- ACP agentKiro, Cursor, …
- checked messagesACP guard
- Audit and telemetrylocal SQLite, OTLP
- Policyrules, scanners, judge
How it works
defenseclaw acp setupreplaces the editor's agent entry with aDefenseClaw · <agent>entry that startsdefenseclaw-acp.- The guard checks prompts, agent output, permission requests, file and terminal calls, and vendor extensions.
- It talks only to the gateway on a loopback address. Each request is encrypted and each verdict is authenticated, so another process on the port cannot read content or fake a verdict.
- Setup starts in observe mode: it records policy findings without blocking on them. A standalone managed guard refuses an unevaluated frame by default; a previously authenticated profile verdict selecting
fail_mode: closedalso refuses it on a per-user install. Add--activatefor action mode. - In action mode a blocked prompt ends that turn, and the editor shows the reason as an agent message.
- Policy is set per editor and agent pair through profiles, so one editor can enforce on one agent and observe another.
Quick start: Kiro in Zed
This page puts Kiro behind the ACP guard in Zed, then finds agents the guard does not cover.
Before you start
- DefenseClaw is installed and the gateway runs.
- You use Zed or JetBrains, and the agent has run from a terminal once (for Kiro,
kiro-cli login). An editor starts it with no terminal, so a first-run question would hang the thread.
Guard Kiro in Zed
-
Start the gateway and add a guarded entry.
defenseclaw-gateway start defenseclaw acp setup --client zed --agent kiroSetup writes one
DefenseClaw · Kiroentry, a private token file and a contract lock; a failed write rolls all three back. Your other settings stay, but Zed's file is rewritten as plain JSON, so comments after the opening block and trailing commas are dropped. For JetBrains use--client jetbrains. -
Check the binding.
defenseclaw acp statusA healthy binding shows the guard on and the profile and mode it uses. Setup starts in observe mode.
-
Open a Kiro thread from the
DefenseClaw · Kiroentry in Zed, then look at the events in the TUI Audit panel or withdefenseclaw audit export. -
When the events look right, turn on blocking.
defenseclaw acp setup --client zed --agent kiro --activate
Finding what is already unguarded
An editor may launch an agent directly. Those launches skip the guard.
defenseclaw acp detect zed - ~/.config/zed/settings.json
guarded DefenseClaw · Kiro
! UNGUARDED cursor -> launched from the client's own registry; replace with a guarded cursor entry
foreign Something Else (left untouched)guarded entries start defenseclaw-acp. UNGUARDED is a native agent the guard can wrap (direct launch), or an agent the editor starts from its own registry, which cannot be wrapped. foreign entries are not agents DefenseClaw knows and are never changed.
Wrap the direct launches:
defenseclaw setup acpThis runs setup for each pair and points your entry at the guard, keeping its key and env without adding a duplicate picker entry. Later acp setup updates that same entry when its profile or mode changes, and acp verify checks every guarded entry for the pair. If the reserved DefenseClaw · <Agent> name is taken by an entry DefenseClaw has no record of writing (an unrelated command, or an entry from DefenseClaw 1.0.3 or earlier), setup leaves it and writes its own entry next to it as DefenseClaw · <Agent> (2), and removal leaves it too. acp verify fails until you delete that entry in the editor settings and run setup again. It starts in observe mode; add --activate for action mode. Remove a client-registry entry in the editor once the guarded entry exists, or the agent can still start without the guard. On Windows, an entry that starts the agent through its npm .cmd launcher (for example opencode.cmd) is adopted like any other direct entry.
Several guarded agents in one editor
Run setup once for each agent. Each run writes its own entry and contract lock and leaves the others alone.
defenseclaw acp setup --client zed --agent kiro --profile locked --activate
defenseclaw acp setup --client zed --agent devin --profile watchEach editor and agent pair has its own profile, so Kiro can enforce while Devin only observes. Without --profile, both use the default profile, and setup refuses to change that profile's mode for one pair only. Profile keys are in the reference.
Check it worked
defenseclaw acp statusACP guard: off (1 binding)
Bindings:
zed/kiro NEEDS ATTENTION profile default (observe, inherited)
! contract lock is missing or unsafe
Fix with: defenseclaw acp verify, then re-run defenseclaw acp setup for that pairThe sample shows a binding with a missing contract lock. After a good setup the binding is not flagged. defenseclaw doctor lists the same bindings and fails on a broken one.
Undo
defenseclaw acp remove --client zed --agent kiroThis removes the entry and the contract lock for that pair. It also takes out the acp.profiles entries that acp setup --profile wrote, once no pair uses them.
Managed computers
defenseclaw acp setup is for a per-user install. On an enterprise managed computer an administrator enrolls each user, editor and agent with defenseclaw-gateway enterprise acp enroll, and the user then runs the setup command it reports, defenseclaw-gateway enterprise acp setup. The administrator tools are enroll, list, verify and revoke; list shows each enrollment and whether its user has run setup. revoke leaves the user's editor entry and contract lock in place; Managed computers says how the user removes them. Secure Client deployments are not covered.
defenseclaw-gateway enterprise acp has enroll, verify and revoke, each taking --user, --client, --agent and --profile, and list. Each enrollment is a unique credential for one user, editor, agent and central profile. list shows every enrollment with its user, editor, agent, profile and whether the user has run setup (--json for the same rows). The administrator commands never write an editor profile. The enrolled user runs the command the enrollment reports, defenseclaw-gateway enterprise acp setup --client --agent --profile, which writes the editor entry and the contract lock as that user. revoke removes the credential and the user's copy of it (token_file), but not the editor entry or the contract lock. Those stay, and the editor's next start of that agent fails because the gateway no longer accepts the credential. defenseclaw acp remove is a per-user command and is refused on a managed computer, so the user removes the two by hand: the DefenseClaw · Kiro entry (named after the agent) from the editor's file (Zed: ~/.config/zed/settings.json, or $XDG_CONFIG_HOME/zed/settings.json, %APPDATA%\Zed\settings.json on Windows; JetBrains: ~/.jetbrains/acp.json), and ~/.defenseclaw/acp/<client>-<agent>.contract-lock.json, for example zed-kiro.contract-lock.json.
What is guarded
| Surface | ACP methods | Protection |
|---|---|---|
| Protocol | initialize, auth, session lifecycle, cancellation, extensions | Strict JSON-RPC 2.0 NDJSON; no batches; 1 MiB frame and 128 pending-ID limits. An error response with a null id is checked and forwarded. In action mode any other invalid frame ends the session and names the side that sent it. |
| Prompt | session/prompt | Rules, scanners, optional judge, observe or action policy. |
| Output | session/update and the prompt response | Checked per frame and again over the whole buffered turn. Action mode releases nothing before the terminal prompt response passes. |
| Approval | session/request_permission, elicitation/create | Allow or block. An unresolved confirmation fails closed in action mode. |
| Filesystem | fs/read_text_file, fs/write_text_file | Content and method policy in both directions. |
| Terminal | terminal/* | Content and method policy in both directions. |
| Extensions | for example cursor/* | Kept as ACP extensions and checked as tool traffic. |
Protocol version: ACP schema-v1.21.0.
When a step is blocked or cannot be checked
| Situation | Editor sees |
|---|---|
| Blocked prompt (action mode) | The turn ends with the reason as an agent message, for example "DefenseClaw policy blocked this action (rule SEC-AWS-KEY: AWS access key). Do not retry it in another form." The next prompt runs. |
| Blocked agent output | The turn ends and the guard cancels it in the agent. The message says the agent may already have run the step. |
| A check fails after the prompt reached the agent | The editor is told the agent may already have run tool actions and asks you to inspect the results before retrying. A prompt refused before it was forwarded keeps the "not delivered" wording. A refusal with a specific cause (ACP switched off, a rejected credential, the gateway's own refusal) keeps that cause. |
| Observe mode, the gateway refuses the frame in every mode | The guard refuses it too. This covers an account holder the gateway could not confirm. An ordinary policy finding in observe mode is still only recorded. |
| Gateway does not answer (one retry) | Only that step is refused: "DefenseClaw could not check this step because the gateway did not answer". The session stays open. |
| Gateway rejects the credential | "the gateway did not accept this editor's ACP credential: your administrator may have revoked your access" |
| ACP checking switched off centrally | "your administrator switched ACP checking off" |
| Observe mode, step cannot be checked | The session goes on unchecked and the guard says once: "DefenseClaw is not checking this session". |
| Guard cannot start | The editor shows the reason (Zed: under Failed to Launch). |
| Agent silent for 60 seconds after a prompt | The guard says the agent may be waiting for first-run questions in a terminal. |
A profile's fail_mode: closed makes an observe-mode guard refuse a step it could not check, once the gateway has answered that session. fail_mode: open lets such a step through. Hook fail modes are separate (gateway outage table).
If the central profile mode and the mode the entry was set up for differ, the guard stops the session and names both modes and the setup command to run. Zed keeps one guard for every thread of an agent, so after such an end the guard answers with the same message until you run the command.
Observability
Every guarded decision is a guardrail.evaluation.completed event with these attributes.
| Attribute | Example |
|---|---|
defenseclaw.acp.client | zed |
defenseclaw.acp.agent | kiro |
defenseclaw.acp.method | session/prompt |
defenseclaw.acp.direction | client_to_agent, agent_to_client |
defenseclaw.acp.surface | prompt, output, permission, filesystem, terminal, tool, protocol |
defenseclaw.acp.profile | kiro-only |
defenseclaw.acp.protocol.version | schema-v1.21.0 |
The event also carries defenseclaw.guardrail.raw_action, effective_action and would_block (so an observe-mode pass-through is not mistaken for an action-mode allow), rule_ids, finding_count, reason and defenseclaw.security.severity. A profile-denied method blocks with finding_count 0 and no rule_ids.
A decision with findings, and a profile-denied method, also export a trace: an invoke_agent <agent> span with an apply_guardrail child. Both carry the ACP sessionId as session and conversation ID.
Configuration
acp:
enabled: true
default_profile: locked
clients:
zed: {enabled: true}
agents:
kiro: {enabled: true}
devin: {enabled: true}
bindings:
zed/kiro: {enabled: true, profile: locked}
zed/devin: {enabled: true, profile: watch}
profiles:
locked:
mode: action
fail_mode: closed
allowed_clients: [zed]
allowed_agents: [kiro]
denied_methods: [fs/write_text_file]
watch:
mode: observe
allowed_clients: [zed]
allowed_agents: [devin]| Key | What it does |
|---|---|
acp.enabled | Turns the guard on. |
acp.default_profile | Profile used when nothing more specific applies. |
acp.clients.<id>, acp.agents.<id> | Enable or pin a profile for an editor or an agent. Disabling one disables every pair that uses it. |
acp.bindings.<client>/<agent> | Per-pair switch and profile. |
acp.profiles.<name>.mode | observe or action. |
acp.profiles.<name>.fail_mode | closed or open. |
acp.profiles.<name>.allowed_clients, allowed_agents | The pairs the profile admits. The resolved profile must admit the pair. |
acp.profiles.<name>.denied_methods | ACP methods always blocked, for example fs/write_text_file. |
A pair's profile is chosen most specific first: the binding, then the agent, then the editor, then default_profile. defenseclaw acp status --json shows profile_source for each binding.
The profile name is pinned into the contract lock at setup. If you edit a pair's profile by hand, acp verify and acp status report the drift instead of calling the binding healthy:
ACP binding verification failed: contract lock profile 'locked' no longer
matches the configured 'watch' for zed/kiro; re-run acp setupA request that names a profile the configuration does not assign to that pair is refused.
Each binding pins its own lock at <data-dir>/acp/<client>-<agent>.contract-lock.json. The lock records the guard and agent executable paths and SHA-256 digests, the client configuration path and digest, and the ACP schema version. Setup pins the entries that start the guard with that lock (DefenseClaw · <Agent>, an adopted entry under its own name, or a numbered one such as DefenseClaw · <Agent> (2)), not the whole editor file, so a theme change or another edit outside those entries, and the mode or model Zed stores in them, keep the guard starting. A change to an entry's command, arguments, env or type, or a new entry that starts the guard with the same lock, needs setup again. After defenseclaw rollback to 1.0.4 or earlier, run setup again for an adopted or numbered entry: those guards find the entry only by its reserved name. A lock written by 1.0.4 or earlier pins the whole file until you run setup for the pair. Agents share one scoped token, a private file named on the guard's command line. Setup uses only the catalog that ships with DefenseClaw and never downloads from the online ACP registry.
Setup and adopt also record a SHA-256 digest of each editor entry they write, in <data-dir>/acp/owned-entries.json (private to your account). An entry is DefenseClaw's only while it still matches that record. The mode or model Zed stores in the entry when you pick one in its agent panel does not count as a change. Setup, adopt and remove change only those entries. They leave any other entry for the pair exactly as it is: one you edited, one pointed back at the agent, or a DefenseClaw · <Agent> entry that DefenseClaw 1.0.3 or earlier wrote without a record. Setup writes its own entry next to it (DefenseClaw · <Agent> (2) when the reserved name is taken). Setup, remove and acp verify print the step to take: defenseclaw acp adopt to guard an entry that starts the agent directly, or delete the entry in the editor settings and run setup again. acp verify fails while such an entry is there, and for an entry whose --gateway is not this gateway. After an upgrade from 1.0.3 or earlier the old entry keeps starting, because the upgrade re-pins its contract lock, but acp verify asks you to replace it this way.
Supported agents
On a per-user install defenseclaw acp catalog prints the list. On a managed computer it is refused (exit 2); the administrator's defenseclaw-gateway enterprise acp commands are in Managed computers. Only agents with their own ACP mode are guarded. Other connectors are guarded through their hooks.
| Agent | Entry point | Status |
|---|---|---|
| Kiro | kiro-cli acp | Tested with Zed |
| Cursor | agent acp | Cataloged |
| OpenCode | opencode acp | Cataloged |
| Hermes | hermes acp | Cataloged |
| GitHub Copilot CLI | copilot --acp --stdio | Cataloged |
| Devin | devin acp | Cataloged |
Zed is tested. JetBrains is cataloged. A cataloged entry point matches the vendor's documentation; it does not mean DefenseClaw tested that editor and agent together.
What it does not do
- It does not stop an agent entry the editor launches without
defenseclaw-acp.defenseclaw acp detectlists those entries. - It can only withhold what the agent sends. An agent that runs its own tools may already have run a step when its output is blocked. Keep the agent's connector hooks in action mode next to the guard to stop a step before it runs.
- An organization-wide "only these agents" rule also needs IDE policy and operating-system application control.
API and diagnostics
Commands
| Command | What it does |
|---|---|
defenseclaw acp catalog | Prints the compiled client and agent catalog as JSON. |
defenseclaw acp setup --client C --agent A | Writes the guarded entry, token and contract lock. Flags: --profile, --activate, --guard-binary, --agent-binary, --json. --profile NAME uses that profile, or creates it when config.yaml does not define it; the last line then ends with new profile NAME. |
defenseclaw acp status | Guard state and each binding's health, profile and mode. --json adds where each profile came from. |
defenseclaw acp verify | Checks the contract lock against what is configured. It reports a binding healthy only when the guard would start it: the lock version and protocol pins match, and the agent executable is the pinned file, not a link to another. It also fails on a direct entry that starts the pinned agent file without the guard, whatever its name. |
defenseclaw acp detect | Lists guarded, unguarded and foreign agent entries in each editor. |
defenseclaw acp adopt (also defenseclaw setup acp) | Points existing unguarded entries at the guard. Flags: --client, --activate. |
defenseclaw acp refresh --from-sha256 DIGEST | Re-pins a new guard in locks that pinned the old one. The installer runs it on upgrade. |
defenseclaw acp remove --client C --agent A | Removes the entry, reclaims the contract lock and takes out the profiles setup wrote for that pair. |
Gateway routes
| Route | Purpose |
|---|---|
POST /api/v1/acp/challenge | Authenticates the loopback gateway before ACP content is encrypted. |
POST /api/v1/acp/evaluate | Loopback-only, mutually authenticated frame evaluation. |
GET /v1/acp/catalog | Compiled client and agent catalog. Gateway token only. |
GET /v1/acp/profiles | Effective profile inventory. Gateway token only. |
The ACP credential works on the two signed routes only; it gets 401 on the two read routes.
Next
- ACP guard reference
- Kiro connector, the separate hook path
- Change block and alert levels
- Connectors and Reference
Sandboxes in the TUI and macOS app
Watch and manage OpenShell sandboxes from the DefenseClaw terminal UI's Sandboxes panel or the macOS app's Sandboxes view, menu bar and notifications, and run sandbox setup from either.
Human-in-the-Loop (HITL)
How DefenseClaw turns confirmable findings into native approval prompts when a connector can pause, and what happens on connectors without an ask surface.