Update dependency gunicorn to v26 #606

Open
Renovate wants to merge 1 commit from renovate/gunicorn-26.x into main
Collaborator

This PR contains the following updates:

Package Type Update Change
gunicorn (changelog) dependencies major ^25.0.0^26.0.0

Release Notes

benoitc/gunicorn (gunicorn)

v26.2.0: gunicorn 26.2.0

Compare Source

Cleartext HTTP/2 lands, and an HTTP/2 security fix.

Cleartext HTTP/2 (h2c)

http2_cleartext accepts prior-knowledge, upgrade, both or off (the
default). Prior knowledge serves a connection that opens with the HTTP/2
preface; upgrade honours an HTTP/1.1 Upgrade: h2c request. Both work on the
gthread, gevent and asgi workers.

This is for deployments where TLS is terminated by a proxy that speaks HTTP/2
upstream, so the hop into gunicorn no longer drops to HTTP/1.1. Only peers in
forwarded_allow_ips are considered; everyone else is served HTTP/1.x exactly
as if the setting were off. Each mechanism is enabled separately, so turning one
on does not turn the other on.

Do not expose a cleartext HTTP/2 port to the internet.

Security

HTTP2Request built its headers straight from the stream, so nothing the HTTP/1
path enforces applied over HTTP/2: the underscore and header_map policy,
duplicate Host and Content-Type, control characters in values, and the
forwarded_allow_ips trust gate. An untrusted client could set SCRIPT_NAME
and forge HTTP_* entries in the WSGI environ, and decide wsgi.url_scheme
through :scheme. Both request classes now share one policy mixin, and the
scheme comes from the transport.

If you serve HTTP/2, this is the reason to upgrade.

Other HTTP/2 fixes

WSGI responses were buffered whole before anything was sent; they stream now.
HEAD, 204 and 304 no longer carry a body. Events read while blocked on a
flow-control window were discarded, losing requests and body data outright.
sendfile() is refused on HTTP/2 responses rather than bypassing framing.

Request bodies dropped on Upgrade requests

On the ASGI worker with the fast parser, any request carrying an Upgrade
header reached the application with an empty body, whatever the header's value
and with HTTP/2 switched off entirely. Fixed in gunicorn_h1c 0.6.9, which the
fast extra now requires.

Full changelog: https://gunicorn.org/news/

v26.1.0: gunicorn 26.1.0

Compare Source

