Connectors

Experimental Tool-Call State Contract

How DefenseClaw routes structured tool calls, proves successful execution, and correlates dangerous actions across turns without broadening false-positive behavior.

Experimental contract

This is the rollout contract for experimental stateful tool-call protection. It does not make every connector stateful, change existing policy defaults, or turn an observe-only connector into an enforcement surface. A connector stays at the most conservative level in the matrix until its hook identity, outcome, and native-host behavior pass automated and live tests.

DefenseClaw uses this path to stop supported unsafe tool calls at a connector's last synchronous, block-capable hook before execution and to remember only actions that later have trustworthy evidence of success. The initial rollout targets higher true-positive coverage for destructive actions and ordered chains while paired controls and shadow/detection-only modes guard against false-positive regressions.

No separate public endpoint

The feature reuses each connector's existing authenticated hook transport. The server resolves the connector and its versioned hook contract, then classifies the event from the connector's documented lifecycle name. A remote payload cannot opt itself into the trusted path by setting a structured or tool_call flag.

Resolved eventRoutePurpose
Recognized pre-tool proposalStructured actionNormalize tool name, arguments, command operations, paths, endpoints, and data flow into private ActionFacts; evaluate semantic CEL owners and block only when the connector declares a synchronous enforcement surface.
Recognized post-tool resultResult content / outcomeInspect returned content and, only where the connector contract proves identity and success, resolve a pending action. It cannot undo the completed tool.
Connector-proven typed lifecycle changeState transition (reserved)No current connector qualifies. This route stays unavailable until an official contract exposes an exact, authenticated security-state change; it is never inferred from event text.
Prompt, model, stop, session, and subagent lifecycleExisting prompt/result or audit routePreserve the current connector behavior; these events are neither tool proposals nor typed state transitions.
Unsupported, partial, ambiguous, or malformed action schemaOwner-local regex fallbackPreserve compatible detection without granting the payload semantic authority it did not prove.

This is exclusive at the semantic-rule-owner boundary: a CEL-owned rule and its legacy regex do not both produce decisions for the same action. CEL owns a rule when the connector supplied a trusted tool boundary and ActionFacts produced an authoritative projection. The regex fallback owns that rule when the parser or schema cannot do so. Unrelated legacy rules can still run from the same immutable rule-pack generation.

The state-transition route is a closed, versioned extension point, and its list is currently empty for every connector. It can be populated only when an official connector contract exposes typed fields that prove the exact security state change. Claude Code ConfigChange exposes source, file path, and common hook context, which is not proof of a guardrail-state transition, so it remains outside this route. Similarly named events, free-form content, and payload-provided flags cannot opt themselves in.

Static tool/MCP blocks, explicit allows, CodeGuard, managed-enterprise policy, and the existing OPA policy role remain separate controls. CEL is not a second standalone policy engine and is not run on ordinary prompts or tool-result text.

Covered tool surfaces

The structured route understands these closed families; an absent family is not inferred from a similarly named tool:

  • shell execution, including POSIX shell, PowerShell, and cmd.exe where the parser can establish a conservative dialect;
  • direct file read, write, edit, append, delete, move, and copy arguments;
  • generic built-in tools with a reviewed argument shape;
  • MCP tools when their server/tool identity and arguments are present; and
  • skill invocation only when the connector exposes it as a real tool event.

Opaque SaaS, mail, calendar, or custom MCP arguments still receive server/tool policy and compatible fallback scanning. They do not gain guessed file, network, or data-flow semantics until a narrow schema adapter is reviewed. Likewise, loading a SKILL.md, expanding a slash command, and a skill's later shell call are different events; only the actual tool call enters this path.

Pending to successful state

A pre-tool hook describes intent, not execution. If DefenseClaw allows a proposal on a connector with authoritative pairing, it stores a bounded, content-free pending marker or predecessor projection keyed by the connector instance, authenticated session, and strongest invocation identity.

pre-tool proposal -> standalone/chain decision -> pending
pending + matching successful post-tool result -> durable successful step
pending + failure/cancel/unknown/expiry -> discard, never advance state
pending + matching typed denial -> discard the action; record only denial state

