3612 Commits

Author SHA1 Message Date
Benoit Chesneau
5d819cf360 release: 26.0.0
- Bump version_info to (26, 0, 0)
- Update SECURITY.md supported releases (26.0.0, 25.3.0)
- Add 26.0.0 entry to news.md and 2026-news.md covering eventlet
  removal, ASGI framework compatibility suite, RFC 9110/9112
  request-target and header hardening, smuggling fixes, HEAD/204/304
  body framing, WebSocket close handshake compliance, HTTP/2 ASGI
  stream completion, early-hints validation, framework fixes
  (Django/Litestar/Quart/BlackSheep), and gunicorn_h1c >= 0.6.5
2026-05-05 08:35:19 +02:00
Benoit Chesneau
b45c70df10
Merge pull request #3611 from zc-mattcen/docs-typo
docs: Typo in run example
2026-05-05 01:23:24 +02:00
Benoit Chesneau
99c8d48acf
Merge pull request #3623 from benoitc/chore/drop-eventlet-add-h2-uvloop-test-deps
chore: remove eventlet worker; add h2 and uvloop to test deps
2026-05-05 00:47:48 +02:00
Benoit Chesneau
5a655af50f
Merge pull request #3622 from benoitc/test/docker-port-and-ipv4-fixes
test: unblock docker fixtures on macOS hosts
2026-05-05 00:47:22 +02:00
Benoit Chesneau
201df19a80 chore: remove eventlet worker; add h2 and uvloop to test deps
Eventlet was deprecated for 26.0 and is now removed:
- Delete gunicorn/workers/geventlet.py and its registry entry
- Drop eventlet from config help text, HTTP/2 unsupported-worker
  messages, and the dirty client docstring
- Drop the eventlet optional-dependency, the eventlet entry in the
  testing extra, and the eventlet-only filterwarnings ignore
- Drop the EventletWorkerAlpn test class
- Drop the freebsd CI ignore for the (now non-existent) test_geventlet.py
- Drop eventlet from the issue-triage discussion template
- Drop eventlet from README, install/design/http2/settings/news docs;
  rewrite the news.md entry from 'deprecated' to 'removed in this release'

Add h2 and uvloop to requirements_test.txt so a plain
'pip install -r requirements_test.txt' run reaches feature parity with
'pip install .[testing]' for those two deps. The container suite
previously skipped 87 HTTP/2 tests for missing h2 and 1 for uvloop;
the in-process suite skips drop from 67 to 40.
2026-05-05 00:36:46 +02:00
Benoit Chesneau
f4ac8e1f1b test: pass action name to dirty client and stabilize after TTOU spam
- /unlimited and /limited handlers passed the data dict where the dirty
  client expected the action (method) name, surfacing as a 500 from
  getattr(self, action) on the dirty worker. Pass 'process' as the
  action so the call routes to DirtyApp.process(data).
- TestUnlimitedApps now bumps worker count via TTIN and polls both apps
  for readiness before each test. The preceding TTOU-spam test pins the
  worker count at the LimitedTask floor (2) and the arbiter takes a
  moment to rebind apps to the surviving workers; the previous tests
  raced that rebind and saw 'No workers available'.
2026-05-04 10:32:44 +02:00
Benoit Chesneau
54d38afddf test: unblock docker fixtures on macOS hosts
- per_app_allocation: move host port from 8001 to 28001. OrbStack reserves
  8001 on macOS for vcom-tunnel which makes 'Bind: port already allocated'
  the default failure mode.
- dirty_ttin_ttou: pin BASE_URL to 127.0.0.1 instead of 'localhost'. macOS
  resolves 'localhost' to ::1 first; Docker Desktop / OrbStack only forward
  host ports on IPv4 so the IPv6 attempt resets and the test fixture treats
  the service as unhealthy.
- dirty_ttin_ttou: add setproctitle to the test image. The TTIN/TTOU tests
  count workers via 'pgrep -f dirty-worker', which only matches once
  gunicorn's util._setproctitle has actually renamed the processes.
2026-05-04 09:47:38 +02:00
Benoit Chesneau
68843c8893
Merge pull request #3621 from benoitc/fix/asgi-preserve-content-length-on-head-and-304
fix: keep Content-Length on HEAD and 304 responses
2026-05-03 22:56:24 +02:00
Benoit Chesneau
31f2618f73
Merge pull request #3620 from benoitc/fix/asgi-proxy-protocol-trust-and-parsing
fix: enforce proxy_allow_ips and tighten PROXY parsing in ASGI
2026-05-03 22:56:03 +02:00
Benoit Chesneau
41ec7527db fix: keep Content-Length on HEAD and 304 responses
RFC 9110 §6.4.2 forbids Content-Length only on 1xx and 204 responses.
HEAD MAY include the Content-Length the same GET would return, and 304
MAY include the Content-Length the unconditional response would carry.
WSGI preserves app-supplied Content-Length on those statuses; ASGI was
stripping it indiscriminately for any no-body response.

