fix(proxy): support HTTP/2 via ALPN, tunnel h2 connections transparently#71
Merged
Merged
Conversation
When a client (e.g. the Cursor CLI, gRPC-based tools) negotiates HTTP/2 via ALPN during the TLS handshake, the existing MITM proxy fails with "malformed HTTP request/response" errors because it tries to parse HTTP/2 binary frames as HTTP/1.1 text. Fix: - Advertise both "h2" and "http/1.1" in NextProtos on the server-side TLS config so that ALPN negotiation completes successfully. - After the handshake, check NegotiatedProtocol. If "h2", hand off to the new tunnelH2() function instead of the HTTP/1.1 MITM path. - tunnelH2() opens a fresh TLS connection to the origin (also with h2 in NextProtos), then pipes the two sides together with io.Copy — a transparent tunnel that preserves all HTTP/2 semantics. Policy enforcement (connect allow/deny) is unchanged: it still happens in handleTransparentHTTPS before the tunnel is established, so h2 traffic is still subject to Cedar policy decisions. Full h2 MITM (header inspection, rewriting) is left as a future improvement; transparent tunneling is sufficient to unblock h2 clients. Made-with: Cursor
JuroOravec
added a commit
to safe-ai-factory/leash
that referenced
this pull request
Apr 6, 2026
Builds a patched version of the upstream Leash Docker image that adds transparent HTTP/2 tunnelling via ALPN. This is a temporary workaround for the Cursor CLI (and any gRPC client) failing inside Leash-sandboxed containers with 'malformed HTTP' errors. The fix is submitted upstream at strongdm#71. This file is intentionally on a separate branch from the upstream PR so it does not pollute the files to be merged. Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
I tried to use Cursor CLI inside the Leash coder container, but it was consistently failing to connect. The problem wasn't with authentication.
With the help of Claude Sonnet 4.6, I was able to triage the issue.
Problem
When a client negotiates HTTP/2 via ALPN during the TLS handshake, Leash's
MITM proxy fails with errors like:
This happens because the proxy accepts the TLS connection but then tries to
parse HTTP/2 binary frames as HTTP/1.1 text. The
\x00\x00\x06\x04bytesare an HTTP/2 SETTINGS frame;
"SM"is the start of the HTTP/2 clientpreface (
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n).Affected clients include the Cursor CLI, any gRPC-based tool, and anything
else that uses ALPN to negotiate HTTP/2 over TLS.
Fix
Two changes to
handleTransparentHTTPS:Advertise ALPN protocols — add
NextProtos: []string{"h2", "http/1.1"}to the server-side
tls.Config. Without this, clients that require ALPNnegotiation abort the handshake entirely; clients that proceed anyway send
HTTP/2 frames into an HTTP/1.1 parser.
Detect and tunnel h2 — after the handshake, check
NegotiatedProtocol. If it is"h2", hand off to the newtunnelH2()function instead of the HTTP/1.1 MITM path.
tunnelH2()opens a fresh TLS connection to the origin (also withh2inNextProtos), then pipes the two sides together withio.Copy— atransparent tunnel that preserves all HTTP/2 semantics.
Policy enforcement
Connect allow/deny policy is unchanged. It is evaluated in
handleTransparentHTTPSbefore the tunnel is established, so HTTP/2traffic remains subject to Cedar policy decisions.
Limitations / future work
This PR does not implement full HTTP/2 MITM (header inspection, request
logging, rewriting). The transparent tunnel approach is intentional: full h2
MITM requires a complete HTTP/2 framing + HPACK implementation and is
significantly more complex. A transparent tunnel is sufficient to unblock h2
clients while keeping the change small and reviewable.
Testing
Verified by running the Cursor CLI agent inside a Leash-sandboxed Docker
container. Previously the agent entered an infinite reconnect loop
(
{"type":"connection","subtype":"reconnecting",...}); with this fix itconnects successfully and processes requests.