Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions gems/puma/GHSA-86xc-wrr9-xmmq.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
gem: puma
ghsa: 86xc-wrr9-xmmq
url: https://github.com/puma/puma/security/advisories/GHSA-86xc-wrr9-xmmq
title: pumactl does not verify the TLS identity of SSL control servers
date: 2026-11-13
description: |
## Impact

An attacker on the network path between pumactl and a Puma control
server reached over ssl:// can steal the control token and use it
to stop, restart, or inspect Puma.

pumactl support for ssl:// control URLs was added in v4.3.0 (#2052).

When pumactl connects to an ssl:// control URL, it accepts any TLS
certificate. It should check that a trusted authority issued the
certificate and that the certificate matches the hostname in the URL.
An attacker who can intercept or redirect the connection can present
their own certificate, and pumactl then sends them the control token.

With the token, the attacker can send authenticated commands to the
real control server, including stop, restart, and stats.

Only deployments that run pumactl against an ssl:// control URL
are affected, for example:

* pumactl --control-url 'ssl://control.example.com:9293'
--control-token "$PUMA_CONTROL_TOKEN" stats

Puma's default configuration is not affected. The control server
is off by default, and unix:// control URLs are not affected.

## Acknowledgements

Thanks to @ahmedehab-nopr for reporting this issue.
cvss_v4: 8.3
unaffected_versions:
- "< 4.3.0"
patched_versions:
- "~> 7.2.2"
- ">= 8.0.3"
related:
url:
- https://rubygems.org/gems/puma/versions/8.0.3
- https://github.com/puma/puma/blob/main/History.md#803--2026-11-13
- https://rubygems.org/gems/puma/versions/7.2.2
- https://github.com/puma/puma/blob/main/History.md#722--2026-11-13
- https://github.com/puma/puma/pull/2052
- https://github.com/puma/puma/security/advisories/GHSA-86xc-wrr9-xmmq
notes: |
- No CVE from GHSA URL.
- cvss_v4 from GHSA URL.
- No gap between 7.2.2 and 8.0.0 releases.
63 changes: 63 additions & 0 deletions gems/puma/GHSA-jm4r-w4xx-qxw7.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
gem: puma
ghsa: jm4r-w4xx-qxw7
url: https://github.com/puma/puma/security/advisories/GHSA-jm4r-w4xx-qxw7
title: Graceful shutdown waits indefinitely on trickled request bodies
date: 2026-11-13
description: |
## Impact

Puma servers running in single mode with the default force_shutdown_after
:forever can be prevented from shutting down or restarting by an
unauthenticated client.

When Puma begins a graceful shutdown, it hands any request that is
still being received to the thread pool so that it can finish. The
thread pool then waits for that request with no deadline. Puma's
read timeout restarts every time a byte arrives, so an attacker who
sends a POST with a large Content-Length and then trickles one body
byte every few seconds can keep that request "in progress" for as
long as they like. The same works with chunked bodies and with
trickled headers. Puma stops accepting new connections, but the
process does not exit until the attacker closes the socket.

This delays or blocks deploys, restarts, and operational recovery.
Process supervisors that eventually send SIGKILL will end the
process, but any other in-flight requests are lost when they do.
Many trickling connections can be used together; each holds a
file descriptor but uses very little memory.

Deployments using Puma's default single-mode configuration are affected.

Unaffected:

* Cluster mode (workers N) bounds the delay: the master kills a
stuck worker after worker_shutdown_timeout (30 seconds by default).

* A finite force_shutdown_after bounds the delay.

* Deployments behind a proxy that buffers request bodies before
forwarding them are not affected. For example, nginx's default
proxy_request_buffering on keeps a trickled body from reaching Puma.

## Acknowledgements

Thanks to Zheng Yu of depthfirst (@cla7aye15I4nd) for reporting
this issue and providing a proposed fix.
cvss_v4: 6.9
unaffected_versions:
- "< 5.0.0"
patched_versions:
- "~> 7.2.2"
- ">= 8.0.3"
related:
url:
- https://rubygems.org/gems/puma/versions/8.0.3
- https://github.com/puma/puma/blob/main/History.md#803--2026-11-13
- https://rubygems.org/gems/puma/versions/7.2.2
- https://github.com/puma/puma/blob/main/History.md#722--2026-11-13
- https://github.com/puma/puma/security/advisories/GHSA-jm4r-w4xx-qxw7
notes: |
- No CVE from GHSA URL.
- cvss_v4 from GHSA URL.
- No gap between 7.2.2 and 8.0.0 releases.
50 changes: 50 additions & 0 deletions gems/puma/GHSA-m5xw-3hrw-qcmf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
gem: puma
ghsa: m5xw-3hrw-qcmf
url: https://github.com/puma/puma/security/advisories/GHSA-m5xw-3hrw-qcmf
title: Reactor timeout queue loses order, letting a slow client
keep other connections open past their timeout
date: 2026-11-13
description: |
## Impact

