Skip to content

Intercept

The intercept feature lets you pause HTTP, WebSocket, and gRPC messages in transit, inspect them, and decide whether to release, modify, or drop them. This gives you real-time control over traffic flowing through the proxy.

How interception works

  1. Define intercept rules that match specific requests or responses
  2. When a matching message passes through the proxy, it is held in the intercept queue
  3. The AI agent (or WebUI user) reviews the queued item
  4. The item is released as-is, modified and forwarded, or dropped

Per-protocol rule engines

The Pipeline InterceptStep dispatches via a type-switch on env.Message to a per-protocol intercept engine:

Message type Engine
HTTPMessage internal/rules/http.InterceptEngine
WSMessage internal/rules/ws.InterceptEngine
GRPCStartMessage, GRPCDataMessage, GRPCEndMessage internal/rules/grpc.InterceptEngine
SSEMessage pass-through (half-duplex receive-only, no Send-side rules to apply)

Each engine evaluates protocol-specific match conditions (HTTP path/method, WS opcode, gRPC service/method) and produces the matched rule list.

Hold queue

A matched envelope is parked in the HoldQueue (internal/rules/common.HoldQueue) — a thread-safe map of held envelopes keyed by intercept ID. The queue is shared across all protocols. Each held entry waits for an external action (release, drop, or modify-and-forward) delivered via the intercept MCP tool or the Web UI.

The queue is bounded (default 100 items) and time-bounded (default 5 minutes). When either limit is hit, the configured timeout behavior runs (auto_release or auto_drop).

When you choose modify-and-forward, the user-modified envelope is re-evaluated by SafetyStep before being released downstream. This closes the gap where an operator-injected payload could bypass the input filter that gated the original held envelope (USK-702).

Configuring intercept rules

Intercept rules define which requests and responses are captured. You configure rules using the configure tool:

// configure
{
  "intercept_rules": {
    "add": [
      {
        "id": "api-requests",
        "enabled": true,
        "direction": "request",
        "conditions": {
          "host_pattern": "api\\.target\\.com",
          "path_pattern": "/api/.*",
          "methods": ["POST", "PUT", "DELETE"]
        }
      }
    ]
  }
}

Rule fields

Field Type Description
id string Unique rule identifier
enabled boolean Whether the rule is active
direction string "request", "response", or "both"
conditions object Matching criteria

Condition fields

Field Type Description
host_pattern string Regex matched against the hostname (port excluded)
path_pattern string Regex matched against the URL path
methods array HTTP method whitelist (case-insensitive)
header_match object Header name to regex mapping (AND logic)

All specified conditions must match for a rule to trigger (AND logic).

Direction

  • request -- intercepts the request before it reaches the upstream server
  • response -- intercepts the response before it reaches the client
  • both -- intercepts both the request and the response

Intercepted items include a phase field ("request" or "response") so you know which direction was captured.

Managing the queue

Viewing queued items

Use the query tool to list items waiting in the intercept queue:

// query
{
  "action": "intercept_queue"
}

Each queued item has an intercept_id that you use for subsequent actions.

Release

Forward the intercepted item as-is, without modifications:

// intercept
{
  "action": "release",
  "params": {
    "intercept_id": "int-abc-123"
  }
}

Modify and forward

Modify the intercepted item before forwarding. The available mutation parameters depend on the phase.

Request phase mutations:

// intercept
{
  "action": "modify_and_forward",
  "params": {
    "intercept_id": "int-abc-123",
    "override_method": "POST",
    "override_url": "https://api.target.com/v2/users",
    "override_headers": {"Authorization": "Bearer injected-token"},
    "add_headers": {"X-Custom": "value"},
    "remove_headers": ["X-Debug"],
    "override_body": "{\"role\":\"admin\"}"
  }
}

Response phase mutations:

// intercept
{
  "action": "modify_and_forward",
  "params": {
    "intercept_id": "int-resp-456",
    "override_status": 200,
    "override_response_headers": {"Content-Type": "application/json"},
    "add_response_headers": {"X-Modified": "true"},
    "remove_response_headers": ["X-Server-Info"],
    "override_response_body": "{\"success\": true}"
  }
}

Drop

Discard the intercepted item. For requests, this returns a 502 Bad Gateway response to the client:

// intercept
{
  "action": "drop",
  "params": {
    "intercept_id": "int-abc-123"
  }
}

Request phase parameters

Parameter Type Description
override_method string Override the HTTP method
override_url string Override the target URL
override_headers object Header overrides (key-value pairs)
add_headers object Headers to add
remove_headers array Header names to remove
override_body string Override the request body

Response phase parameters

Parameter Type Description
override_status integer Override the HTTP status code
override_response_headers object Response header overrides
add_response_headers object Response headers to add
remove_response_headers array Response header names to remove
override_response_body string Override the response body

Queue configuration

Configure the intercept queue behavior using the configure tool:

// configure
{
  "intercept_queue": {
    "timeout_ms": 300000,
    "timeout_behavior": "auto_release",
    "protocol_overrides": {
      "ws":  {"timeout_ms": 60000},
      "sse": {"timeout_ms": 60000}
    }
  }
}
Parameter Type Default Description
timeout_ms integer 300000 (5 min) Global timeout for held envelopes (minimum 1000 ms)
timeout_behavior string auto_release What happens on timeout: auto_release or auto_drop
protocol_overrides object Per-protocol timeout/behavior overrides keyed by canonical envelope.Protocol (http, ws, grpc, grpc-web, sse, raw, tls-handshake). null removes a key under merge; replace mode atomically swaps the map

When a blocked request times out:

  • auto_release -- the request is forwarded as-is (default)
  • auto_drop -- the request is dropped with a 502 response

Per-protocol hold-timeout defaults

Protocol Default hold-timeout
http 300 000 ms
ws, sse, grpc, grpc-web 60 000 ms (USK-855)
raw, tls-handshake inherit global (300 000 ms)

WebSocket and SSE were raised to 60s so multi-second human reviews on long-lived streams do not race against the upstream's idle timeout. While a WebSocket frame is held, the proxy injects synthetic keepalive pings to the upstream (USK-854) so the connection stays alive for the full hold window. The literal "http2" is not a valid override key -- both HTTP/1 and HTTP/2 envelope as "http".

Managing rules

Enable and disable rules

// configure
{
  "intercept_rules": {
    "disable": ["api-requests"],
    "enable": ["other-rule"]
  }
}

Remove rules

// configure
{
  "intercept_rules": {
    "remove": ["api-requests"]
  }
}

Replace all rules

// configure
{
  "operation": "replace",
  "intercept_rules": {
    "rules": [
      {
        "id": "new-rule",
        "enabled": true,
        "direction": "both",
        "conditions": {
          "path_pattern": "/api/.*"
        }
      }
    ]
  }
}

Raw bytes mode

By default, the intercept tool operates in structured mode -- it parses and serializes requests at the HTTP (L7) layer. This means modifications go through the standard HTTP library, which may normalize headers, adjust Content-Length, and apply transfer encoding.

Raw bytes mode bypasses this entirely. When you set "mode": "raw", the proxy sends bytes directly on the wire without any HTTP library processing. This gives you full control over every byte of the request or response.

Structured vs raw mode

Aspect Structured mode (default) Raw mode
Layer L7 (HTTP semantics) L4 (raw bytes on the wire)
Modifications override_method, override_headers, etc. raw_override_base64 (entire payload)
Normalization HTTP library may adjust headers, Content-Length, encoding None -- bytes are forwarded as-is
Auto-transform Applied (e.g., gzip decompression for display) Not applied
Use case Standard request/response editing Protocol-level anomaly testing

HTTP/1.x raw forwarding

In raw mode for HTTP/1.x, the proxy writes your raw bytes directly to a TCP (or TLS) connection to the upstream server, completely bypassing net/http.Transport. The raw response from upstream is read back and forwarded to the client as-is.

This enables testing scenarios where HTTP library normalization would otherwise mask the issue:

  • HTTP Request Smuggling -- craft requests with conflicting Content-Length and Transfer-Encoding headers (CL/TE, TE/CL)
  • Header injection -- send malformed headers that a standard HTTP library would reject or rewrite
  • Protocol downgrade testing -- send intentionally non-conformant HTTP to observe server behavior

HTTP/2 raw forwarding

For HTTP/2, raw mode operates on individual frames rather than the complete connection stream. When you provide raw bytes, the proxy:

  1. Parses the 9-byte frame headers to locate stream ID fields
  2. Rewrites stream IDs to match the upstream connection's allocated stream (connection-level frames with stream ID 0 are not rewritten)
  3. Forwards the frames as-is, preserving all other bytes including flags, padding, and payload

This means you can craft custom HEADERS, DATA, or other frame types while the proxy handles the stream multiplexing automatically.

Using raw mode

Release with raw bytes (forward original wire bytes without HTTP normalization):

// intercept
{
  "action": "release",
  "params": {
    "intercept_id": "int-abc-123",
    "mode": "raw"
  }
}

Modify and forward with raw bytes (replace the entire payload):

// intercept
{
  "action": "modify_and_forward",
  "params": {
    "intercept_id": "int-abc-123",
    "mode": "raw",
    "raw_override_base64": "R0VUIC8gSFRUUC8xLjENCkhvc3Q6IGV4YW1wbGUuY29tDQoNCg=="
  }
}

The raw_override_base64 value is the Base64 encoding of the complete raw bytes you want sent on the wire. For HTTP/1.x, this is the full request including the request line, headers, and body. For HTTP/2, this is one or more serialized frames.

Note

Raw mode requires raw bytes to be available for the intercepted item. The response includes raw_bytes_available: true when this is the case.

WebUI integration

The intercept queue is also accessible through the Web UI, where you can visually inspect and act on queued items. The WebUI provides a hex dump editor and text editor for raw bytes editing. See WebUI Intercept for details.

Practical use cases

Inspect login requests

Intercept login requests to test different credential combinations in real time:

// configure
{
  "intercept_rules": {
    "add": [
      {
        "id": "login-intercept",
        "enabled": true,
        "direction": "request",
        "conditions": {
          "path_pattern": "/login",
          "methods": ["POST"]
        }
      }
    ]
  }
}

Modify API responses

Intercept responses to test how the client handles different server behaviors:

// configure
{
  "intercept_rules": {
    "add": [
      {
        "id": "api-responses",
        "enabled": true,
        "direction": "response",
        "conditions": {
          "host_pattern": "api\\.target\\.com",
          "header_match": {"Content-Type": "application/json"}
        }
      }
    ]
  }
}

Intercept by header

Intercept only requests with specific headers:

// configure
{
  "intercept_rules": {
    "add": [
      {
        "id": "auth-requests",
        "enabled": true,
        "direction": "request",
        "conditions": {
          "header_match": {"Authorization": "Bearer.*"}
        }
      }
    ]
  }
}