Concepts

How ArmorClaude Works

Architecture, intent enforcement, drift detection, trust operations, and the hook system

How ArmorClaude Works

ArmorClaude is a Claude Code plugin that intercepts every tool call via lifecycle hooks. It enforces a simple rule: Claude must declare what it intends to do before doing it.

Architecture

User Prompt
     |
     v
UserPromptSubmit Hook
  /armor commands → instant policy response
  Other prompts → inject directive: "register your plan first"
     |
     v
Claude calls register_intent_plan MCP tool
  Plan validated + stored (local or signed JWT)
     |
     v
Claude calls a tool (Read, Write, Bash, etc.)
     |
     v
PreToolUse Hook (ENFORCEMENT)
  1. Is this tool in the registered plan?
  2. Do the parameters match?
  3. Do policy rules allow it?
  4. Is the intent token still valid?
  5. Is the MCP server approved?
     |
  ALLOW or DENY
     |
     v
PostToolUse Hook
  Audit log sent to ArmorIQ (if API key set)

Hook Events

ArmorClaude registers 7 lifecycle hooks with Claude Code:

HookWhenWhat ArmorClaude Does
SessionStartSession opensInitialize, show enforcement status
UserPromptSubmitUser sends a promptProcess /armor commands, inject plan directive
PreToolUseBefore any tool runsEnforce: check plan, policy, token, MCP server
PostToolUseAfter tool succeedsSend audit log to backend
PostToolUseFailureAfter tool failsSend failure audit log
StopEnd of a turnCheck token expiry
SessionEndSession closesClean up state

Daemon Architecture

ArmorClaude uses a daemon process for performance. The hook-router tries the daemon socket first (target: under 5ms p50), falling back to in-process handling if the daemon is unavailable:

Hook event → hook-router.mjs
  -> Try daemon socket (fast path, ~5ms)
  -> Fall back to in-process (30-350ms)

The daemon is a long-lived Node.js process that keeps SDK clients warm, batches audit logs, and avoids cold-start overhead per hook event.

Intent Drift Detection

If Claude tries a tool that was not in its declared plan, ArmorClaude blocks it:

ArmorClaude intent drift: tool not in plan (Bash)

This prevents prompt injection from silently steering Claude into unauthorized tool use. Claude sees the denial and either re-registers a new plan that includes the tool, or tells the user it cannot perform that action.

Parameter Enforcement

Plan steps can constrain expected parameters. If Claude's actual tool call parameters do not match the plan's declared inputs, the call is blocked:

ArmorClaude intent mismatch: parameters not allowed for Write

MCP Server Trust

By default, ArmorClaude denies tools from unknown MCP servers (mcpDenyByDefault). When an unknown server's tool is called:

  1. ArmorClaude blocks the call and surfaces it through Claude Code's native tool approval UI
  2. The user can approve the one-time call
  3. For persistent trust, use /armor mcp approve <server>

Approved and denied servers persist across sessions.

Token Lifecycle

With an API key, ArmorClaude gets a signed JWT from the ArmorIQ backend:

  1. Claude calls register_intent_plan with its tool list
  2. ArmorClaude sends the plan to the backend's token endpoint
  3. Backend returns a signed JWT (default TTL: 1 hour)
  4. Each tool call is validated against the plan embedded in the token
  5. When the token nears expiry, ArmorClaude auto-refreshes at turn boundaries

Without an API key, the plan is stored locally with no expiry.

Trust Operations

Trust operations modify the active intent token without starting from scratch. These are available as MCP tools:

Revoke

Immediately invalidates the active token. Within ~55ms, every enforcement point in the fleet refuses it. Use when the plan has gone wrong.

Re-anchor

Updates the plan without revoking the token. Signs a delta linking the old plan hash to the new one, preserving the tamper-evident lineage in the audit log.

Delegate

Issues a subtree-bounded child token with a Merkle inclusion proof. A sub-agent receives authority confined to a specific part of the plan (e.g., /steps/[1]).

Crypto Policy Binding (CSRG)

When an API key is configured, ArmorClaude cryptographically binds policies using Merkle trees:

  1. Policy statements are hashed into a Merkle tree
  2. The policy hash is included in the signed intent token
  3. At enforcement time, the hash is verified to ensure the policy hasn't been tampered with
  4. After any policy change, the crypto binding is automatically reissued

This ensures that the policy evaluated at token-minting time matches what's enforced at tool-call time.

Enforcement Engines

ArmorClaude supports two enforcement engines:

EngineDescriptionWhen to use
local (default)Policy evaluated in-process by the pluginSingle user, low latency
opaPolicy delegated to an OPA PDP serverFleet-wide enforcement, central policy management

Switch engines with:

/armor settings enforcement opa

OPA mode requires ARMORCLAUDE_OPA_PDP_URL to be set and pushes compiled policy bundles to the backend on every policy change.

Fail-Closed

In enforce mode (the default), any of these causes a tool call to be blocked:

  • No intent plan registered (and intent is required)
  • Tool not in plan (intent drift)
  • Parameters do not match plan constraints
  • Policy rule denies the tool
  • Intent token expired
  • MCP server not approved
  • Internal hook error

When an API key is configured, every block is recorded in the ArmorIQ audit log with the matched rule, the registered plan, the tool call, and the deny reason - so the chain of evidence is complete even when enforcement is silent to the user. In local-only mode (no API key), blocks still surface in Claude's response but are not shipped to a backend.

Whitelisted Tools

These tools are never blocked (they are needed for ArmorClaude to function):

  • register_intent_plan - Claude needs this to declare plans
  • policy_read - Policy reading (read-only)
  • trust_revoke / trust_reanchor / trust_delegate - Trust operations
  • ExitPlanMode - Plan mode approval
  • ToolSearch / TodoWrite / ListMcpResourcesTool - Claude Code internals with no side effects

State Management

The daemon maintains in-memory state, with files as the durable backing store:

FileWhatLifecycle
runtime.jsonSessions, tokens, plans, discovered tools, trust opsPer-session, pruned after 24h
policy.jsonPolicy rules + version historyPersistent
pending-plan.*.jsonPlan awaiting consumption by PreToolUseConsumed + deleted on next tool call
policy-pending.jsonStaged policy proposal (awaiting /armor yes)Expires after 30 minutes
policy-drafts.jsonDraft policies in progressUntil staged or discarded

Files live in ~/.claude/plugins/data/armorclaude-armoriq/.

No Separate LLM Call

ArmorClaude does not call a separate LLM to generate plans. Claude itself generates the plan as part of its normal reasoning turn, using the session's own credentials. Zero extra cost, zero extra latency.

On this page