Transport Mode Comparison#
Pipelock supports multiple proxy modes, each with different scanning capabilities. Choosing the right mode determines what security checks apply to your agent's traffic.
Mode Summary#
| Mode | Endpoint | Protocol | Content Inspection | Response Scanning | Best For |
|---|---|---|---|---|---|
| Fetch | /fetch?url=... | HTTP | Full body | Injection detection | AI agents that need extracted text |
| CONNECT | HTTPS_PROXY | HTTPS tunnel | Hostname only | None | Standard HTTPS clients (no interception) |
| CONNECT + TLS interception | HTTPS_PROXY | HTTPS tunnel (MITM) | Full body + headers | Injection detection | Full DLP on HTTPS traffic |
| Absolute-URI | HTTP_PROXY | HTTP | Full URL | Injection detection (when enabled) | Plaintext HTTP clients |
| WebSocket | /ws?url=... | WS/WSS | Bidirectional frames | DLP + injection | Real-time agent communication |
| MCP stdio | pipelock mcp proxy -- CMD | stdio | Full messages | Full (6 layers) | Local MCP servers |
| MCP HTTP | pipelock mcp proxy --upstream URL | HTTP | Full messages | Full (6 layers) | Remote MCP servers (HTTP) |
| MCP WebSocket | pipelock mcp proxy --upstream ws://... | WS/WSS | Full messages | Full (6 layers) | Remote MCP servers (WS) |
Detailed Breakdown#
Fetch Proxy (/fetch?url=...)#
The highest-protection mode. Designed for AI agents that need web content.
Scanning:
- Ordered URL scan (length/parsing, scheme, CRLF injection, path traversal, destination policy, immutable SSRF/DLP floors, configured DLP, entropy, DNS SSRF/rebinding, rate limit, data budget, and context checks)
request_policyroute and operation checks, including followed redirect hops- Raw HTML scan for injection in hidden elements (script, style, comments, hidden divs)
- Readability text extraction (strips HTML, returns clean text)
- Response injection detection on extracted content
- Redirect chain: each hop traverses the ordered URL scanner pipeline
What the agent receives: Extracted text content, not raw HTML. Hidden injection is detected even though the agent never sees it.
Use when: Your agent fetches web pages and you want both URL scanning and content inspection.
curl "http://localhost:8888/fetch?url=https://example.com"
CONNECT Tunnel (via HTTPS_PROXY)#
Standard HTTP CONNECT proxy. Without TLS interception, pipelock cannot see the encrypted traffic after the tunnel is established.
Scanning (without TLS interception):
- Ordered URL scan on the target hostname (before tunnel)
- No content inspection during the tunnel (encrypted bytes)
- No response scanning
Scanning (with tls_interception.enabled: true):
- Ordered URL scan on the target hostname (before tunnel)
- Full request body DLP (JSON, form, multipart extraction)
- Request header DLP scanning
- Authority enforcement (Host must match CONNECT target)
request_policyroute and operation checks on the inner HTTP request- Response injection detection (buffered scan-then-send)
- Compressed response blocking (fail-closed)
What the agent receives: Without interception: raw HTTPS response from the origin server. With interception: response re-encrypted by pipelock after scanning.
Use when: Your agent or SDK uses HTTPS_PROXY natively. Enable TLS interception for full DLP and injection scanning. Without interception, only hostname-level protection applies.
# Without TLS interception (hostname scanning only)
HTTPS_PROXY=http://localhost:8888 curl https://example.com
# With TLS interception (full body/header DLP + response scanning)
# Requires: tls_interception.enabled: true in config
# Requires: pipelock CA trusted by the agent (pipelock tls install-ca)
HTTPS_PROXY=http://localhost:8888 curl --cacert ~/.pipelock/ca.pem https://example.com
Absolute-URI Forward Proxy (via HTTP_PROXY)#
Handles plaintext HTTP requests where the client sends the full URL as the request target.
Scanning:
- Ordered URL scan on the full URL
request_policyroute and operation checks- Response injection scanning (buffer-then-scan-then-send, fail-closed on compressed responses)
- Response body buffered (up to MaxResponseMB), scanned for injection, then forwarded; oversized buffered responses are blocked fail-closed
- Data budget tracking on response size
What the agent receives: Raw HTTP response from the origin server.
Use when: Your application makes plaintext HTTP requests through HTTP_PROXY. Note that most modern APIs use HTTPS, making this mode less common.
HTTP_PROXY=http://localhost:8888 curl http://example.com
WebSocket Proxy (/ws?url=...)#
Bidirectional WebSocket proxy with frame-level scanning.
Scanning:
- Ordered URL scan on the target URL
request_policyroute-only checks on the upgrade and per-frame operation checks on reassembled text frames- DLP scanning on WebSocket upgrade request headers
- Bidirectional frame scanning (both client-to-server and server-to-client)
- Fragment reassembly for multi-frame messages
- Compression rejection (RSV1 bit check prevents deflate-based DLP bypass)
- Data budget tracking per domain
Learn-and-lock contracts: WebSocket handshakes use HTTP GET semantics. A signed GET rule for https://api.example.com/stream also authorizes a /ws?url=wss://api.example.com/stream handshake; frame-level DLP and injection scanning still run after the connection is established.
What the agent receives: WebSocket frames, scanned in both directions.
Use when: Your agent uses WebSocket connections for real-time communication and you need DLP scanning on the message content.
# Agent connects to pipelock, which proxies to the target
ws://localhost:8888/ws?url=wss://api.example.com/stream
MCP stdio proxy (pipelock mcp proxy -- COMMAND)#
Wraps a local MCP server process with full bidirectional message scanning.
Scanning:
- Response scanning: injection detection in tool results
- Input scanning: DLP + injection in tool arguments (when
mcp_input_scanningenabled) - Tool scanning: poisoned description detection + rug-pull drift detection (when
mcp_tool_scanningenabled) - Tool policy: pre-execution allow/deny rules with shell obfuscation detection (when
mcp_tool_policyenabled) - Chain detection: suspicious tool call sequence patterns (when
tool_chain_detectionenabled) - Session binding: tool inventory pinning per session (when
mcp_session_bindingenabled)
What the agent receives: MCP responses with injection warnings injected, or blocked entirely depending on config action.
Use when: Running local MCP servers (filesystem, database, custom tools) and you want to scan all tool interactions.
{
"mcpServers": {
"filesystem": {
"command": "pipelock",
"args": ["mcp", "proxy", "--config", "pipelock.yaml", "--", "npx", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
}
}
MCP HTTP Proxy (pipelock mcp proxy --upstream URL)#
Proxies a remote MCP server over HTTP with the same scanning as stdio mode.
Scanning: Same 6 layers as MCP stdio (response, input, tool, policy, chain, session binding).
Transport sub-modes:
- Stdio-to-HTTP bridge (
pipelock mcp proxy --upstream URL): Translates stdio JSON-RPC to HTTP requests against a streamable HTTP MCP server - HTTP reverse proxy (
pipelock mcp proxy --listen ADDR --upstream URLorpipelock run --mcp-listen ADDR --mcp-upstream URL): Listens on an HTTP port and reverse-proxies to the upstream MCP server
Non-loopback reverse-proxy listeners fail closed unless a bearer token file is
configured. Standalone mode uses --listener-auth-token-file; combined mode
uses --mcp-auth-token-file. Clients authenticate to Pipelock with
Proxy-Authorization: Bearer ...; the independent Authorization header is
preserved for the upstream. Browser clients instead use Authorization for
the listener credential because browsers do not expose Proxy-Authorization
to JavaScript; that consumed header is not forwarded, so configure any browser
upstream credential separately. Browser requests carrying Origin are rejected by
default and can be admitted one exact origin at a time with
--listener-allowed-origin or --mcp-allowed-origin. The explicit
--listener-allow-unauthenticated / --mcp-allow-unauthenticated escape hatch
is for deployments where a verified network policy is the access boundary.
Standalone reverse-listener mode also honors --header / --header-file for
operator-configured upstream credentials; those values take precedence over
client-supplied Authorization, MCP protocol-version, and A2A service headers.
Duplicate or malformed session/protocol service headers are rejected before
forwarding. A tokenless loopback listener additionally requires the request
Host to be localhost or a literal loopback address on the listener's actual
port, closing the browser DNS-rebinding path that a loopback bind alone leaves
open.
Use when: Connecting to remote MCP servers over HTTP and you want the same scanning coverage as local stdio servers.
Authenticated upstreams (--header): When the upstream MCP server requires a static auth header (Bearer token, API key), pass it via --header:
pipelock mcp proxy --upstream https://mcp.example.com/v1 \
--header "Authorization: Bearer $UPSTREAM_TOKEN"
The flag is repeatable. Pipelock validates header names as RFC 7230 tokens and rejects ASCII control bytes, DEL, CRLF, and Unicode whitespace in values. The transport-managed and connection-critical headers Mcp-Session-Id, Content-Type, Accept, Content-Length, Transfer-Encoding, and Host are blocked case-insensitively at both the CLI flag-parser and the transport layer so an attacker-controlled extra header cannot shadow Pipelock's session correlation or smuggle a request via header injection.
MCP WebSocket Proxy (pipelock mcp proxy --upstream ws://...)#
Proxies a remote MCP server over WebSocket with the same scanning as stdio mode.
Scanning: Same 6 layers as MCP stdio (response, input, tool, policy, chain, session binding).
How it works: When --upstream receives a ws:// or wss:// URL, pipelock connects to the upstream over WebSocket and translates between stdin/stdout JSON-RPC and WebSocket text frames. Each JSON-RPC message maps to one WebSocket text frame. Fragment reassembly is handled automatically.
Use when: Connecting to MCP servers that expose a WebSocket endpoint (common with OpenClaw gateways and other real-time MCP hosts).
{
"mcpServers": {
"remote": {
"command": "pipelock",
"args": ["mcp", "proxy", "--config", "pipelock.yaml", "--upstream", "ws://localhost:3000/mcp"]
}
}
}
Security Implications#
CONNECT Tunnels: With and Without TLS Interception#
Without TLS interception (tls_interception.enabled: false, the default), CONNECT tunnels are opaque encrypted bytes after the hostname scan. DLP cannot detect secrets in bodies or headers, and response injection scanning does not apply.
With TLS interception enabled, pipelock performs a TLS MITM: it terminates TLS with the client (forged certificate), scans the decrypted traffic, then forwards to the upstream server over a separate TLS connection. This closes the body-blindness gap.
Without interception:
- DLP cannot detect secrets in HTTPS request/response bodies
- Response injection scanning does not apply
- Only destination-visible URL scanning applies; encrypted request and response content remains opaque
With interception:
- Full request body DLP (JSON, form, multipart)
- Request header DLP (Authorization, Cookie, etc.)
- Response injection scanning (buffered, scan-then-send)
- Authority enforcement (Host must match CONNECT target)
If your agent handles secrets and you need content-level DLP on HTTPS traffic, either enable TLS interception or use the fetch proxy or MCP proxy modes.
Fetch Proxy vs CONNECT: Trade-offs#
| Concern | Fetch Proxy | CONNECT (no interception) | CONNECT (TLS interception) |
|---|---|---|---|
| URL scanning | Ordered pipeline | Ordered pipeline | Ordered pipeline |
| DLP on request bodies | N/A | No (encrypted) | Yes |
| DLP on responses | Yes | No (encrypted) | Yes |
| Injection detection | Yes | No (encrypted) | Yes |
| Agent receives | Extracted text | Raw HTTPS response | Raw HTTPS response (re-encrypted) |
| TLS termination | Pipelock terminates | End-to-end | Pipelock MITM (forged cert) |
| SDK compatibility | Requires /fetch API | Native HTTPS_PROXY | Native HTTPS_PROXY + CA trust |
| Performance | Slower (extraction) | Fastest (pass-through) | Moderate (decrypt + scan + re-encrypt) |
Signed Action Receipt Coverage#
Every configured enforcement event produces a signed action receipt: every block, and — under flight_recorder.require_receipts: true — every allow on the per-request proxy and MCP decision paths (including A2A method allows). Clean frames of a long-lived stream are summarized rather than individually receipted; the deliberate exceptions are listed in Intentional no-receipt and summarized cases below. The table below enumerates which deny paths are covered on each transport. Every row has been exercised by a test in the signed-receipt-coverage suite.
| Transport | Pre-forward blocks | Post-forward blocks | Transport-specific blocks | Receipt path |
|---|---|---|---|---|
Fetch (/fetch) | URL scan, DLP, SSRF | Redirect block, response scan, audit-mode escalation, session profiling, header DLP, budget exhaustion, cross-request exfiltration | — | Direct emit to flight recorder |
| CONNECT (no TLS intercept) | URL scan, DLP, SSRF, blocklist | — | Redirect inside tunnel (not visible) | Hostname-only receipts |
| CONNECT + TLS interception | URL scan + full hostname DLP | Body DLP, header DLP, response injection | Authority mismatch | Full content receipts; required inner-request allows are durable before upstream |
| Absolute-URI (forward proxy) | URL scan, DLP, SSRF | Redirect block, response scan, audit-mode escalation, session profiling, header DLP, budget exhaustion, CEE | A2A header scan, A2A stream scan, A2A response body scan | Full content receipts |
WebSocket (/ws) | Handshake-time URL scan, DLP | Frame-level DLP, injection, address poisoning, CEE | Session close reason | Per-frame block receipts + session close (clean frames summarized, not individually receipted) |
| MCP stdio | Input scan, tool scan, policy | Response injection, chain detection, session binding drift | Tool call, tool response, policy decision | Full content receipts |
| MCP HTTP / SSE | Input scan, tool scan, policy | Response injection, chain detection, session binding drift | Tool call, tool response, policy decision | Full content receipts, stream-aware |
| MCP HTTP reverse proxy | Input scan, tool scan, policy | Response injection, chain detection, session binding drift | Tool call, tool response, policy decision | Full content receipts |
Receipt emission is best-effort by default on the async flight-recorder channel and survives config reload across all transports. Set flight_recorder.require_receipts: true to fail closed before allow-path proxy/MCP traffic is forwarded when the required receipt cannot be emitted; for TLS-intercepted CONNECT, the inner HTTP request's durable intent receipt is emitted before the upstream request. Block-path receipts stay best-effort because the action is already denied. Receipts chain via chain_prev_hash / chain_seq for tamper-evidence. See docs/guides/receipt-verification.md for the verify CLI and the cross-implementation conformance suite.
Intentional no-receipt and summarized cases#
The guarantee is "every configured enforcement event is provable," not "every frame produces a receipt." The matrix below is the canonical, single-source-of-truth list of when a signed action receipt is and is not emitted, including the deliberate no-receipt cases (clean streaming frames are summarized to avoid an O(n)-in-stream-length receipt flood that a chatty peer could weaponize as a denial-of-service vector). It is generated from and drift-checked against TestReceiptCoverage_MatrixMatchesDocs in internal/proxy/receipt_coverage_matrix_test.go; edit the matrix there and run UPDATE_GOLDEN=1 go test ./internal/proxy/ -run TestReceiptCoverage_MatrixMatchesDocs to regenerate this block.
| Scenario | Receipt | Notes |
|---|---|---|
tools/call block | yes | Block receipt (best-effort: the action is already denied). |
tools/call allow, require_receipts: true | yes | Allow receipt; fails closed if emission fails. |
tools/call allow, default | no | Allow receipts are opt-in via require_receipts. |
| A2A method block | yes | Block receipt with the A2A method name as target. |
A2A method allow, require_receipts: true | yes | Allow receipt; fails closed if emission fails. |
| A2A method allow, default | no | Allow receipts are opt-in via require_receipts. |
| Proxy block (fetch / CONNECT / forward / WS handshake) | yes | Pre- or post-forward block receipt. |
Proxy allow, require_receipts: true | yes | Allow receipt; fails closed if emission fails. |
| Clean WebSocket frame | no (intentional) | Per-frame allow receipts are O(n) in stream length; summarized, not emitted, to avoid a receipt-flood denial-of-service vector. |
| Clean SSE / streamed response chunk | no (intentional) | Streamed response chunks are summarized, not receipted per chunk. |
| WebSocket session close | yes | Session-close receipt records the close reason. |
| Required receipt emission fails | block | Request fails closed with receipt_emission_failed instead of forwarding. |
See Also#
- Configuration Reference for all config fields controlling each proxy mode
- OpenClaw Integration for deploying pipelock with OpenClaw gateways
- Deployment Recipes for Docker Compose, Kubernetes, and host-level enforcement
pipelock init sidecarfor the generated companion-proxy deploymentpipelock sessionfor airlock inspection and recovery- Bypass Resistance for details on how each scanning layer resists evasion
- Attacks Blocked for real-world attack examples across all transport modes