Split the predicate: _response_forbids_content_length() returns True
only for 1xx/204; _strip_body_framing_headers(headers, status) always
strips Transfer-Encoding (no body, no chunked terminator) and strips
Content-Length only when forbidden.
2026-05-03 22:32:28 +02:00
Benoit Chesneau
112d5353c1 fix: enforce proxy_allow_ips and tighten PROXY parsing in ASGI
Three findings against the ASGI PROXY protocol path:

- High: an untrusted peer could send a PROXY v1/v2 header and have the
  client address surfaced to the app. _setup_callback_parser now passes
  proxy_protocol='off' to the parser when the peer is not in
  proxy_allow_ips. _effective_peername adds a defensive re-check.
- Medium: PROXY v1 TCP4/TCP6 addresses were copied as strings without
  validation. Validate with socket.inet_pton, mirroring the WSGI parser.
- Medium: PROXY v2 quietly mapped non-STREAM (DGRAM) protocols to
  UDP4/UDP6. gunicorn is an HTTP server; reject non-STREAM with
  InvalidProxyHeader, mirroring the WSGI parser.
2026-05-03 22:28:48 +02:00
Benoit Chesneau
9902bc761c
Merge pull request #3619 from benoitc/test/asgi-pipelined-smuggling-fast-parser
test: parametrize smuggling regression across python and fast parsers
2026-05-03 21:25:30 +02:00
Benoit Chesneau
35c6a2abef test: parametrize smuggling regression across python and fast parsers
The previous test forced http_parser='python' to avoid a hard
dependency on gunicorn_h1c. Now run the same scenario under both
parser implementations so the smuggling guard is exercised on every
supported request-line/header path.
2026-05-03 21:18:15 +02:00
Benoit Chesneau
4e4a60ee77
Merge pull request #3618 from benoitc/test/asgi-pipelined-smuggling-regression
fix: keep _body_receiver alive across the keepalive smuggling gate
2026-05-03 21:15:14 +02:00
Benoit Chesneau
0a736ea4a2 fix: keep _body_receiver alive across the keepalive smuggling gate
The smuggling guard added in #3614 reads self._body_receiver after
_handle_http_request returns to refuse keepalive on a body that did
not finish framing.  But _handle_http_request's finally cleared the
receiver before returning, so the gate always saw None and let
keepalive proceed unconditionally.  Move the clear into the
connection loop's per-iteration cleanup (it already had one there
for the same purpose).

Adds an end-to-end regression test that pipelines a partial-body POST
followed by a smuggled GET and asserts the second request is not
served and the transport closes.
2026-05-03 21:03:49 +02:00
Benoit Chesneau
8d9d9030ff
Merge pull request #3617 from benoitc/fix/asgi-bodyreceiver-closed-semantic-split
refactor: split BodyReceiver._closed into transport vs body-wait
2026-05-03 20:53:20 +02:00
Benoit Chesneau
fe3655b9d3 refactor: split BodyReceiver._closed into transport vs body-wait
_closed now means the client transport has gone away. Body-wait timeouts
flip a separate _body_wait_expired flag. Both still surface as
http.disconnect to the app, but downstream code can now distinguish 'the
socket is dead' from 'the body never finished framing in time' without
guessing which path set the flag.
2026-05-03 20:42:55 +02:00
Benoit Chesneau
02c53cfcfc
Merge pull request #3616 from benoitc/fix/asgi-protocol-review-followups
fix: drop body framing on HEAD/204/304 even when framework set it
2026-05-03 20:38:57 +02:00
Benoit Chesneau
0d35d2ae44 fix: warn once when ASGI app emits a body for a no-body response
A framework bug — say, returning bytes from a HEAD or 204 handler — is
now logged at WARNING level the first time it happens for a request
so the misbehavior is visible without spamming on multi-chunk streams.
2026-05-03 20:25:23 +02:00
Benoit Chesneau
2191832b8d fix: drop body framing on HEAD/204/304 even when framework set it
RFC 9110 forbids a body for HEAD requests and for 1xx/204/304 status
codes. PR #3614 stopped gunicorn from auto-applying chunked encoding
in those cases, but if the application explicitly emitted a
Content-Length or Transfer-Encoding header (and possibly body bytes),
gunicorn still passed them through. Now strip both headers, force
plain framing, and discard any body the app emits.
2026-05-03 20:17:00 +02:00
Benoit Chesneau
1cbe7d189b
Merge pull request #3614 from benoitc/fix/parser-protocol-findings
fix: address six WSGI/ASGI parser and protocol findings
2026-05-03 20:12:20 +02:00
Benoit Chesneau
d6443e5a6e test: poll for control socket re-creation after SIGHUP
The previous assertion ran immediately after a 2s sleep and raced
the arbiter's socket re-creation on slow runners (observed flake on
FreeBSD 14.2 / Python 3.13). Replace with the wait_for_socket helper
already used elsewhere in the file.
2026-05-03 19:13:40 +02:00
Benoit Chesneau
8e25cb2400 fix: tighten keepalive gate and scope finish_body byte cap
- ASGI keepalive gate now keys on receiver._complete only. _closed is
  overloaded across transport disconnect and receive timeout; treating
  either as 'message complete' would re-enable the smuggling vector
  the previous PR was meant to close.
- Parser.finish_body's 64 KiB byte cap now applies only when an explicit
  deadline is given. Default invocations (notably __next__, used by
  base_async / sync workers) regain the prior unbounded drain so a
  partial drain does not silently desync the next request.
2026-05-03 18:37:45 +02:00
Benoit Chesneau
6f9ed30d23 lint: use dict literal and hoist mock import 2026-05-03 18:23:45 +02:00
Benoit Chesneau
e90b1c2c1e fix: address six WSGI/ASGI parser and protocol findings
- WSGI fast parser now applies the same per-header policy as the Python
  parser (Expect, secure_scheme_headers, forwarded_allow_ips trust gate,
  forwarder_headers / header_map). Shared helpers extracted on Message.

- ASGI keepalive no longer resets the parser when the previous request
  body was not fully framed; the connection closes instead, preventing
  request smuggling on pipelined connections.

- BodyReceiver._wait_for_data timeout flips _closed and yields
  http.disconnect rather than synthesizing more_body=False. Timeout
  honors cfg.timeout.

- ASGI chunked encoding now skips HEAD, 204, and 304 (matches
  Response.is_chunked in the WSGI path) via a small helper.

- _setup_callback_parser passes proxy_protocol to PythonProtocol; auto
  falls back to the Python parser when proxy_protocol != off (the C
  parser does not implement PROXY framing). _effective_peername swaps
  the transport peer with the PROXY-supplied client address.

- Parser.finish_body accepts a deadline and a 64KiB byte cap; gthread
  passes a deadline and abandons keepalive on incomplete drain so a
  stalled client cannot tie up a worker thread.