An unauthenticated client can stop Puma from closing other connections
when their first_data_timeout or persistent_timeout expires.

The reactor keeps waiting connections in a queue sorted by deadline.
Puma sorted the queue only when new connections arrived, but it
extended a connection's deadline every time that connection sent
data. A client that connects and then sends about one byte per
timeout interval, without completing its request, stays at the
front of the queue with a deadline that is always in the future.
Every expired connection behind it stays open for as long as the
client keeps sending, or until a new connection arrives and causes a sort.

Connections held this way cost Puma one file descriptor and a small
amount of memory each. Puma does not crash, and the effect ends
when the slow client stops or new traffic arrives.
This bug was introduced in v5.0.3 by the reactor refactor (#2279).

The default configuration is affected.

## Acknowledgements

Thanks to @0xMigoo for the report and the controlled proof of
concept that isolated the missing sort.
cvss_v4: 6.9
unaffected_versions:
- "< 5.0.3"
patched_versions:
- ">= 7.2.2"
- ">= 8.0.3"
related:
url:
- https://rubygems.org/gems/puma/versions/8.0.3
- https://github.com/puma/puma/blob/main/History.md#803--2026-11-13
- https://rubygems.org/gems/puma/versions/7.2.2
- https://github.com/puma/puma/blob/main/History.md#722--2026-11-13
- https://github.com/puma/puma/security/advisories/GHSA-m5xw-3hrw-qcmf
notes: |
- No CVE from GHSA URL.
- cvss_v4 from GHSA URL.
- No gap between 7.2.2 and 8.0.0 releases.
59 changes: 59 additions & 0 deletions gems/puma/GHSA-r885-m39c-939g.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
gem: puma
ghsa: r885-m39c-939g
url: https://github.com/puma/puma/security/advisories/GHSA-r885-m39c-939g
title: ssl_cipher_filter values containing `+` are decoded
to a wider cipher list
date: 2026-11-13
description: |
## Impact

Puma servers that set an ssl_cipher_filter containing + accept TLS
cipher suites that the filter is meant to exclude. Any client that
can reach the SSL listener can negotiate those suites.

Support for ssl_cipher_filter in ssl:// bind URLs was added in
v3.12.0 (#1478), and in the ssl_bind DSL in v4.0.0 (#1676).

In an OpenSSL cipher string, + means "both": AES+SHA selects suites
that are AES and SHA. Puma decodes the query of a bind URL as HTML
form data, where + means a space, and a space in a cipher string
means "either". So the filter !aNULL:AES+SHA, which is the example
in Puma's README, reaches OpenSSL as !aNULL:AES SHA: every AES
suite plus every SHA suite. On OpenSSL 3.x that is 94 suites in
place of 27, including suites without forward secrecy.

Puma logs nothing, and the startup output shows the filter as written,
so operators cannot see that their filter is not applied. The filter
only gets wider: explicit exclusions such as !aNULL or !3DES still apply.

Deployments that set ssl_cipher_filter with a + are affected, in either form:

* ssl_bind "0.0.0.0", "9292", ssl_cipher_filter: "!aNULL:AES+SHA",
cert: "...", key: "..."

* puma -b 'ssl://0.0.0.0:9292?key=...&cert=...&ssl_cipher_filter=!aNULL:AES+SHA'

The default configuration is not affected. Filters without +,
ssl_ciphersuites (TLS 1.3), and JRuby's ssl_cipher_list are not affected.

## Acknowledgements

Thanks to @b84330808 for reporting this issue.
cvss_v4: 6.3
unaffected_versions:
- "< 3.12.0"
patched_versions:
- "~> 7.2.2"
- ">= 8.0.3"
related:
url:
- https://rubygems.org/gems/puma/versions/8.0.3
- https://github.com/puma/puma/blob/main/History.md#803--2026-11-13
- https://rubygems.org/gems/puma/versions/7.2.2
- https://github.com/puma/puma/blob/main/History.md#722--2026-11-13
- https://github.com/puma/puma/security/advisories/GHSA-r885-m39c-939g
notes: |
- No CVE from GHSA URL.
- cvss_v4 from GHSA URL.
- No gap between 7.2.2 and 8.0.0 releases.
59 changes: 59 additions & 0 deletions gems/puma/GHSA-rf6g-xhmp-gm7v.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
gem: puma
ghsa: rf6g-xhmp-gm7v
url: https://github.com/puma/puma/security/advisories/GHSA-rf6g-xhmp-gm7v
title: Accept loop retries without pausing when out of file
descriptors, flooding the log
date: 2026-11-13
description: |
## Impact

An unauthenticated client can make Puma:

1. write log lines as fast as it can...
2. ...which eventually may fill the disk...
3. ...and when the disk fills, Puma will stop accepting connections.

The client can do this by opening enough idle connections to use up
the process's file descriptors.

When accept fails because the process is out of file descriptors
(EMFILE), the connection stays in the listen backlog, so the listening
socket stays readable. Puma logs the error and calls accept again
at once, with no pause. Each attempt fails the same way and writes
another "Listen loop" error line. An attacker who opens about as
many idle connections as the file descriptor limit, and sends
nothing on them, keeps Puma in this loop. The attacker reconnects
when Puma closes idle connections after first_data_timeout, so
the loop does not end.

If the log destination then raises an error, such as ENOSPC when
the disk is full, that error escapes the listen loop and ends
the thread that accepts connections.

Deployments that accept connections directly from clients are
affected, including the default configuration.

Deployments behind a reverse proxy or load balancer that limits
connections to Puma are not affected, because the client cannot
open enough connections to Puma itself.

## Acknowledgements

Found by Puma's maintainers during an internal security review.x
cvss_v4: 8.7
patched_versions:
- "~> 7.2.2"
- ">= 8.0.3"
related:
url:
- https://rubygems.org/gems/puma/versions/8.0.3
- https://github.com/puma/puma/blob/main/History.md#803--2026-11-13
- https://rubygems.org/gems/puma/versions/7.2.2
- https://github.com/puma/puma/blob/main/History.md#722--2026-11-13
- https://github.com/puma/puma/pull/2052
- https://github.com/puma/puma/security/advisories/GHSA-rf6g-xhmp-gm7v
notes: |
- No CVE from GHSA URL.
- cvss_v4 from GHSA URL.
- No gap between 7.2.2 and 8.0.0 releases.
70 changes: 70 additions & 0 deletions gems/puma/GHSA-x646-wp7c-h9mg.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
gem: puma
ghsa: x646-wp7c-h9mg
url: https://github.com/puma/puma/security/advisories/GHSA-x646-wp7c-h9mg
title: Client that stops reading a TLS or file response holds
a worker thread indefinitely
date: 2026-11-13
description: |
## Impact

An unauthenticated client can hold a Puma worker thread indefinitely
when Puma sends it a TLS response, or a file response larger than
64 KB. With enough connections, it can take the server offline.

The client sends a request and then stops reading the response.
Puma's write timeout (10 seconds) should then free the worker
thread. In two cases it never fires, and the thread stays blocked
until the client disconnects:

* TLS: MiniSSL::Socket#write_nonblock performs a blocking write.
This has been the case since v3.9.0 (#1274).

* File bodies over 64 KB, on any bind: Puma sends File bodies, and
bodies that respond to to_path, with IO.copy_stream, which has no
write timeout. This has been the case since v6.0.0 (#2896).
Rack::Files, Rails static file serving, and Rails send_file return
these bodies.

An attacker who opens max_threads such connections per worker leaves
no threads to serve requests. The attack needs very little bandwidth
and no CPU on the server.

Two kinds of deployment are affected:

* Deployments where Puma terminates TLS itself, with an ssl://
bind or ssl_bind:

* ssl_bind "0.0.0.0", "9292", cert: "/path/to/cert.pem", key:
"/path/to/key.pem"

* Applications that serve a file larger than 64 KB through Puma, on
any bind. Puma's default configuration is affected in this case.

Deployments behind a proxy that buffers responses are not affected
for files that fit in the proxy's buffer. nginx buffers by default
(proxy_buffering on), up to 1 GB per response in a temporary file.

## Acknowledgements

Thanks to @wtn and @SounLabs, who reported this issue
independently, and to @wtn for the fix.
cvss_v4: 8.7
unaffected_versions:
- "< 3.9.0"
patched_versions:
- ">= 7.2.2"
- ">= 8.0.3"
related:
url:
- https://rubygems.org/gems/puma/versions/8.0.3
- https://github.com/puma/puma/blob/main/History.md#803--2026-11-13
- https://rubygems.org/gems/puma/versions/7.2.2
- https://github.com/puma/puma/blob/main/History.md#722--2026-11-13
- https://github.com/puma/puma/pull/1274
- https://github.com/puma/puma/pull/2896
- https://github.com/puma/puma/security/advisories/GHSA-x646-wp7c-h9mg
notes: |
- No CVE from GHSA URL.
- cvss_v4 from GHSA URL.
- No gap between 7.2.2 and 8.0.0 releases.
Loading
Loading