Skip to content

feat: API collector — agent inspection in the proxy (phase 11b) - #18

Merged
carbon-evolution merged 8 commits into
mainfrom
feat/agent-firewall-11b-api
Jul 30, 2026
Merged

carbon-evolution merged 8 commits into
mainfrom
feat/agent-firewall-11b-api

Conversation

@carbon-evolution

Copy link
Copy Markdown
Owner

Phase 11b — the API collector

The reverse proxy already inspected request/response text. This embeds an AgentFirewall in it so it also inspects the tool_use / tool_result blocks in OpenAI and Anthropic traffic — giving tool-boundary protection (taint, egress, secrets, injection) to any framework speaking those APIs, not just Claude Code.

The stateless simplification

Every API request re-sends the whole conversation, so the collector works per request/response cycle with no state: the request's tool_result blocks build the taint set; the model response's tool_use blocks are the actions checked against it. No session store, no cross-request bleed.

What it catches

Via the existing AgentFirewall + shipped policy: a tool_use acting on content tainted by an earlier tool_result (indirect injection), secrets/PII in tool arguments, egress to a non-allowlisted host, destructive/privilege actions.

Verdict → action

The blockable moment is the response (the proxy can't stop a tool the client already ran). Deny + enforce → refuse the response with an error body; otherwise audit and pass. Off by default, shadow-first (agent_inspection.enabled / .enforce, both default false).

Notable change

The OpenAI request model (ChatMessage.content) is relaxed to Option<String> so tool-call conversations (assistant messages with content: null + tool_calls) parse instead of 400-ing.

Tests

415 workspace tests (from 405), 0 failing; clippy -D warnings clean; fmt clean. New: OpenAI + Anthropic tool-block extraction, the single-request taint kill chain, inspect_cycle, the agent_inspection config, and 3 end-to-end handler tests (enforce → 502, shadow → 200, disabled → 200 unchanged).

v1 deferrals

Streaming (SSE) tool_use isn't structurally parsed — streamed responses skip agent inspection (the text layer's sliding scan still applies). Request-side tool_use (already executed) isn't inspected. Deny refuses the whole response rather than stripping the block.

Design: docs/superpowers/specs/2026-07-30-agent-firewall-11b-api-collector-design.md. Plan: docs/superpowers/plans/2026-07-30-agent-firewall-11b-api-collector.md.

🤖 Generated with Claude Code

carbon-evolution and others added 8 commits July 30, 2026 22:57
…proxy)

Embedded AgentFirewall in the reverse proxy parses tool_use/tool_result blocks
from OpenAI/Anthropic traffic and applies agent-layer verdicts. Stateless
per-cycle (each request re-sends history), blockable moment is the response's
tool_use, flag/shadow by default, off unless configured, fails open. v1 defers
streaming tool_use parsing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7 TDD tasks: relax the OpenAI request model for tool-call messages, agent_scan
extraction (OpenAI + Anthropic tool_result/tool_use), per-cycle AgentFirewall,
agent_inspection config, handler wiring on the response path, end-to-end test,
README + PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChatMessage.content is now Option<String> (None for assistant tool-call messages)
with a flattened rest map capturing tool_calls/tool_call_id, so tool-using
conversations parse instead of 400-ing. The text pipeline skips null-content
messages. Prereq for the agent collector to see tool blocks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OpenAI + Anthropic tool_result/tool_use extraction and inspect_cycle: feed
request tool outputs as taint, check the response's tool calls against it, return
the worst verdict. The single-request kill chain (tainted output -> exfil tool
call) is caught. Adds the llm-firewall-agent dependency to the proxy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AppState gains an AgentFirewall (behind a Mutex). Both the OpenAI and Anthropic
non-streaming response paths, when agent_inspection.enabled, extract the
response's tool calls + the request's tool results, run inspect_cycle, and (only when enforce is on and the verdict is Deny) refuse the response with an
error body. Otherwise the verdict is audited and the response passes (shadow-first). Derives AgentInspection::default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drives the router with a mock upstream returning a tool call that exfiltrates an
AWS secret (a deterministic deny-secret-egress Deny). enforce -> 502; shadow ->
200 (audited only); disabled -> 200 unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds an 'Agent inspection in the API proxy' subsection (agent_inspection config,
the stateless per-cycle model, the response-tool_use blockable moment, shadow-
first, streaming gap), updates the test badge (405->415) and proxy crate count
(24->34), and records phase 11b in the history and roadmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@carbon-evolution
carbon-evolution merged commit 543ce35 into main Jul 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant