You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
**Per-request policy evaluation** applies to HTTP/HTTPS. Policy is re-evaluated for every HTTP request, so "Allow Once" permits a single HTTP request and subsequent requests on the same keep-alive connection re-trigger the approval flow. When a per-request approval resolves to "Always Allow" or "Always Deny", the `RequestPolicyChecker` persists the new rule to the policy store via its `PersistRuleFunc` callback and swaps in a freshly compiled engine, so subsequent requests match via the fast path instead of re-entering the approval flow. A fast path skips per-request checks when the SOCKS5 CONNECT matched an explicit allow rule (`RuleMatch`, not default verdict) so normally allowed destinations incur no extra overhead. WebSocket, SSH, and IMAP/SMTP remain connection-level on purpose: per-message or per-command policy on those would blow past the broker's 5/min per-destination rate limit and break normal usage.
171
+
**Per-request policy evaluation** applies to HTTP/HTTPS, gRPC-over-HTTP/2, and QUIC/HTTP3. Policy is re-evaluated for every HTTP request (or HTTP/2 stream, or HTTP/3 request), so "Allow Once" permits a single request and subsequent requests on the same connection re-trigger the approval flow. When a per-request approval resolves to "Always Allow" or "Always Deny", the `RequestPolicyChecker` persists the new rule to the policy store via its `PersistRuleFunc` callback and swaps in a freshly compiled engine, so subsequent requests match via the fast path instead of re-entering the approval flow. A fast path skips per-request checks when the SOCKS5 CONNECT matched an explicit allow rule (`RuleMatch`, not default verdict) so normally allowed destinations incur no extra overhead. WebSocket, SSH, and IMAP/SMTP remain connection-level on purpose: per-message or per-command policy on those would blow past the broker's 5/min per-destination rate limit and break normal usage.
172
172
173
-
**gRPC caveat**: honest gRPC rides over HTTP/2 and enters goproxy via the HTTP/2 PRI preface upgrade. goproxy v1.8.3 defaults to `AllowHTTP2 == false`, so real HTTP/2 streams are rejected at the goproxy layer and never reach `injectCredentials` per stream. Requests that do reach the handler are HTTP/1.1-shaped (possibly carrying a gRPC content-type header) and go through per-request policy normally. Treat gRPC-over-HTTP/2 as effectively per-connection until `AllowHTTP2` is enabled and the H2Transport is wired to call back into the per-request check.
173
+
**MITM library:** HTTPS interception uses go-mitmproxy (`github.com/lqqyt2423/go-mitmproxy`). The `SluiceAddon` struct in `internal/proxy/addon.go` implements go-mitmproxy's `Addon` interface. `Requestheaders` fires per HTTP/2 stream, giving true per-request policy for gRPC and other HTTP/2 traffic. `Request` handles credential injection (three-pass phantom swap). `Response` handles OAuth token interception.
174
174
175
-
**QUIC caveat**: the per-request machinery in `QUICProxy.buildHandler` is in place but currently unreachable in production because `EvaluateQUIC` only returns Allow or Deny (never Ask), so the UDP dispatch loop in `server.go` always passes a nil checker. Unit tests exercise the mechanism directly via `RegisterExpectedHostWithChecker`.
175
+
**QUIC per-request:**`EvaluateQUICDetailed`returns Ask when an ask rule matches. The UDP dispatch loop creates a `RequestPolicyChecker` and passes it to `buildHandler`, which calls `CheckAndConsume` per HTTP/3 request.
176
176
177
-
See `internal/proxy/request_policy.go`, `internal/policy/engine.go` (`EvaluateDetailed`), and `internal/proxy/inject.go` (`injectCredentials`).
177
+
See `internal/proxy/request_policy.go`, `internal/policy/engine.go` (`EvaluateDetailed`, `EvaluateQUICDetailed`), and `internal/proxy/addon.go` (`SluiceAddon`).
**Per-request policy:** HTTP/HTTPS evaluates policy on every HTTP request. "Allow Once" permits exactly one request, so a second request on the same keep-alive connection re-triggers the approval flow. When a per-request approval resolves to "Always Allow" or "Always Deny", the new rule is persisted to the store and the engine is recompiled so subsequent requests match via the fast path. Destinations matched by an explicit allow rule take a fast path that skips per-request checks entirely. WebSocket, SSH, and IMAP/SMTP remain per-connection on purpose: per-message or per-command approvals would hit the broker's rate limit and break normal usage.
328
-
329
-
**gRPC note:** real gRPC rides over HTTP/2 and enters goproxy via the HTTP/2 PRI preface. goproxy v1.8.3 defaults to `AllowHTTP2 == false`, so HTTP/2 streams are rejected at the goproxy layer and do not reach the per-request handler per stream. HTTP/1.1-shaped requests with a gRPC content-type header go through per-request policy normally. Treat honest gRPC-over-HTTP/2 as per-connection for now.
330
-
331
-
**QUIC note:** the per-request infrastructure in `QUICProxy.buildHandler` is present but currently unreachable because the UDP dispatch loop only reaches the QUIC path for connections that matched an explicit allow rule, so it always passes a nil checker. Tests exercise the mechanism directly.
327
+
**Per-request policy:** HTTP/HTTPS, gRPC-over-HTTP/2, and QUIC/HTTP3 all evaluate policy on every request. "Allow Once" permits exactly one request (or HTTP/2 stream, or HTTP/3 request), so subsequent requests on the same connection re-trigger the approval flow. When a per-request approval resolves to "Always Allow" or "Always Deny", the new rule is persisted to the store and the engine is recompiled so subsequent requests match via the fast path. Destinations matched by an explicit allow rule take a fast path that skips per-request checks entirely. WebSocket, SSH, and IMAP/SMTP remain per-connection on purpose: per-message or per-command approvals would hit the broker's rate limit and break normal usage.
332
328
333
329
**DNS policy design**: The DNS interceptor only blocks explicitly denied domains (returns NXDOMAIN). All other verdicts (allow, ask, default) are forwarded to the upstream resolver. This is intentional. Policy enforcement for "ask" destinations happens at the SOCKS5 CONNECT layer, not DNS. Blocking DNS for "ask" destinations would prevent the TCP connection from ever reaching the approval flow. The DNS interceptor populates a reverse cache (IP -> hostname) so the SOCKS5 handler can recover hostnames from IP-only CONNECT requests sent by tun2proxy. For TLS connections, SNI from the ClientHello provides an additional hostname recovery path.
-[] Verify all existing HTTP/1.1 per-request behavior is preserved
216
-
-[] Verify credential injection works on HTTP/2 streams
217
-
-[] Verify WebSocket upgrade still works
218
-
-[] Run full test suite: `go test ./... -v -timeout 120s`
219
-
-[] Run e2e tests: `go test -tags=e2e ./e2e/ -v -count=1 -timeout=300s`
213
+
-[x] Verify HTTP/2 per-request policy works (real gRPC-over-HTTP/2 stream fires per-request check) -- `TestHTTP2PerRequestPolicyAndInjection` (broker called >=2 times for two HTTP/2 streams on same connection), `TestHTTP2PerRequestDenySecondStream` (first stream 200, second stream 403 with per-stream granularity)
0 commit comments