Skip to content

chrome impersonate sends the HTTP/2 priority header on HTTP/1.1 connections (real Chrome doesn't) #785

Description

@joshkulick

When a request is made over HTTP/1.1, the chrome impersonate target still
emits a priority request header (e.g. priority: u=0, i). Real Chrome
only sends the priority header on HTTP/2 and HTTP/3 — it's the RFC 9218
Extensible Prioritization signal, which has no HTTP/1.1 form. So on any
HTTP/1.1 connection the impersonation is slightly less faithful than the
real browser: it carries an extra header Chrome would never send on that
connection.

This matters for sites whose server only negotiates HTTP/1.1 (so every
request is 1.1): the spurious priority header is a small but real
fingerprint divergence from genuine Chrome traffic, which is the exact
thing impersonate is meant to avoid. I verified against captured real
Chrome 149 HTTP/1.1 traffic — no priority header is present on any
request; curl_cffi adds one.

Environment

  • curl_cffi: 0.15.0
  • Python: 3.13 (Windows 11) — but it's protocol-dependent, not
    OS-dependent
  • impersonate target: chrome (resolves to chrome146)

Reproduction

import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from curl_cffi import requests
from curl_cffi.const import CurlHttpVersion

class H(BaseHTTPRequestHandler):
def do_GET(self):
hdrs = {k.lower(): v for k, v in self.headers.items()}
print("HTTP version:", self.request_version)
print("priority:", hdrs.get("priority"))
self.send_response(204); self.end_headers()
def log_message(self, *a): pass

srv = ThreadingHTTPServer(("127.0.0.1", 0), H)
port = srv.server_address[1]
threading.Thread(target=srv.serve_forever, daemon=True).start()
with requests.Session() as s:
s.get(f"http://127.0.0.1:{port}/", impersonate="chrome",
http_version=CurlHttpVersion.V1_1)
srv.shutdown()

Actual

HTTP version: HTTP/1.1
priority: u=0, i

Expected

No priority header on an HTTP/1.1 request — matching real Chrome, which
only emits priority on HTTP/2/HTTP/3.

Notes / suggested fix

  • The default header set appears to be applied independently of the
    negotiated protocol. priority (and arguably anything else that is
    h2/h3-specific) should be omitted when the connection is HTTP/1.1, i.e.
    the injected header list should be protocol-aware.
  • This likely lives in the upstream curl-impersonate header definitions
    rather than the Python layer, but filing here as the entry point.
  • It only surfaces when the connection is actually HTTP/1.1 (server
    offering only 1.1, or http_version pinned). On the default HTTP/2 path
    priority is correct, so most users won't hit it.
  • Current workaround for anyone who needs HTTP/1.1 fidelity: drop the
    header explicitly by passing headers={"priority": None} (curl_cffi's
    "disable this header" sentinel).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions