Core Concepts

How ArmorGemini Works

Architecture, intent enforcement, drift detection, and the Gemini CLI hook system

How ArmorGemini Works

ArmorGemini is a Gemini CLI hook plugin that intercepts every tool call via the CLI's lifecycle hooks. It enforces a simple rule: Gemini must declare what it intends to do before doing it.

Architecture

User Prompt
     |
     v
SessionStart Hook (first time in a session)
  Inject directive: "register your plan first"
     |
     v
Gemini emits a register_intent_plan step
  Plan captured (local for the session, signed by backend if API key set)
     |
     v
Gemini calls a tool (read_file, write_file, run_shell, fetch, ...)
     |
     v
BeforeTool Hook (ENFORCEMENT)
  1. Is this tool in the registered plan?
  2. Do the parameters match?
  3. POST /iap/verify-step: backend verifies intent + policy + CSRG
     |
  decision: "allow" or decision: "deny"
     |
     v
AfterTool Hook
  POST /iap/audit: send result to ArmorIQ (if API key set)
     |
     v
SessionEnd Hook
  Flush pending audit records, prune old state

Hook Events

ArmorGemini wires 4 Gemini CLI lifecycle hooks:

HookWhenWhat ArmorGemini Does
SessionStartSession opensInitialize, resolve API key, show enforcement status, inject plan directive
BeforeToolBefore any tool runsEnforce: verify with backend, check plan, deny drift
AfterToolAfter tool succeeds or failsSend audit log to backend
SessionEndSession closesFlush pending audits, prune state

Each hook is a type: "command" entry in ~/.gemini/settings.json that shells out to scripts/hook-router.mjs in the ArmorGemini checkout. The router reads the Gemini CLI's stdin JSON payload, dispatches to the engine, and writes the decision JSON back to stdout. That is the entire integration surface, no long-running daemon, no proxy.

Intent Drift Detection

If Gemini tries a tool that was not in its declared plan, ArmorGemini blocks it:

X   ArmorGemini intent drift: tool not in plan (fetch)

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

The drift check has two layers:

  1. Local fast-path: a plain if (toolName not in plan) check in the hook, so obvious drift is denied in single-digit milliseconds without hitting the network.
  2. Backend verification: the same call is also sent to POST /iap/verify-step, which runs the full ArmorIQ policy engine (CSRG binding, Merkle-signed policy, cross-session token validity). The backend's decision is authoritative if the two disagree.

The Backend Does the Heavy Lifting

ArmorGemini itself is intentionally thin. It captures the payload, forwards it, and honors the decision. All of the interesting security work happens on the ArmorIQ backend:

ConcernEndpointWhere it runs
Intent verificationPOST /iap/verify-stepArmorIQ backend
Policy compilationPOST /policies/profiles/propose, PUT /policies/profiles/draft, POST /policies/profiles/templateArmorIQ backend
CSRG Merkle signingPOST /verify/actionArmorIQ backend
Audit logPOST /iap/auditArmorIQ backend
Active policy fetchGET /policies/currentArmorIQ backend

This is the same pattern as ArmorClaude and ArmorCodex, the plugin's job is to wire the hook, ship the request, and respect the answer.

Slash Commands

Four /armor:* slash commands ship as TOMLs under ~/.gemini/commands/armor/:

CommandPurpose
/armor:listShow the current ArmorIQ policy for this workspace
/armor:add <rule>Stage a policy rule change (verb + target + optional note)
/armor:template <name>Stage a named ArmorIQ policy template (lockdown, strict-read-only, balanced, ...)
/armor:helpShow the /armor command help

The TOMLs are the Gemini CLI's native custom-command format, they shell to a small CLI script (scripts/armor/cli.mjs) that talks to the ArmorIQ backend. Staged changes go through the policy proposal flow, they are not applied instantly, the user confirms from the ArmorIQ dashboard.

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
  • Backend POST /iap/verify-step returns decision: "deny"
  • Intent token expired
  • Backend unreachable and the local layer cannot verify

Every block is recorded in the ArmorIQ audit log with the matched rule, the registered plan, the tool call, and the deny reason.

No Separate LLM Call

ArmorGemini does not call a separate LLM to generate plans. Gemini itself generates the plan as part of its normal reasoning turn. Zero extra cost, zero extra latency for planning.

On this page