2026-05-03 18:19:08 +02:00
Matt Cengia
80983a8616
Looks like this .md crept in as part of the MkDocs migration
Original line: https://github.com/benoitc/gunicorn/blob/f9df39f/docs/source/run.rst?plain=1#L43
New line: https://github.com/benoitc/gunicorn/blob/19a2efe/docs/content/run.md?plain=1#L38
2026-04-30 10:02:29 +10:00
Benoit Chesneau
4bcda32a78
Merge pull request #3604 from benoitc/chore/drop-last-python-only-after-h1c-0.6.5
chore: require gunicorn_h1c >=0.6.5 and drop last python_only marker
2026-04-20 10:35:08 +02:00
Benoit Chesneau
98eac0b04e chore: require gunicorn_h1c >=0.6.5 and drop last python_only marker
gunicorn_h1c 0.6.5 ships the Content-Length list-form rejection
(h1c #8). The last python_only marker can now come off
rfc9112_smuggle_cl_list_form_01.
2026-04-20 07:29:47 +02:00
Benoit Chesneau
3af35da8c7
Merge pull request #3603 from benoitc/chore/drop-python-only-after-h1c-0.6.4
chore: require gunicorn_h1c >=0.6.4 and drop python_only markers
2026-04-20 07:18:52 +02:00
Benoit Chesneau
377e8f81f9 test: skip fast parser on PyPy (gunicorn_h1c C extension is CPython-only) 2026-04-19 23:48:18 +02:00
Benoit Chesneau
408b1f0517 chore: require gunicorn_h1c >=0.6.4 and drop python_only markers
gunicorn_h1c 0.6.4 ships the RFC 9110/9112 hardening added in h1c #4,
#6, and #7: control chars in header values, request-target form/method
pairing, and forbidden trailer field-names. All the corresponding
fixtures now pass against the C parser, so their python_only markers
are removed.

The CL list form fixture stays marked — the C parser does not yet
reject Content-Length: "5, 5".
2026-04-19 23:40:58 +02:00
Benoit Chesneau
a635b957c5
Merge pull request #3602 from benoitc/test/rfc9112-cl-list-form-fixture
test: codify rejection of Content-Length list form (RFC 9112 §6.3)
2026-04-19 23:30:15 +02:00
Benoit Chesneau
73e64364ca test: codify rejection of Content-Length list form (RFC 9112 section 6.3) 2026-04-19 20:49:32 +02:00
Benoit Chesneau
4da46edac0
Merge pull request #3601 from benoitc/test/rfc9110-body-framing-fixtures
test: codify body-framing cases (RFC 9110 §8.6 & RFC 9112 §6.1)
2026-04-19 20:46:25 +02:00
Benoit Chesneau
8450ae0d10 test: codify body-framing cases (RFC 9110 section 8.6 and RFC 9112 section 6.1) 2026-04-19 20:32:23 +02:00
Benoit Chesneau
fad75c258d
Merge pull request #3600 from benoitc/test/rfc9110-field-syntax-fixtures
test: codify field-syntax cases (RFC 9110 §5.5 & §5.6.2)
2026-04-19 17:47:57 +02:00
Benoit Chesneau
e223e302af test: codify field-syntax cases (RFC 9110 section 5.5 and 5.6.2) 2026-04-19 14:02:58 +02:00
Benoit Chesneau
5d0f1e9b15
Merge pull request #3599 from benoitc/test/rfc9112-chunked-edge-cases
test: codify chunked size/extension edge cases (RFC 9112 §7.1)
2026-04-19 13:59:45 +02:00
Benoit Chesneau
2391901b40 test: codify chunked size/extension edge cases (RFC 9112 section 7.1) 2026-04-19 13:21:20 +02:00
Benoit Chesneau
dac3fa8497
Merge pull request #3598 from benoitc/fix/rfc9110-reject-control-chars-in-header-value
fix: reject control characters in header field-value (RFC 9110 §5.5)
2026-04-19 13:17:56 +02:00
Benoit Chesneau
2073e13dc8 fix: reject control characters in header field-value (RFC 9110 section 5.5)
field-vchar = VCHAR / obs-text; only SP and HTAB are permitted beyond
that. Previous validation only caught NUL/CR/LF, leaving BEL, DEL, FF,
and other C0/C1 controls accepted — a log/response injection risk. Now
rejected across the WSGI and ASGI Python parsers.
2026-04-19 12:07:16 +02:00
Benoit Chesneau
826bfc7e88 test: add failing fixtures for control chars in header value 2026-04-19 12:05:00 +02:00
Benoit Chesneau
9f7f930a81
Merge pull request #3597 from benoitc/fix/rfc9110-forbidden-trailer-fields
fix: reject forbidden trailer field-names (RFC 9110 §6.5.1)
2026-04-19 12:04:06 +02:00
Benoit Chesneau
a9270e3f9a fix: reject forbidden trailer field-names (RFC 9110 section 6.5.1)
Host, Content-Length, Transfer-Encoding, Trailer, Authorization, and TE
are not allowed in trailer sections; accepting them enables smuggling
and routing confusion. Both WSGI and ASGI Python parsers now raise
InvalidHeaderName when any of these appears in a trailer.
2026-04-19 11:41:00 +02:00
Benoit Chesneau
3b3752eb90 test: add failing fixtures for forbidden trailer fields 2026-04-19 11:38:05 +02:00
Benoit Chesneau
ba8776d3fc
Merge pull request #3596 from benoitc/test/rfc9112-relative-target-fixture
test: codify rejection of relative-reference request-target (RFC 9112 §3.2)
2026-04-19 11:36:57 +02:00
Benoit Chesneau
62252223e0 test: codify rejection of relative-reference request-target (RFC 9112 section 3.2) 2026-04-19 11:23:00 +02:00
Benoit Chesneau
37771e8a44
Merge pull request #3595 from benoitc/fix/rfc9112-reject-authority-form-non-connect
fix: reject authority-form request-target outside CONNECT (RFC 9112 §3.2.3)
2026-04-19 11:22:11 +02:00
Benoit Chesneau
882e636208 fix: reject authority-form request-target outside CONNECT (RFC 9112 section 3.2.3)
Detect authority-form as a request-target that is neither origin-form
(starts with "/"), absolute-form (contains "://"), nor asterisk; reject
it for any method other than CONNECT. Both WSGI and ASGI Python parsers.
2026-04-19 11:11:42 +02:00
Benoit Chesneau
e7fd6a104f test: add failing fixture for authority-form with non-CONNECT method 2026-04-19 11:09:29 +02:00