Intercepting proxy + MCP (offat-proxy)#
OFFAT maps an API from source. The intercepting proxy adds the other way in: map it from live traffic. Point a client at the proxy, exercise the API, and every exchange is captured and turned into the same Bundle the rest of the pipeline consumes — so test-gen, the engine, triage and the reporter all work on proxy-captured traffic unchanged.
It also ships an inbuilt MCP server, so an MCP-speaking agent (Claude Code, etc.) can run the whole loop end to end: set an authorized scope, capture, export a Bundle, run the source-aware pipeline and the Rust engine, and replay or fuzz individual requests.
Authorized use only. Passive capture records the traffic you route through the proxy. Active behaviour — replay/fuzz, running the engine, and TLS interception — is refused for hosts outside an explicit
--scope, and credentials are redacted from stored and exported data by default. This mirrors the engine's--yesgate: only test systems you own or are permitted to assess.
Where it sits in the pipeline#
(source) map ─────────────┐
├─> Bundle ─> test-gen ─> execute ─> triage ─> report
(live traffic) offat-proxy ───────┘
The proxy writes the asm.endpoints and results sections. Observed paths are
templated (/users/1 and /users/2 collapse to /users/{id}) and query/path
parameters are inferred, so test-gen has real endpoints and parameters to work
from.
Capture → Bundle#
make proxy # build -> proxy-rs/target/release/offat-proxy
offat-proxy run --scope api.your-authorized-target.test --persist
# point the client at http://127.0.0.1:8080, exercise the API, then:
offat-proxy export --out app.offat.json
# hand off to the existing pipeline
offat-platform test-gen app.offat.json
offat-engine --bundle app.offat.json --url https://api.your-authorized-target.test --yes
Drive it from an agent (MCP)#
offat-proxy mcp --scope api.your-authorized-target.test
runs the proxy and an MCP server on stdio in one process. Register it with an MCP client:
{
"mcpServers": {
"offat-proxy": {
"command": "offat-proxy",
"args": ["mcp", "--scope", "api.your-authorized-target.test"]
}
}
}
Tools: proxy_status, set_scope, list_flows, get_flow, export_bundle,
replay_flow (Repeater), fuzz_flow (Intruder), ca_cert, run_platform,
run_engine. The last two shell out to the existing tools with an argv array (no
shell), so model calls stay inside offat_triage — the proxy never talks to a
model API itself, and captured target content is always treated as untrusted data.
Live terminal UI#
offat-proxy tui --scope api.example.test
Runs the proxy with a built-in terminal UI (ratatui): a live, colour-coded flow
list (by status, scope and tags), a detail pane for the selected request/response,
and a header showing listen address, counts, scope, MITM/redaction state and rule
count. Press / to filter history with the same expression language the rules use
(e.g. status >= 500 && path contains "/api"); j/k move, Tab switches focus,
? shows help, q quits. It reads the flow store read-only and never alters
traffic.
Desktop GUI (cross-platform)#
offat-proxy gui --scope api.example.test
A native window (egui/eframe) that runs on Linux, macOS and Windows, with the same
live flow list, detail pane, scope/rule status and expression filter as the
terminal UI, plus mouse selection. It ships in the default single binary alongside
the CLI, terminal UI (tui) and MCP; the GUI's graphics libraries load only when
you open the window, so the same binary still runs headless. For a minimal build
without windowing/graphics dependencies, compile with --no-default-features; the
gui command then explains how to re-enable it.
Rules and filters (live block / allow / tag / match-replace)#
A small, safe expression interpreter ("bambdas") matches flows and acts on them live; the same expressions filter captured history. It is an interpreter over a fixed set of flow fields — no code execution, I/O or host access.
offat-proxy run --scope api.example.test \
--rule 'block respond 403 "no" when path contains "/admin" && header["x-role"] != "admin"' \
--rule 'tag "login" when path endswith "/login"' \
--rule 'replace-resp "secret" -> "<redacted>" when status == 200'
offat-proxy run --rules myrules.txt --scope api.example.test # one rule per line
offat-proxy run --scope api.example.test --block-out-of-scope # allowlist mode
Actions: block [respond <status> "body"], drop, allow, tag,
set-req-header, set-resp-header, replace-req, replace-resp, each with an
optional when <expr>. Expression fields include method, host, path, url,
scheme, query, body, status, content_type, in_scope, intercepted,
tags, resp_body, and header["..."] / resp_header["..."] / query["..."];
operators == != contains startswith endswith matches < > <= >=, combined with
&& || ! and parentheses. Over MCP: get_rules, set_rules, test_expr, and a
filter argument on list_flows.
Protocols#
| Protocol | Handling |
|---|---|
| HTTP/1.1 | captured (MITM-decrypted over TLS) |
| HTTP/2 | negotiated to the upstream; captured |
| HTTPS (TLS) | intercepted with --mitm, else tunneled opaquely |
WebSocket (ws / wss) |
handshake captured as a flow; frames relayed (over TLS when intercepted) |
| gRPC / HTTP/3 / other TCP+TLS | tunneled opaquely via CONNECT (metadata recorded) |
The offat-engine executor covers full HTTP/2, WebSocket and gRPC (and opt-in
HTTP/3) when running a test plan.
Launch a browser, zero setup#
offat-proxy run --mitm --scope api.your-authorized-target.test --open-browser
--open-browser finds a Chromium-family browser (or Playwright's bundled
Chromium) and opens it in a dedicated throwaway profile already pointed at the
proxy. With MITM on, it trusts the CA automatically via Chrome's
--ignore-certificate-errors-spki-list=<CA SPKI hash> — scoped to this CA only,
never a blanket "ignore all certificate errors", and never touching your real
browser's trust store. Pick the browser with --browser, the landing page with
--browser-url, and add --browser-headless / --browser-no-sandbox for
containers. Agents can do the same through the open_browser MCP tool.
TLS interception#
offat-proxy ca # prints the local CA (PEM) — install into a TEST profile only
offat-proxy run --mitm --scope api.your-authorized-target.test
In-scope CONNECT tunnels are terminated with a leaf certificate minted by a
locally generated CA, so HTTPS is captured as plaintext. Out-of-scope tunnels stay
opaque — no TLS is broken. The CA private key is generated locally, stored
owner-only under the state dir, and never leaves the machine; install the CA
certificate only into a dedicated throwaway trust store.
Security defaults#
| Default | Why |
|---|---|
binds 127.0.0.1 |
not an open relay; a public bind needs --allow-public + --proxy-auth |
| empty active scope | passive capture only until you authorize hosts with --scope |
| redaction on | Authorization / Cookie / Set-Cookie / token headers never hit disk or a Bundle |
| MITM off | no TLS is intercepted until --mitm and an installed CA |
| upstream TLS verified | unless --insecure-upstream (loud warning) |
| bounded memory | ring-buffered flows and capped bodies |
See proxy-rs/README.md for
the full flag reference.