Skip to content

Header validation stricter than RFC 9113/8441 for pseudo-headers (extended CONNECT / CONNECT edge case) #1308

@chasingimpact

Description

@chasingimpact

Summary

h2 Enforces :path as mandatory for an ordinary HTTP/2 CONNECT request. Per RFC 9113 (HTTP/2) s8.3, CONNECT requests MUST include :method = "CONNECT" and :authority, and MUST NOT include :scheme and :path. In my test, h2 raises ProtocolError: Header block missing mandatory b':path' header when I send an ordinary CONNECT without :path.

Baseline GET is accepted (as expected), and extended CONNECT for WebSocket (RFC 8441 s4) is also accepted (as expected). Only the ordinary CONNECT case seems overly strict.

Environment

  • h2: 4.3.0
  • Python: 3.11.x
  • OS: Linux (local dev)
  • Install method: pip3 install h2 in a fresh venv

Minimal Reproducer

# repro_h2_headers.py
from h2.config import H2Configuration
from h2.connection import H2Connection

def send(stream_id, headers):
    conn = H2Connection(config=H2Configuration(client_side=True, header_encoding="utf-8"))
    conn.initiate_connection()
    # mock a SETTINGS ack so we can try sending headers without a real socket:
    try:
        conn.data_to_send()
        # Empty SETTINGS ack frame: length=0, type=4, flags=1(ACK), stream=0
        conn.receive_data(b"\x00\x00\x00\x04\x01\x00\x00\x00\x00")
    except Exception:
        pass
    try:
        conn.send_headers(stream_id=stream_id, headers=headers, end_stream=True)
        print("OK: headers accepted")
    except Exception as e:
        print("ERROR:", type(e).__name__, str(e))

# A) Ordinary GET (baseline)
send(1, [
    (":method", "GET"),
    (":scheme", "https"),
    (":authority", "example.com"),
    (":path", "/"),
])

# B) Ordinary CONNECT (RFC 9113 s8.3): only :method=CONNECT and :authority; no :scheme/:path
send(3, [
    (":method", "CONNECT"),
    (":authority", "example.com:443"),
])

# C) Extended CONNECT for WebSocket (RFC 8441 s4): :method=CONNECT, :protocol=websocket, plus :scheme/:path/:authority
send(5, [
    (":method", "CONNECT"),
    (":protocol", "websocket"),
    (":scheme", "https"),
    (":path", "/chat?room=1"),
    (":authority", "ws.example.com"),
])

Actual results

OK: headers accepted
ERROR: ProtocolError Header block missing mandatory b':path' header
OK: headers accepted
  • Baseline GET is accepted
  • Ordinary CONNECT without :path is REJECTED: (ProtocolError … missing mandatory ':path')
  • Extended CONNECT (WebSocket) with :scheme/:path/:authority : accepted

Expected results

  • Ordinary CONNECT (RFC 9113 s8.3) - The request header block MUST include :method = CONNECT and :authority, and MUST NOT include :scheme or :path. A header block that omits :path for ordinary CONNECT should be accepted.
  • Extended CONNECT / WebSocket (RFC 8441 s4) - The request MUST include :method = CONNECT, :protocol = "websocket", and the regular pseudo-header tuple :scheme, :path, :authority. The library already accepts this (good).

Given the above, it looks like h2 is applying a generic ":path is mandatory" rule even to ordinary CONNECT, which conflicts with RFC 9113 s8.3.

Why this matters

Implementations that speak HTTP/2 CONNECT (f.e., proxies, gateways) rely on the ordinary CONNECT shape to establish opaque TCP tunnels. If client libraries forbid the spec-compliant header set (no :path), they can’t originate legal CONNECT exchanges without workarounds.

Suggested paths forward

  • Relax header validation for ordinary CONNECT so that :path/:scheme are not required (and ideally rejected if present).
  • Keep stricter validation for extended CONNECT (where :scheme/:path are required alongside :protocol).
  • Optionally, improve the exception message to mention which CONNECT mode was detected and which pseudo-header was unexpected/missing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions