Skip to content

fix: skip http2 fallback when explicitly disabled#530

Closed
khs-alt wants to merge 2 commits intoprojectdiscovery:mainfrom
khs-alt:khs-alt/fix/skip-http2-fallback-when-explicitly-disabled
Closed

fix: skip http2 fallback when explicitly disabled#530
khs-alt wants to merge 2 commits intoprojectdiscovery:mainfrom
khs-alt:khs-alt/fix/skip-http2-fallback-when-explicitly-disabled

Conversation

@khs-alt
Copy link
Copy Markdown

@khs-alt khs-alt commented Mar 4, 2026

fix httpx #2240

Description

When a caller disables HTTP/2 (e.g., httpx -pr http11 sets
TLSNextProto to an empty map), retryablehttp-go still falls back to
HTTPClient2 on malformed HTTP/2 errors, silently switching protocols.

See my comment on the issue for detailed root cause analysis.

Changes

  • DialTLSContext = nil on HTTPClient2 (client.go):
    Clears inherited DialTLSContext so http2.ConfigureTransport can
    negotiate h2 via ALPN properly.

  • isHTTP2Disabled()HTTPClient2 = nil (client.go):
    Detects when caller's transport has HTTP/2 disabled (non-nil empty
    TLSNextProto — Go standard convention) and skips creating HTTPClient2.

  • Nil guard on fallback (do.go):
    Added c.HTTPClient2 != nil check before HTTP/2 fallback, so disabled
    configs go through normal retry policy instead.

Before

Default — HTTPClient2 fallback fires but fails (no h2 ALPN), request sent twice:
run go run ./cmd/httpx -u https://localhost:8443/malformed-version

--- end ---
[http/1.1] GET /malformed-version → malformed version response (HTTP/2 200 OK)
[conn] ALPN negotiated: 
--- end ---
[http/1.1] GET /malformed-version → malformed version response (HTTP/2 200 OK)

-pr http11 — fallback still fires despite HTTP/1.1-only config, request sent twice:
run go run ./cmd/httpx -pr http11 -u https://localhost:8443/malformed-version

--- end ---
[http/1.1] GET /malformed-response → malformed response (HTTP/2.0)
[conn] ALPN negotiated: 
--- end ---
[http/1.1] GET /malformed-response → malformed response (HTTP/2.0)

After

Default — HTTPClient2 fallback works correctly via h2 ALPN:
run go run ./cmd/httpx -u https://localhost:8443/malformed-version

--- end ---
[http/1.1] GET /malformed-version → malformed version response (HTTP/2 200 OK)
[conn] ALPN negotiated: h2
[h2] GET /malformed-version → 200 OK

-pr http11 — no fallback, single request as expected:
run go run ./cmd/httpx -pr http11 -u https://localhost:8443/malformed-version

--- end ---
[http/1.1] GET /malformed-response → malformed response (HTTP/2.0)
Full server logs

Before

run
go run ./cmd/httpx -u https://localhost:8443/malformed-version

malformed test server (HTTPS): https://localhost:8443
  ALPN h2         -> valid HTTP/2 response (pass)
  ALPN http/1.1   -> /malformed-version, /malformed-response demo
[conn] ALPN negotiated: 
--- raw request ---
GET /malformed-version HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.8.1 Safari/605.1.15
Accept-Charset: utf-8
Accept-Encoding: gzip
Connection: close

--- end ---
[http/1.1] GET /malformed-version → malformed version response (HTTP/2 200 OK)
[conn] ALPN negotiated: 
--- raw request ---
GET /malformed-version HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.8.1 Safari/605.1.15
Accept-Charset: utf-8
Accept-Encoding: gzip
Connection: close

--- end ---
[http/1.1] GET /malformed-version → malformed version response (HTTP/2 200 OK)

run
go run ./cmd/httpx -pr http11 -u https://localhost:8443/malformed-version

malformed test server (HTTPS): https://localhost:8443
  ALPN h2         -> valid HTTP/2 response (pass)
  ALPN http/1.1   -> /malformed-version, /malformed-response demo
[conn] ALPN negotiated: 
--- raw request ---
GET /malformed-response HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:1.9.6.20) Gecko/ Firefox/3.6.12
Accept-Charset: utf-8
Accept-Encoding: gzip
Connection: close

--- end ---
[http/1.1] GET /malformed-response → malformed response (HTTP/2.0)
[conn] ALPN negotiated: 
--- raw request ---
GET /malformed-response HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:1.9.6.20) Gecko/ Firefox/3.6.12
Accept-Charset: utf-8
Accept-Encoding: gzip
Connection: close

--- end ---
[http/1.1] GET /malformed-response → malformed response (HTTP/2.0)

After

run
go run ./cmd/httpx -u https://localhost:8443/malformed-version

malformed test server (HTTPS): https://localhost:8443
  ALPN h2         -> valid HTTP/2 response (pass)
  ALPN http/1.1   -> /malformed-version, /malformed-response demo
[conn] ALPN negotiated: 
--- raw request ---
GET /malformed-version HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:1.9.6.20) Gecko/ Firefox/4.0
Accept-Charset: utf-8
Accept-Encoding: gzip
Connection: close

--- end ---
[http/1.1] GET /malformed-version → malformed version response (HTTP/2 200 OK)
[conn] ALPN negotiated: h2
[h2] GET /malformed-version → 200 OK

run
go run ./cmd/httpx -pr http11 -u https://localhost:8443/malformed-version

malformed test server (HTTPS): https://localhost:8443
  ALPN h2         -> valid HTTP/2 response (pass)
  ALPN http/1.1   -> /malformed-version, /malformed-response demo
[conn] ALPN negotiated: 
--- raw request ---
GET /malformed-response HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15
Accept-Charset: utf-8
Accept-Encoding: gzip
Connection: close

--- end ---
[http/1.1] GET /malformed-response → malformed response (HTTP/2.0)

@neo-by-projectdiscovery-dev
Copy link
Copy Markdown

neo-by-projectdiscovery-dev bot commented Mar 4, 2026

Neo - PR Security Review

No security issues found

Highlights

  • Adds isHTTP2Disabled() detection to respect caller's explicit HTTP/2 disable via TLSNextProto
  • Clears DialTLSContext on HTTPClient2 to enable proper h2 ALPN negotiation instead of relying on connection upgrade
  • Adds nil guard on HTTPClient2 fallback in do.go to prevent fallback when HTTP/2 is explicitly disabled
Hardening Notes
  • Add nil check before c.HTTPClient2.CloseIdleConnections() in do.go:154 to prevent panic when HTTP/2 is disabled and KillIdleConn is enabled

Comment @pdneo help for available commands. · Open in Neo

@Mzack9999
Copy link
Copy Markdown
Member

The underlying issue (httpx#2240) has been resolved at the httpx level — the HTTP/1.1 protocol preference is now correctly enforced without requiring changes to retryablehttp-go. Closing as the original bounty issue is already complete. Thanks for the contribution.

@Mzack9999 Mzack9999 closed this Mar 22, 2026
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.

-pr http11 flag is ignored on retryablehttp-go due to HTTP/2 fallback

2 participants