Pending rows do not satisfy an ordered chain. An exact replay returns the same decision without duplicating state or telemetry. A ruleset change, expired window, mismatched session, conflicting ID, or ambiguous join cannot promote the proposal. Successful steps and deduplication receipts live in DefenseClaw's SQLite store so reviewed chains can span turns and gateway restarts while still respecting wall-clock and event-count windows.

The fixed catalog contains six ordered chain definitions: guardrails-off then egress, permission denial then bypass, privilege discovery then elevation, secret read then egress, secret-manager read then egress, and workload identity then lateral execution. Five currently have production projection paths. Of those, permission-denial then bypass and privilege-discovery then elevation are block-capable when both steps carry exact enforcement evidence. Secret read, secret-manager read, and workload identity followed by their respective second steps are detection-only because temporal proximity does not prove data flow. Guardrails-off then egress is reserved and dormant: no current connector exposes the exact authenticated state transition needed for its first step, and DefenseClaw does not infer it from configuration text. Coincidental steps may produce detection telemetry, but only enforcement-safe facts can contribute to a synchronous block. A later risky proposal may be blocked using already committed predecessors; the blocked proposal is never recorded as successful.

Connector outcome and identity matrix

State levels have strict meanings:

The stateful route is enabled only for a known compatible agent version or an explicitly reviewed unversioned default contract. A detected version mismatch, unknown pin, or missing lifecycle contract disables state mutation and artifact enforcement even if older hook fields still happen to parse. Compatibility loss also clears pending state in the exact authenticated session, and the persisted fingerprint binds the resolved contract so an older proposal cannot cross a contract or agent-version boundary.

  • paired-outcomes — a pre proposal can become pending and only an exact, authoritative matching result can commit it;
  • detection-only — pre and post events can be inspected, but post events do not authorize a durable cross-event transition; and
  • direct-only — only the current pre-tool proposal is enforceable; there is no trustworthy post outcome for state.
Connector / primary contractStructured proposal eventsResult / outcome evidenceInvocation identityState level
CodexPreToolUse; PermissionRequest is a direct decision only and never creates pending statePostToolUse; exact apply_patch scalar success and MCP CallToolResult success/failure are authoritative, while shell and generic responses are unknownpaired tool_use_id on PreToolUse/PostToolUse; none on PermissionRequestpaired-outcomes only for the exact apply_patch and MCP response shapes
Claude CodePreToolUse; PermissionRequest is direct-onlyPostToolUse success; PostToolUseFailure failure; PermissionDenied denialpaired tool_use_id on tool-use events; none on PermissionRequestpaired-outcomes
Hermespre_tool_callpost_tool_call; only extra.status=ok succeeds, while error and blocked discardpaired extra.tool_call_idpaired-outcomes
Cursorgeneric preToolUse; specialized shell, MCP, and read hooks remain directgeneric postToolUse success and postToolUseFailure failure; specialized after-hooks are result contentpaired tool_use_id only on the generic tool lifecyclepaired-outcomes for generic events
Windsurf / Cascadepre_read_code, pre_write_code, pre_run_command, pre_mcp_tool_usesuccessful read/write/MCP post surfaces; post_run_command has no authoritative exit status or outputtrajectory_id and execution_id identify conversation/turn, not a tool calldetection-only
Gemini CLIBeforeToolAfterTool; inspect tool_response.error because the event covers both outcomessession ID only; no stable hook tool-call IDdetection-only
GitHub Copilot CLIpreToolUse, permissionRequestpostToolUse has resultType=success; postToolUseFailure is failuresession ID only; no per-tool invocation IDdetection-only
OpenHandspre_tool_usepost_tool_use; only tool_response.is_error=false succeedsthe shell-hook payload omits the SDK's internal action IDdetection-only
AntigravityPreToolUsePostToolUse; success requires an absent/empty errorpaired (conversationId, stepIdx) sequence, not an opaque call IDdetection-only
OpenCodetool.execute.beforev7 awaits tool.execute.after with the executed args and validated result; bash additionally requires numeric metadata.exit=0paired (sessionID, callID)paired-outcomes
Amptool.callterminal tool.result; status=done succeeds, error fails, and cancelled discardspaired toolUseID on both eventspaired-outcomes
OmniGenttool_call mapped to PreToolUsetool_result repeats request data but has no authoritative success/failure fieldno stable call or session ID in PolicyEventdirect-only

