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.

starts, stdio
if set up separately
checked messages
asks, encrypted
evaluates
records
EditorZed or JetBrains
ACP guarddefenseclaw-acp
Direct agent entrynot guarded
ACP agentKiro, Cursor, …
Gateway on loopbackchecks each message
Policyrules, scanners, judge
Audit and telemetrylocal SQLite, OTLP

List view for small screens. Use the expand button to open the drawing.

  1. EditorZed or JetBrains
    • starts, stdioACP guard
    • if set up separatelyDirect agent entry
  2. Direct agent entrynot guarded
  3. ACP guarddefenseclaw-acp
    • checked messagesACP agent
    • asks, encryptedGateway on loopback
    • starts, stdioEditor
  4. Gateway on loopbackchecks each message
    • evaluatesPolicy
    • recordsAudit and telemetry
  5. ACP agentKiro, Cursor, …
    • checked messagesACP guard
  6. Audit and telemetrylocal SQLite, OTLP
  7. Policyrules, scanners, judge
The editor starts defenseclaw-acp instead of the agent. The guard asks the local gateway for a verdict on each message before it forwards it. An agent entry that the editor launches directly does not pass through the guard.

How it works

  • defenseclaw acp setup replaces the editor's agent entry with a DefenseClaw · <agent> entry that starts defenseclaw-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: closed also refuses it on a per-user install. Add --activate for 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

  1. Start the gateway and add a guarded entry.

    defenseclaw-gateway start
    defenseclaw acp setup --client zed --agent kiro

    Setup writes one DefenseClaw · Kiro entry, 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.

  2. Check the binding.

    defenseclaw acp status

    A healthy binding shows the guard on and the profile and mode it uses. Setup starts in observe mode.

  3. Open a Kiro thread from the DefenseClaw · Kiro entry in Zed, then look at the events in the TUI Audit panel or with defenseclaw audit export.

  4. 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 acp

This 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 watch

Each 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 status
ACP 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 pair

The 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 kiro

This 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

SurfaceACP methodsProtection
Protocolinitialize, auth, session lifecycle, cancellation, extensionsStrict 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.
Promptsession/promptRules, scanners, optional judge, observe or action policy.
Outputsession/update and the prompt responseChecked per frame and again over the whole buffered turn. Action mode releases nothing before the terminal prompt response passes.
Approvalsession/request_permission, elicitation/createAllow or block. An unresolved confirmation fails closed in action mode.
Filesystemfs/read_text_file, fs/write_text_fileContent and method policy in both directions.
Terminalterminal/*Content and method policy in both directions.
Extensionsfor 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

SituationEditor 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 outputThe 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 agentThe 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 modeThe 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 checkedThe session goes on unchecked and the guard says once: "DefenseClaw is not checking this session".
Guard cannot startThe editor shows the reason (Zed: under Failed to Launch).
Agent silent for 60 seconds after a promptThe 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.

AttributeExample
defenseclaw.acp.clientzed
defenseclaw.acp.agentkiro
defenseclaw.acp.methodsession/prompt
defenseclaw.acp.directionclient_to_agent, agent_to_client
defenseclaw.acp.surfaceprompt, output, permission, filesystem, terminal, tool, protocol
defenseclaw.acp.profilekiro-only
defenseclaw.acp.protocol.versionschema-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]
KeyWhat it does
acp.enabledTurns the guard on.
acp.default_profileProfile 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>.modeobserve or action.
acp.profiles.<name>.fail_modeclosed or open.
acp.profiles.<name>.allowed_clients, allowed_agentsThe pairs the profile admits. The resolved profile must admit the pair.
acp.profiles.<name>.denied_methodsACP 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 setup

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

AgentEntry pointStatus
Kirokiro-cli acpTested with Zed
Cursoragent acpCataloged
OpenCodeopencode acpCataloged
Hermeshermes acpCataloged
GitHub Copilot CLIcopilot --acp --stdioCataloged
Devindevin acpCataloged

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 detect lists 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

CommandWhat it does
defenseclaw acp catalogPrints the compiled client and agent catalog as JSON.
defenseclaw acp setup --client C --agent AWrites 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 statusGuard state and each binding's health, profile and mode. --json adds where each profile came from.
defenseclaw acp verifyChecks 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 detectLists 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 DIGESTRe-pins a new guard in locks that pinned the old one. The installer runs it on upgrade.
defenseclaw acp remove --client C --agent ARemoves the entry, reclaims the contract lock and takes out the profiles setup wrote for that pair.

Gateway routes

RoutePurpose
POST /api/v1/acp/challengeAuthenticates the loopback gateway before ACP content is encrypted.
POST /api/v1/acp/evaluateLoopback-only, mutually authenticated frame evaluation.
GET /v1/acp/catalogCompiled client and agent catalog. Gateway token only.
GET /v1/acp/profilesEffective profile inventory. Gateway token only.

The ACP credential works on the two signed routes only; it gets 401 on the two read routes.

Next