# Detect a compromised agent

Inputs are only half the picture. Even when a request looked clean, the model's **output** can reveal that an injection succeeded — a leaked system prompt, a jailbreak persona, dumped credentials, a safety-bypass confirmation. And for tool-using agents, the real danger is the **action** an injected instruction provokes: a high-risk tool call (send an email, delete a file) triggered by untrusted content. This guide covers both defenses: output inspection and the action gate.

## Prerequisites

- A firewall key (`sfw_live_xxxxxxxx`). [Sign in](/sign-in) and open **Keys** to mint one.
- For the action gate: the transparent proxy path with sessions (see [Transparent proxy](/docs/guides/transparent-proxy)), and `engines.agent_security.enabled: true` server-side (off by default).

```bash
export SFW_KEY=sfw_live_xxxxxxxx...your-full-key
```

## Output inspection

The firewall screens model output for signs an injection succeeded. Where it runs depends on your integration:

- **Transparent proxy** — every response is screened automatically.
- **Claude Code hooks** — install adds a `Stop` / `SubagentStop` hook that runs the agent's final message through the same check.
- **Direct** — call `/v1/inspect/response` yourself, from an agent you control or a parallel watcher.

### Inspect a response directly

```bash
curl -s -X POST https://your-firewall.example.com/v1/inspect/response \
  -H "Authorization: Bearer $SFW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "response_text": "<the model reply to check>",
    "system_goal": "You are a customer support agent."
  }'
```