New Features
  • Glob patterns in reload_extra_files: entries containing *, ? or [
    are treated as patterns, so ui/*/config.json watches every view's config
    without listing them one by one. Patterns are re-expanded on every reload
    check rather than once at startup, so a file created later starts being
    watched without restarting gunicorn, and ** recurses. A pattern matching
    nothing warns instead of failing, since with live expansion it may match later
    (#​1643,
    #​3662).
Security
  • Dependency floors raised past known advisories: every declared floor was
    checked against the advisory database. tornado, h2, setuptools and
    pymdown-extensions permitted vulnerable versions and now require the first
    clean release; pytest and httpx were unpinned and now carry floors. The
    tornado example pinned tornado<6, which was both the source of several
    advisories and older than the >=6.5.0 the tornado worker needs, so the
    example could not run as pinned.
Bug Fixes
  • SIGHUP did not reload the logger configuration: Arbiter.reload()
    re-read the configuration file but kept using the logger built at startup,
    calling only reopen_files() on its existing handlers. Changes to
    logconfig, logconfig_dict, logconfig_json and loglevel were ignored
    until a full restart, which in containers meant replacing the pod. The
    existing logger now re-runs its setup on reload, so new handlers, formats
    and levels take effect while the process identity and its listeners are
    preserved, and re-running the setup no longer stacks duplicate syslog
    handlers. An invalid log configuration on reload is not fatal either: the
    error is reported on stderr, the previous working configuration is restored
    and the master keeps running with it
    (#​3353).

  • Truncated chunked bodies accepted: RFC 9112 section 7.1.2 ends a chunked
    body with 0 CRLF CRLF, the second CRLF being the mandatory empty trailer
    section. ChunkedReader.parse_chunk_size() swallowed the NoMoreData raised
    while scanning for it, so a body cut short right after the last chunk line was
    treated as complete instead of rejected. It now raises
    ChunkMissingTerminator
    (#​3382,
    #​3685).

  • --spew crashed on dynamically generated code: the trace hook indexed the
    2-tuple returned by inspect.getsourcelines() by line number rather than
    indexing the list of lines, so a frame with no __file__ raised
    AttributeError: 'int' object has no attribute 'rstrip' on line 1 and
    IndexError beyond it. The tuple is now unpacked and offset by the source's
    starting line (#​3344,
    #​3495).

  • Duplicate Host and Content-Type headers accepted: RFC 9110 section 5.3
    allows only one of each, and a repeat cannot be merged into a list, so the
    message means different things to gunicorn and to anything downstream. Both
    are now rejected with InvalidHeader. The check lives in the policy hook
    shared by both parsers, so the pure-Python and fast parsers agree. Duplicate
    Content-Length was already rejected and is unchanged
    (#​3366,
    #​3548).

  • Non-worker children reported as failed workers: reap_workers() reaps
    every child through waitpid(-1), including processes the kernel reparented
    onto gunicorn when it runs as PID 1 in a container, but it logged the exit
    status before checking whether the pid was ever a worker. An unrelated process
    produced Worker (pid:N) exited with code M and triggered alerts. More
    seriously, such a process exiting with code 3 or 4 raised HaltServer and shut
    the server down. Ownership is now established first: the dirty arbiter is
    reported as itself, unknown children are reaped silently at debug level, and
    only real workers can halt the server
    (#​3220,
    #​3566).

  • Dirty arbiter exits were invisible on SIGCHLD: handle_chld() called
    reap_workers() first, whose waitpid(-1) claimed the dirty arbiter before
    reap_dirty_arbiter() could identify it, so the latter always hit ECHILD and
    its reporting never ran. The dirty arbiter is now reaped first, and
    reap_workers() recognises it if it exits mid-loop.

  • Dirty arbiter returned stale responses after a worker timeout: when a
    request reached dirty_timeout the arbiter answered the client with a timeout
    error but kept the worker connection open. The worker's late response was then
    the first message waiting on that socket, so the next request routed to the
    same worker received the previous request's result, and every request after it
    stayed one response behind. The connection is now closed on timeout, so the
    late answer is discarded with it
    (#​3626).

  • ASGI connection count leaked on server-initiated close: nr_conns was
    only decremented in connection_lost(), behind a guard keyed on the same
    flag _close_transport() sets first. Every close the server started (a
    Connection: close response, a keepalive timeout, an error abort) leaked one
    count, so ASGIWorker._shutdown() ran the full graceful_timeout and warned
    about connections that were already gone. The guard now uses its own flag, so
    the decrement and the rest of the cleanup run exactly once whichever side
    closes first (#​3661).

  • Inotify reloader on cwd-relative extra files: reload_extra_files entries
    with no directory part (for example .env) produced an empty dirname, and
    watching it raised InotifyError with ENOENT. The current directory is now
    watched as . (#​3377,
    #​3667).

  • StatsD zero-valued metrics: gauges, counters, histograms and timers
    reporting 0 were silently dropped because the value was tested for
    truthiness. Only None is skipped now
    (#​3676).

  • Spurious no-body warning from sendfile(): a HEAD, 204 or 304 response
    served through sendfile() warned about dropped body bytes even when the
    file was empty and nothing was dropped. It now warns only when there are
    bytes to drop, matching write()
    (#​3684).

  • Bare except in the gevent websocket example: narrowed to
    except Exception (#​3683).

  • ASGI receive() cancellation: Let asyncio.CancelledError propagate
    from BodyReceiver instead of swallowing it and returning
    http.disconnect. Frameworks that cancel their disconnect listener after
    the response completes (Django) no longer see the cancel masked, so
    request_finished fires and close_old_connections() runs. Fixes idle
    database connections leaking since 25.1.0
    (#​3627,
    #​3654).

  • Control socket leak on SIGHUP reload: The control thread is now marked
    ready once its loop and server are live, and the stop paths wait on that
    readiness before scheduling shutdown. Reloads no longer leak one thread and
    its selector fd plus unix socket per worker, which eventually raised
    "too many open files"
    (#​3648).

  • WSGI body framing on HEAD/1xx/204/304: Mirror the ASGI strip-and-warn
    behavior on the WSGI path. Content-Length is stripped on 1xx/204 per
    RFC 9110 section 6.4.2, body bytes are dropped for no-body responses in
    both write() and sendfile(), and a single warning is logged per request
    (#​3413).

Refactoring
  • Pass log arguments to the logger instead of pre-formatting the worker
    termination message in Arbiter.reap_workers()
    (#​3678).
Changes
  • packaging is no longer a runtime dependency: it was only ever imported by
    the gevent worker, to compare gevent's version. It moved to the gevent and
    testing extras, so a plain pip install gunicorn pulls in nothing
    (#​3643).

  • Fast HTTP Parser: Require gunicorn_h1c >= 0.6.6, which rejects duplicate
    Host and Content-Type headers in the C parser itself. Gunicorn already
    refuses them on both the WSGI and ASGI paths, so this changes nothing that is
    reachable; it moves the rejection to where the bytes are read and lets the
    ASGI corpus exercise those cases against the fast parser directly.

Full changelog: https://gunicorn.org/2026-news/

v26.0.0

Compare Source

Breaking Changes

  • Eventlet worker removed: The eventlet worker class has been dropped. Migrate to gevent, gthread, or tornado.

New Features

  • ASGI Framework Compatibility Suite: New end-to-end compatibility test harness covering Starlette, FastAPI, Litestar, Quart, Sanic, and BlackSheep. Current grid passes 438/444 tests (98%).
  • ASGI Test Suite Expansion: 134 additional ASGI unit tests covering protocol semantics, lifespan, websockets, and chunked framing.

Security

  • HTTP/1.1 Request-Target Validation (RFC 9112 sections 3.2.3, 3.2.4):
    • Reject authority-form request-target outside CONNECT
    • Reject asterisk-form request-target outside OPTIONS
    • Reject relative-reference request-targets
  • Header Field Hardening (RFC 9110):
    • Reject control characters in header field-value (section 5.5)
    • Reject forbidden trailer field-names (section 6.5.1)
    • Reject Content-Length list form (RFC 9112 section 6.3)
  • Request Smuggling Hardening:
    • Tighten keepalive gate and scope finish_body byte cap
    • Keep _body_receiver alive across the keepalive smuggling gate so pipelined requests cannot re-enter a closed body
    • Address parser/protocol findings from a six-point WSGI/ASGI audit
  • PROXY Protocol (ASGI): Enforce proxy_allow_ips and tighten v1/v2 parsing in the ASGI callback parser.
  • Connection Draining: Drain the connection on close per RFC 9112 section 9.6 to prevent reset-on-close truncation.

Bug Fixes

  • Body Framing on HEAD/204/304:
    • Keep Content-Length on HEAD and 304 responses (#​3621)
    • Drop body framing on HEAD/204/304 even when the framework set it
    • Warn once when an ASGI app emits a body for a no-body response
  • HTTP/2 ASGI:
    • Fix _handle_stream_ended to set _body_complete in the async HTTP/2 handler so request bodies finalize correctly on stream end
    • Add InvalidChunkExtension mapping and fast-parser support in ASGI tests (#​3565)
  • HTTP/1.1 100-Continue: Stop adding Transfer-Encoding: chunked to 100-Continue interim responses.
  • WebSocket Close Handshake (RFC 6455):
    • Comply with the close handshake state machine
    • Close the transport after the close handshake completes
    • Fix binary send when the text key is None
  • Early Hints: Validate headers in the early_hints callback to match process_headers; pass only the header name to InvalidHeader (#​3588).
  • ASGI Framework Fixes:
    • Fix ASGI disconnect handling for Django-style apps
    • Fix Litestar request handling (use raw ASGI receive for body/headers)
    • Fix Litestar HTTP endpoints for compatibility tests
    • Fix Quart headers endpoint to normalize keys to lowercase
    • Fix Quart WebSocket close test app (missing accept())
    • Fix duplicate Transfer-Encoding header for BlackSheep streaming

Refactoring

  • Split BodyReceiver._closed into separate transport and body-wait flags for clearer keepalive/EOF semantics.

Changes

  • Fast HTTP Parser: Require gunicorn_h1c >= 0.6.5. Drop the last python_only test markers; the C extension is now used wherever available (CPython only; PyPy continues to use the Python parser).
  • Test Dependencies: Add h2 and uvloop to the testing extra; remove eventlet.
  • Docker Build: Bump GitHub Actions docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/build-push-action, and docker/metadata-action to current major versions.

Full changelog: https://github.com/benoitc/gunicorn/compare/25.3.0...26.0.0


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate CLI.

This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [gunicorn](https://github.com/benoitc/gunicorn) ([changelog](https://gunicorn.org/news/)) | dependencies | major | `^25.0.0` → `^26.0.0` | --- ### Release Notes <details> <summary>benoitc/gunicorn (gunicorn)</summary> ### [`v26.2.0`](https://github.com/benoitc/gunicorn/releases/tag/26.2.0): gunicorn 26.2.0 [Compare Source](https://github.com/benoitc/gunicorn/compare/26.1.0...26.2.0) Cleartext HTTP/2 lands, and an HTTP/2 security fix. #### Cleartext HTTP/2 (h2c) `http2_cleartext` accepts `prior-knowledge`, `upgrade`, `both` or `off` (the default). Prior knowledge serves a connection that opens with the HTTP/2 preface; `upgrade` honours an HTTP/1.1 `Upgrade: h2c` request. Both work on the gthread, gevent and asgi workers. This is for deployments where TLS is terminated by a proxy that speaks HTTP/2 upstream, so the hop into gunicorn no longer drops to HTTP/1.1. Only peers in `forwarded_allow_ips` are considered; everyone else is served HTTP/1.x exactly as if the setting were off. Each mechanism is enabled separately, so turning one on does not turn the other on. Do not expose a cleartext HTTP/2 port to the internet. #### Security `HTTP2Request` built its headers straight from the stream, so nothing the HTTP/1 path enforces applied over HTTP/2: the underscore and `header_map` policy, duplicate `Host` and `Content-Type`, control characters in values, and the `forwarded_allow_ips` trust gate. An untrusted client could set `SCRIPT_NAME` and forge `HTTP_*` entries in the WSGI environ, and decide `wsgi.url_scheme` through `:scheme`. Both request classes now share one policy mixin, and the scheme comes from the transport. If you serve HTTP/2, this is the reason to upgrade. #### Other HTTP/2 fixes WSGI responses were buffered whole before anything was sent; they stream now. HEAD, 204 and 304 no longer carry a body. Events read while blocked on a flow-control window were discarded, losing requests and body data outright. `sendfile()` is refused on HTTP/2 responses rather than bypassing framing. #### Request bodies dropped on Upgrade requests On the ASGI worker with the fast parser, any request carrying an `Upgrade` header reached the application with an empty body, whatever the header's value and with HTTP/2 switched off entirely. Fixed in `gunicorn_h1c` 0.6.9, which the `fast` extra now requires. Full changelog: <https://gunicorn.org/news/> ### [`v26.1.0`](https://github.com/benoitc/gunicorn/releases/tag/26.1.0): gunicorn 26.1.0 [Compare Source](https://github.com/benoitc/gunicorn/compare/26.0.0...26.1.0) ##### New Features - **Glob patterns in `reload_extra_files`**: entries containing `*`, `?` or `[` are treated as patterns, so `ui/*/config.json` watches every view's config without listing them one by one. Patterns are re-expanded on every reload check rather than once at startup, so a file created later starts being watched without restarting gunicorn, and `**` recurses. A pattern matching nothing warns instead of failing, since with live expansion it may match later ([#&#8203;1643](https://github.com/benoitc/gunicorn/issues/1643), [#&#8203;3662](https://github.com/benoitc/gunicorn/pull/3662)). ##### Security - **Dependency floors raised past known advisories**: every declared floor was checked against the advisory database. `tornado`, `h2`, `setuptools` and `pymdown-extensions` permitted vulnerable versions and now require the first clean release; `pytest` and `httpx` were unpinned and now carry floors. The `tornado` example pinned `tornado<6`, which was both the source of several advisories and older than the `>=6.5.0` the tornado worker needs, so the example could not run as pinned. ##### Bug Fixes - **SIGHUP did not reload the logger configuration**: `Arbiter.reload()` re-read the configuration file but kept using the logger built at startup, calling only `reopen_files()` on its existing handlers. Changes to `logconfig`, `logconfig_dict`, `logconfig_json` and `loglevel` were ignored until a full restart, which in containers meant replacing the pod. The existing logger now re-runs its setup on reload, so new handlers, formats and levels take effect while the process identity and its listeners are preserved, and re-running the setup no longer stacks duplicate syslog handlers. An invalid log configuration on reload is not fatal either: the error is reported on stderr, the previous working configuration is restored and the master keeps running with it ([#&#8203;3353](https://github.com/benoitc/gunicorn/issues/3353)). - **Truncated chunked bodies accepted**: RFC 9112 section 7.1.2 ends a chunked body with `0 CRLF CRLF`, the second CRLF being the mandatory empty trailer section. `ChunkedReader.parse_chunk_size()` swallowed the `NoMoreData` raised while scanning for it, so a body cut short right after the last chunk line was treated as complete instead of rejected. It now raises `ChunkMissingTerminator` ([#&#8203;3382](https://github.com/benoitc/gunicorn/issues/3382), [#&#8203;3685](https://github.com/benoitc/gunicorn/pull/3685)). - **`--spew` crashed on dynamically generated code**: the trace hook indexed the 2-tuple returned by `inspect.getsourcelines()` by line number rather than indexing the list of lines, so a frame with no `__file__` raised `AttributeError: 'int' object has no attribute 'rstrip'` on line 1 and `IndexError` beyond it. The tuple is now unpacked and offset by the source's starting line ([#&#8203;3344](https://github.com/benoitc/gunicorn/issues/3344), [#&#8203;3495](https://github.com/benoitc/gunicorn/pull/3495)). - **Duplicate `Host` and `Content-Type` headers accepted**: RFC 9110 section 5.3 allows only one of each, and a repeat cannot be merged into a list, so the message means different things to gunicorn and to anything downstream. Both are now rejected with `InvalidHeader`. The check lives in the policy hook shared by both parsers, so the pure-Python and fast parsers agree. Duplicate `Content-Length` was already rejected and is unchanged ([#&#8203;3366](https://github.com/benoitc/gunicorn/issues/3366), [#&#8203;3548](https://github.com/benoitc/gunicorn/pull/3548)). - **Non-worker children reported as failed workers**: `reap_workers()` reaps every child through `waitpid(-1)`, including processes the kernel reparented onto gunicorn when it runs as PID 1 in a container, but it logged the exit status before checking whether the pid was ever a worker. An unrelated process produced `Worker (pid:N) exited with code M` and triggered alerts. More seriously, such a process exiting with code 3 or 4 raised `HaltServer` and shut the server down. Ownership is now established first: the dirty arbiter is reported as itself, unknown children are reaped silently at debug level, and only real workers can halt the server ([#&#8203;3220](https://github.com/benoitc/gunicorn/issues/3220), [#&#8203;3566](https://github.com/benoitc/gunicorn/pull/3566)). - **Dirty arbiter exits were invisible on SIGCHLD**: `handle_chld()` called `reap_workers()` first, whose `waitpid(-1)` claimed the dirty arbiter before `reap_dirty_arbiter()` could identify it, so the latter always hit `ECHILD` and its reporting never ran. The dirty arbiter is now reaped first, and `reap_workers()` recognises it if it exits mid-loop. - **Dirty arbiter returned stale responses after a worker timeout**: when a request reached `dirty_timeout` the arbiter answered the client with a timeout error but kept the worker connection open. The worker's late response was then the first message waiting on that socket, so the next request routed to the same worker received the previous request's result, and every request after it stayed one response behind. The connection is now closed on timeout, so the late answer is discarded with it ([#&#8203;3626](https://github.com/benoitc/gunicorn/pull/3626)). - **ASGI connection count leaked on server-initiated close**: `nr_conns` was only decremented in `connection_lost()`, behind a guard keyed on the same flag `_close_transport()` sets first. Every close the server started (a `Connection: close` response, a keepalive timeout, an error abort) leaked one count, so `ASGIWorker._shutdown()` ran the full `graceful_timeout` and warned about connections that were already gone. The guard now uses its own flag, so the decrement and the rest of the cleanup run exactly once whichever side closes first ([#&#8203;3661](https://github.com/benoitc/gunicorn/issues/3661)). - **Inotify reloader on cwd-relative extra files**: `reload_extra_files` entries with no directory part (for example `.env`) produced an empty dirname, and watching it raised `InotifyError` with `ENOENT`. The current directory is now watched as `.` ([#&#8203;3377](https://github.com/benoitc/gunicorn/issues/3377), [#&#8203;3667](https://github.com/benoitc/gunicorn/pull/3667)). - **StatsD zero-valued metrics**: gauges, counters, histograms and timers reporting `0` were silently dropped because the value was tested for truthiness. Only `None` is skipped now ([#&#8203;3676](https://github.com/benoitc/gunicorn/pull/3676)). - **Spurious no-body warning from `sendfile()`**: a HEAD, 204 or 304 response served through `sendfile()` warned about dropped body bytes even when the file was empty and nothing was dropped. It now warns only when there are bytes to drop, matching `write()` ([#&#8203;3684](https://github.com/benoitc/gunicorn/pull/3684)). - **Bare `except` in the gevent websocket example**: narrowed to `except Exception` ([#&#8203;3683](https://github.com/benoitc/gunicorn/pull/3683)). - **ASGI `receive()` cancellation**: Let `asyncio.CancelledError` propagate from `BodyReceiver` instead of swallowing it and returning `http.disconnect`. Frameworks that cancel their disconnect listener after the response completes (Django) no longer see the cancel masked, so `request_finished` fires and `close_old_connections()` runs. Fixes idle database connections leaking since 25.1.0 ([#&#8203;3627](https://github.com/benoitc/gunicorn/issues/3627), [#&#8203;3654](https://github.com/benoitc/gunicorn/pull/3654)). - **Control socket leak on SIGHUP reload**: The control thread is now marked ready once its loop and server are live, and the stop paths wait on that readiness before scheduling shutdown. Reloads no longer leak one thread and its selector fd plus unix socket per worker, which eventually raised "too many open files" ([#&#8203;3648](https://github.com/benoitc/gunicorn/issues/3648)). - **WSGI body framing on HEAD/1xx/204/304**: Mirror the ASGI strip-and-warn behavior on the WSGI path. `Content-Length` is stripped on 1xx/204 per RFC 9110 section 6.4.2, body bytes are dropped for no-body responses in both `write()` and `sendfile()`, and a single warning is logged per request ([#&#8203;3413](https://github.com/benoitc/gunicorn/issues/3413)). ##### Refactoring - Pass log arguments to the logger instead of pre-formatting the worker termination message in `Arbiter.reap_workers()` ([#&#8203;3678](https://github.com/benoitc/gunicorn/pull/3678)). ##### Changes - **`packaging` is no longer a runtime dependency**: it was only ever imported by the gevent worker, to compare gevent's version. It moved to the `gevent` and `testing` extras, so a plain `pip install gunicorn` pulls in nothing ([#&#8203;3643](https://github.com/benoitc/gunicorn/pull/3643)). - **Fast HTTP Parser**: Require `gunicorn_h1c >= 0.6.6`, which rejects duplicate `Host` and `Content-Type` headers in the C parser itself. Gunicorn already refuses them on both the WSGI and ASGI paths, so this changes nothing that is reachable; it moves the rejection to where the bytes are read and lets the ASGI corpus exercise those cases against the fast parser directly. Full changelog: <https://gunicorn.org/2026-news/> ### [`v26.0.0`](https://github.com/benoitc/gunicorn/releases/tag/26.0.0) [Compare Source](https://github.com/benoitc/gunicorn/compare/25.3.0...26.0.0) #### Breaking Changes - **Eventlet worker removed**: The `eventlet` worker class has been dropped. Migrate to `gevent`, `gthread`, or `tornado`. #### New Features - **ASGI Framework Compatibility Suite**: New end-to-end compatibility test harness covering Starlette, FastAPI, Litestar, Quart, Sanic, and BlackSheep. Current grid passes 438/444 tests (98%). - **ASGI Test Suite Expansion**: 134 additional ASGI unit tests covering protocol semantics, lifespan, websockets, and chunked framing. #### Security - **HTTP/1.1 Request-Target Validation** (RFC 9112 sections 3.2.3, 3.2.4): - Reject `authority-form` request-target outside `CONNECT` - Reject `asterisk-form` request-target outside `OPTIONS` - Reject `relative-reference` request-targets - **Header Field Hardening** (RFC 9110): - Reject control characters in header field-value (section 5.5) - Reject forbidden trailer field-names (section 6.5.1) - Reject `Content-Length` list form (RFC 9112 section 6.3) - **Request Smuggling Hardening**: - Tighten keepalive gate and scope `finish_body` byte cap - Keep `_body_receiver` alive across the keepalive smuggling gate so pipelined requests cannot re-enter a closed body - Address parser/protocol findings from a six-point WSGI/ASGI audit - **PROXY Protocol (ASGI)**: Enforce `proxy_allow_ips` and tighten v1/v2 parsing in the ASGI callback parser. - **Connection Draining**: Drain the connection on close per RFC 9112 section 9.6 to prevent reset-on-close truncation. #### Bug Fixes - **Body Framing on HEAD/204/304**: - Keep `Content-Length` on HEAD and 304 responses ([#&#8203;3621](https://github.com/benoitc/gunicorn/issues/3621)) - Drop body framing on HEAD/204/304 even when the framework set it - Warn once when an ASGI app emits a body for a no-body response - **HTTP/2 ASGI**: - Fix `_handle_stream_ended` to set `_body_complete` in the async HTTP/2 handler so request bodies finalize correctly on stream end - Add `InvalidChunkExtension` mapping and fast-parser support in ASGI tests ([#&#8203;3565](https://github.com/benoitc/gunicorn/issues/3565)) - **HTTP/1.1 100-Continue**: Stop adding `Transfer-Encoding: chunked` to 100-Continue interim responses. - **WebSocket Close Handshake** (RFC 6455): - Comply with the close handshake state machine - Close the transport after the close handshake completes - Fix binary send when the `text` key is `None` - **Early Hints**: Validate headers in the `early_hints` callback to match `process_headers`; pass only the header name to `InvalidHeader` ([#&#8203;3588](https://github.com/benoitc/gunicorn/issues/3588)). - **ASGI Framework Fixes**: - Fix ASGI disconnect handling for Django-style apps - Fix Litestar request handling (use raw ASGI receive for body/headers) - Fix Litestar HTTP endpoints for compatibility tests - Fix Quart headers endpoint to normalize keys to lowercase - Fix Quart WebSocket close test app (missing `accept()`) - Fix duplicate `Transfer-Encoding` header for BlackSheep streaming #### Refactoring - Split `BodyReceiver._closed` into separate transport and body-wait flags for clearer keepalive/EOF semantics. #### Changes - **Fast HTTP Parser**: Require `gunicorn_h1c >= 0.6.5`. Drop the last `python_only` test markers; the C extension is now used wherever available (CPython only; PyPy continues to use the Python parser). - **Test Dependencies**: Add `h2` and `uvloop` to the `testing` extra; remove `eventlet`. - **Docker Build**: Bump GitHub Actions `docker/setup-qemu-action`, `docker/setup-buildx-action`, `docker/login-action`, `docker/build-push-action`, and `docker/metadata-action` to current major versions. **Full changelog**: <https://github.com/benoitc/gunicorn/compare/25.3.0...26.0.0> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjUuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjQxLjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->
Update dependency gunicorn to v26
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
7a3cf07292
Renovate force-pushed renovate/gunicorn-26.x from 7a3cf07292
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to a4f07dbbe0
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-05-15 11:25:24 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from a4f07dbbe0
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to 3f60d0099c
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-05-24 23:24:27 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from 3f60d0099c
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to 7650719556
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-06-04 05:26:58 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from 7650719556
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to 9c6c8cba94
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-06-14 23:25:54 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from 9c6c8cba94
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to b3068b3aac
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-06-23 11:25:46 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from b3068b3aac
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to 4a376ffe63
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-07-02 11:27:45 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from 4a376ffe63
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to 0a21e13db6
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-07-04 17:27:04 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from 0a21e13db6
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to 8d7c55a1b6
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-07-08 23:27:28 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from 8d7c55a1b6
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to c82fa3b8d9
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-08-01 17:30:39 +00:00
Compare
Renovate force-pushed renovate/gunicorn-26.x from c82fa3b8d9
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
to a8d62220ac
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
2026-08-03 11:31:17 +00:00
Compare
All checks were successful
ci/woodpecker/pr/testbuild Pipeline was successful
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin renovate/gunicorn-26.x:renovate/gunicorn-26.x
git switch renovate/gunicorn-26.x

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff renovate/gunicorn-26.x
git switch renovate/gunicorn-26.x
git rebase main
git switch main
git merge --ff-only renovate/gunicorn-26.x
git switch renovate/gunicorn-26.x
git rebase main
git switch main
git merge --no-ff renovate/gunicorn-26.x
git switch main
git merge --squash renovate/gunicorn-26.x
git switch main
git merge --ff-only renovate/gunicorn-26.x
git switch main
git merge renovate/gunicorn-26.x
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Bluemedia/LibreCharge!606
No description provided.