Skip to content

feat(proxy): per-request policy, go-mitmproxy migration, QUIC Ask#27

Merged
nnemirovsky merged 19 commits intomainfrom
per-request-policy
Apr 12, 2026
Merged

feat(proxy): per-request policy, go-mitmproxy migration, QUIC Ask#27
nnemirovsky merged 19 commits intomainfrom
per-request-policy

Conversation

@nnemirovsky
Copy link
Copy Markdown
Owner

Summary

  • Per-request HTTP policy evaluation: "Allow Once" means one HTTP request, not one TCP connection
  • Replace goproxy with go-mitmproxy for HTTP/2 per-stream interception (gRPC now per-request)
  • QUIC/HTTP3 per-request Ask verdicts via EvaluateQUICDetailed
  • E2E tests with configurable webhook approval channel

Changes

  • internal/policy/engine.go: EvaluateDetailed, EvaluateQUICDetailed with MatchSource
  • internal/proxy/request_policy.go: RequestPolicyChecker with seed credits, persist callback, protocol-aware evaluation
  • internal/proxy/addon.go: SluiceAddon (go-mitmproxy Addon) with per-request policy, credential injection, OAuth response interception, streaming phantom swap
  • internal/proxy/ca_adapter.go: Bridge sluice CA to go-mitmproxy cert.CA interface
  • internal/proxy/server.go: SOCKS5 routing to go-mitmproxy, pending checker handoff, QUIC Ask wiring
  • internal/proxy/quic.go: Per-request checker in buildHandler
  • e2e/per_request_test.go: 4 e2e tests with webhook-driven approval flow
  • Removed internal/proxy/inject.go (goproxy-based, replaced by addon.go)
  • Removed github.com/elazarl/goproxy dependency

Test plan

  • go test ./... -timeout 120s (all 12 packages)
  • go test -tags=e2e ./e2e/ -v -count=1 -timeout=300s (41 pass, 2 skip Apple Container)
  • Deploy dev image to knuth, test per-request with real Telegram approvals
  • Verify HTTP/2 negotiation through the proxy
  • Verify WebSocket MCP gateway still works

…al channels

Integrate RequestPolicyChecker into the SOCKS5 handler and QUIC/HTTP3
path. Add method/path context to approval requests so Telegram shows
which HTTP request triggered the Ask verdict. Include fixes for edge
cases in broker-less and default-verdict scenarios.
Replace the goproxy-based HTTPS MITM with go-mitmproxy and a unified
SluiceAddon that handles per-request policy checks, credential
injection (static phantom swap and binding headers), and OAuth response
interception in a single addon lifecycle. Add CONNECT auth secret to
prevent direct bypass of the MITM listener.
…tests

Two full-pipeline integration tests proving go-mitmproxy fires per-request
addon hooks for each HTTP/2 stream on a single TCP connection:

- TestHTTP2PerRequestPolicyAndInjection: two HTTP/2 requests through
  SOCKS5 -> go-mitmproxy MITM -> TLS HTTP/2 backend. Verifies the broker
  is called for each stream (connection-level + per-request) and credential
  injection works on both streams independently.

- TestHTTP2PerRequestDenySecondStream: sequencing broker allows the
  connection and first stream but denies the second. Verifies per-stream
  granularity: first request 200, second request 403, backend sees exactly
  one request.

These tests are the key validation that the goproxy replacement works for
the gRPC use case (HTTP/2 multiplexed streams with per-request policy).
Add EvaluateQUICDetailed to the policy engine for QUIC-specific Ask
verdict support. Wire the per-request checker through buildHandler so
HTTP/3 requests on keep-alive QUIC sessions are individually approved.
Extract resolveQUICPolicy helper in server.go for clarity.
Add webhook-based verdict server and sluiceWithWebhook test helper.
Add e2e tests verifying that Allow Once approves exactly one HTTP
request per SOCKS5 connection and that subsequent requests are blocked.
Update CLAUDE.md and README with per-request policy details and
go-mitmproxy migration notes. Move completed plan files to
docs/plans/completed/.
Defer ask approval from SOCKS5 CONNECT to per-request HTTP handler
so only one Telegram message appears per request (not two). The
message combines destination and request info:

  OpenClaw wants to connect to:
  HTTPS httpbin.org:443
  GET https://httpbin.org/ip
  Allow this request?

Also disable Telegram web page preview on approval messages to
prevent link-preview artifacts.
Non-MITM protocols (SSH, SMTP, plain HTTP, generic TCP) now check
the deferred per-request policy before relaying. Without this,
connections to non-TLS ports on ask destinations would pass through
without any approval.
…rotocols

SSH, plain TCP, and other non-MITM connections that go through the
direct dial path now check the deferred per-request policy before
connecting upstream. Without this, connections to ask destinations
on non-TLS ports (e.g. SSH port 22) bypassed approval entirely.
Approval messages now display the negotiated HTTP version alongside
the request method, e.g. "GET https://example.com/api (HTTP/2)".
Helps distinguish HTTP/1.1 from HTTP/2 traffic in approval prompts.
…provals

Use go-mitmproxy fork with WebSocket Requestheaders fix.
…ent port overwrite

All CheckAndConsume calls in byte-detection and direct-connect paths
now pass WithProtocol so protocol-scoped rules (ssh, http, imap, smtp)
are matched correctly.

Prevent TlsEstablishedServer from overwriting a port already captured
by ServerConnected with a recovered/defaulted value.
@nnemirovsky nnemirovsky merged commit 085fd23 into main Apr 12, 2026
6 checks passed
@nnemirovsky nnemirovsky deleted the per-request-policy branch April 12, 2026 10:13
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