This returns the same [verdict shape](/docs/guides/direct-api#the-verdict-shape) as `/v1/inspect`, with `issues` describing any output anomaly that fired.

### Output signals

The response check looks for the fingerprints of a successful attack: leaked system prompts or credentials, jailbreak personas, and safety-bypass confirmations. Detections show in the dashboard's **Activity** tab under the **Output** direction filter.

### Enforcement posture

Output checks are **advisory by default** — a false block cuts off a real answer. Raise `response_action_policy` in the key's **Configuration** tab to enforce blocking where the deployment topology allows it (for example, where you can safely retry or fall back).

## Agent Security — the action gate

For tool-using agents, untrusted tool **output** (a web page, an email) can carry an injected instruction. The action gate catches a high-risk tool **call** taken after untrusted data entered the session — the indirect-injection exfiltration path (search → send_email), decided by provenance. It reasons about two axes per tool:

- **output-trust** — can this tool's result be attacker-controlled? (`web_search`, `read_email` = untrusted)
- **action-risk** — does calling it *do* something consequential? (`send_email`, `delete_file` = high)

It blocks only the dangerous composition: a HIGH-risk action after an UNTRUSTED observation tainted the session. It needs the same `X-SFW-Session-Id` as multi-turn tracking.

### 1. Declare your tools under your key (once, at startup)

```bash
curl -s -X POST https://your-firewall.example.com/v1/tools \
  -H "Authorization: Bearer $SFW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tools": {
      "web_search": { "output_trust": "untrusted", "action_risk": "low" },
      "read_email": { "output_trust": "untrusted", "action_risk": "low" },
      "send_email": { "output_trust": "trusted",   "action_risk": "high" }
    },
    "default": { "output_trust": "untrusted", "action_risk": "low" }
  }'
# GET /v1/tools to read them back · DELETE /v1/tools to clear
```

Registration is **under your API key** (the envelope), not sent inline with each request (the content) — so injected content cannot downgrade `send_email` to low-risk to slip the gate. An undeclared tool defaults to untrusted-output (so it taints) and low-risk (so it is never blocked); declare a tool **high-risk** only when you want it gated.

### 2. Make it actually engage — the checklist

1. **One-time setup —** enable it server-side (`engines.agent_security.enabled: true`, off by default). Then toggle it per key under **Configuration**.
2. **One-time setup —** register your tools under your key (above) **before any proxied agent turn**. Until a tool is registered high-risk, it uses the untrusted-output + low-risk default, so nothing is gated.
3. **Per request —** send `X-SFW-Session-Id` on the proxied turns that make tool calls; the gate correlates observations and actions by session.
4. **Per request —** deliver tool results as **structured** `tool` / `tool_result` messages, not flattened into a `user` message. This is how the firewall identifies an observation and taints the session.
5. **Per request —** keep the user's earlier turns in the proxied message history. The goal-alignment judge authorizes a high-risk action against the user turns present in the request.

### Two layers decide how hard the gate acts

Provenance decides *whether* a high-risk action is suspect. Two layers then decide *how hard* to act, so the classic false positive — "research a topic, then email me a summary" — is not blocked outright:

1. **Goal-alignment (primary).** For a high-risk action in a tainted session, an LLM judge asks: does this action serve what the user actually asked for? The requested flow (email the summary to the person the user named) is **allowed**; an exfil the user never asked for (send to an address that only appeared in a tool result) is **blocked**. Any judge error or timeout is treated as "can't tell," never as "aligned." The judge sees the tool call's arguments **in full, never truncated**; an action whose arguments exceed the verifiability bound (100k chars) is refused outright and blocked as unverifiable, so padding an action can never soften the outcome. Configure it under `agent_security.goal_alignment`; toggle it per tenant and per key (inherit / on / off) in the **Configuration** tab.
2. **Graded taint (supporting).** Each untrusted observation is content-scanned to set taint *severity*: a clean read taints **LOW**, a read carrying a likely injection taints **HIGH**. When the judge is off or ambiguous, HIGH taint **blocks** and LOW taint only **flags** (advisory). Opt in with `agent_security.graded_taint.enabled` (off by default — enable it together with the judge).

**Pick a posture deliberately.** The strongest is graded taint on + goal-alignment on: legit flows pass, exfil blocks. Graded taint on + judge off favors UX — a scanner-missed injection drops to LOW and only flags — so run the judge in production. Graded taint off (the default) keeps the conservative posture: every untrusted read taints HIGH, so any high-risk action in a tainted session is blocked.

### What detection looks like

- **A gated action** comes back as a provider-shaped `403` (with the `firewall` extension and `X-SFW-Trace-Id`) — handle it as a normal API error or fallback.
- **A flagged action** is not blocked: it returns normally with an advisory `X-SFW-*` header for your logs.

## Two ways the gate silently does nothing — check both

- **Flattened tool results.** If your platform concatenates tool output into a user message, the firewall sees plain user text, nothing taints, and every action is allowed. Tool output must arrive as a role-tagged `tool` / `tool_result` message (checklist step 4).
- **Streaming.** The gate runs on **non-streaming** responses only. Since `stream: true` is the SDK default, a streaming tool-call turn is not gated. For real coverage, either do not stream tool-call turns, or set `agent_security.reject_streaming: true` to fail closed (reject streaming requests while the tool is active).

## What to do on detection

- **On a blocked action or a flagged output**, treat the session as suspect: stop the current tool chain, surface an error or fallback to the user, and do not silently retry the same action.
- **Review the session** in the dashboard's **Activity** tab (filter by the **Output** direction for response anomalies) to see which observation tainted it and which action was gated.
- **Tighten policy** if false negatives slipped through: raise `response_action_policy` for output enforcement, and move to graded-taint-on + judge-on for the action gate.

## Troubleshooting

- **Nothing is ever gated** — check the two silent-failure modes above (flattened tool results, streaming), confirm `agent_security.enabled` is on, and confirm your high-risk tools are declared under the key.
- **A legitimate research-then-email flow is blocked** — enable the goal-alignment judge and keep the user's original request turns in the message history so the judge can authorize the action.
- **Output anomalies never block** — that is the advisory default; raise `response_action_policy` on the key's **Configuration** tab.
- **A `403` with an `X-SFW-Trace-Id`** — the action gate blocked an action; use the trace id to find the session in **Activity**.