Turn-completion events such as Codex, Claude Code, or Cursor Stop, OpenCode session.idle, and Amp agent.end discard only unfinished proposals. They deliberately preserve successful predecessor history so a reviewed chain can span user turns; a late result cannot promote work left pending when the turn ended. True session-end, deletion, or reset events atomically discard pending intent and close the exact session's committed chain window. Bounded, content-free cutoffs make both operations replay-safe: a turn cutoff suppresses only stale pending proposals, while a session cutoff also suppresses stale committed events. Neither kind of boundary implies that an unfinished tool succeeded. Native OTel IDs may enrich correlation, but they do not upgrade a hook contract unless the upstream source proves the same identity on both rails.

The exact cutoff catalogs are capped. If a catalog saturates, a fixed durable received-time barrier summarizes the evicted cutoff identity while the current boundary still clears only its own session. Existing unrelated pending and committed predecessors remain intact; a delayed event received at or before the barrier can be conservatively suppressed instead of re-arming stale state. This can reduce experimental chain detection under extreme session churn, but it cannot create a new chain block, and direct single-action rules are unaffected.

Subagent-stop events do not clear session-wide pending state: current ledgers do not carry an exact child-scope digest on every connector, so treating a child stop as a session terminal could erase concurrent parent or sibling calls.

Tool-surface matrix

Skills means a connector exposes a skill as a tool/lifecycle event. It does not mean DefenseClaw watches every skill file read. No reviewed connector has a dedicated mail lifecycle: email, calendar, SaaS, and similar actions enter only through the connector's generic or MCP tool event, with the exact reported tool identity and arguments. DefenseClaw does not guess that a custom tool is mail. This is a cross-platform contract catalog, not a promise that every connector is available on Windows. DefenseClaw enables and tests only the connectors in the native Windows connector matrix and does not create hook integrations for clients that are not installed or supported there.

ConnectorGenericShellFile readFile write/editMCPSkillsMail / SaaS
Codex>=0.135.0 local functionsyesno reviewed coverageyesyesno dedicated lifecyclegeneric (>=0.135.0) or MCP only
Claude Codeyesyesyesyesyesyes, with slash-expansion gapsgeneric or MCP only
Hermesyesyesyesyesyesyesgeneric or MCP only
Cursoryesyesyesyesyesno dedicated lifecyclegeneric or MCP only
Windsurf / Cascadeno generic eventyesyesyesyesno dedicated lifecycleMCP only
Gemini CLIyesyesyesyesyesyesgeneric or MCP only
GitHub Copilot CLIyesyesyesyesyes when emitted as a toolno dedicated lifecyclegeneric or MCP only
OpenHandsyesyesyesyesyesyes (InvokeSkill)generic or MCP only
Antigravityyesyesyesyesundocumentedno dedicated lifecyclegeneric only; MCP undocumented
OpenCodeyesyesyesyesyesyesgeneric or MCP only
Ampyesyesyesyesyesno dedicated lifecyclegeneric or MCP only
OmniGentyesyesyesyesyesyesgeneric or MCP only

Files written now and executed later

DefenseClaw intentionally does not watch or rescan every file mutation on the host. Direct file-write/edit hooks inspect the proposed path and content they actually receive, while shell-mediated writes are represented by the shell facts available at that boundary. Write-time fragments are not assumed to be executable or malicious.

The reviewed apply_patch, applypatch, and apply-patch tool family has a narrow structured adapter for the standard *** Begin Patch / *** End Patch envelope in command, patch, patchText, or patch_text. Exact add and update directives project writes; delete directives project deletes; and an update followed by *** Move to projects a source delete plus destination write. Only relative paths qualify, and they resolve from the trusted invocation working directory. Unknown, conflicting, oversized, or malformed envelopes stay non-authoritative and use the compatible rule fallback; DefenseClaw does not retain patch contents in semantic facts or durable chain state. Codex's authenticated hook shape is documented in the upstream structured apply_patch hook contract.

When a later blockable shell proposal executes, sources, or loads a local script, the experimental artifact-promotion gate can reopen that exact path at the execution boundary and analyze its current bytes. The initial gate is deliberately narrow: an absolute regular file, no final-component symlink, a stable file identity, size, and modification time throughout the bounded read, a recognized shell/shebang dialect, and a maximum of 64 KiB. This catches “write harmless-looking fragments across turns, then run the assembled script” without creating a global filesystem watcher or treating every generated source file as an imminent command.

The proven inner script facts feed the same chain projection as the outer tool call. A chain-relevant action inside an allowed script remains pending until that exact script invocation succeeds, while a chain-ending action inside the script can use only already committed predecessors. Outer control-flow or artifact uncertainty clears the enforcement projection but retains detection.

The gate skips work when the standalone decision already blocks. Reads use a small global concurrency bound and sub-second hook budget so a stalled mount cannot create unbounded gateway work; Windows UNC/device paths are excluded. Direct execution also requires a recognized shebang, a matching explicit interpreter, or an ambient PowerShell/cmd.exe grammar that proves its own script suffix. A filename suffix without that execution context proves nothing. Relative paths inside the script are resolved from the trusted caller working directory inherited by the interpreter, not from the script's own directory.

This check is best-effort rather than an immutable-execution guarantee. The agent executes the pathname after the hook returns, so another process could replace or modify it after inspection. Closing that final race requires the agent to execute a DefenseClaw-owned immutable snapshot or file descriptor, which is outside this experimental contract.

Low-false-positive invariants

  • The authenticated connector route and pinned hook contract establish the event; untrusted payload fields cannot claim a stronger boundary.
  • The typed state-transition route is closed and currently unavailable for all connectors. If a future official source qualifies, the event still cannot produce ActionFacts, masquerade as a tool proposal, or create a pending tool call.
  • A semantic rule owner takes one route: authoritative ActionFacts plus CEL, or its compatible fallback, never duplicate owner findings.
  • Ambiguous, preview-only, unsupported, or malformed facts cannot be promoted into authoritative semantic execution. The sole exception is one static, top-level opaque script path whose final bytes replace exactly that one uncertainty at the execution boundary; any additional parse issue remains detection-only. A top-level POSIX exec before a later sibling command also stays detection-only because the later command is not unconditionally reachable.
  • Proposal, denial, failure, cancellation, timeout, unknown outcome, and success are distinct states. Only proven success advances action-derived history. A matching connector-authenticated denial may advance only the denial-specific chain; an unmatched reported ID remains detection-only.
  • Durable pending-to-success promotion requires an exact connector-owned opaque call ID. Reviewed sequences may aid detection correlation but cannot promote state; DefenseClaw does not fuzzy-join calls by tool name, arguments, or time.
  • Detection-only evidence may alert, but cannot become an enforcement-safe chain predecessor merely because two suspicious events occurred nearby.
  • State is partitioned by connector instance and authenticated session, bounded by time/event windows, deduplicated, and stores projections/digests rather than raw tool content.
  • Result hooks never claim to undo side effects. Blocking happens only at a connector-declared synchronous pre-action surface.
  • The experimental lane does not change existing connector modes, rule-pack defaults, fail modes, or native ask capabilities.

Current rollout holds

  • OpenCode: paired state requires the v7 bridge, which forwards input.args and the actual result, awaits delivery, and rejects missing delegated-task results. Bash success additionally requires numeric metadata.exit=0; nonzero exits fail and missing/null exits remain unknown. Exceptions can still skip the after-hook and therefore never imply success.
  • Antigravity: CLI 1.1.9 or newer is required because that release fixed spurious PostToolUse and matcher behavior. Its (conversationId, stepIdx) sequence is not accepted as an exact opaque invocation ID, so state remains detection-only. MCP coverage remains undocumented.
  • Codex: 0.124.0 through 0.134.x expose only the reviewed shell, file-write/edit, and MCP local-function surfaces; generic local-function payloads start at 0.135.0. Hosted tools, file reads, and write_stdin do not have complete hook coverage. Shell and generic PostToolUse payloads do not prove success, so they cannot advance ordered state. Only the exact apply_patch scalar response and MCP CallToolResult shape currently do.
  • Windsurf, Gemini CLI, Copilot CLI, and OpenHands: post-tool inspection is useful, but absent per-call identity keeps cross-event state detection-only.
  • OmniGent: no authoritative tool outcome exists in the policy event, so only direct pre-tool decisions are eligible for enforcement.

These holds are intentional. Coverage moves upward only after the connector's official contract, bridge fixtures, and smoke evidence on every applicable certified host all agree; missing evidence reduces capability instead of guessing success.