Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-47736

CVE-2026-47736: Puma Web Server DoS Vulnerability

CVE-2026-47736 is a denial of service flaw in Puma Ruby/Rack web server that allows unbounded memory growth via PROXY protocol exploitation. This article covers technical details, affected versions, and mitigation.

Published:

CVE-2026-47736 Overview

CVE-2026-47736 is a resource exhaustion vulnerability [CWE-400] in Puma, a Ruby/Rack web server built for parallelism. The flaw affects Puma versions from 5.5.0 up to but not including 7.2.1 and 8.0.2. When PROXY protocol v1 support is enabled, Puma reads incoming bytes into an internal buffer while waiting for a CRLF sequence to determine whether a PROXY v1 header line is present. An attacker who continuously streams bytes without sending CRLF can cause unbounded in-process memory growth and additional CPU cost from repeatedly scanning the growing buffer. The issue is fixed in Puma 7.2.1 and 8.0.2.

Critical Impact

A single unauthenticated network client can exhaust memory and CPU on any Puma worker configured with :proxy_protocol => :v1, leading to denial of service across the affected process.

Affected Products

  • Puma >= 5.5.0, < 7.2.1
  • Puma >= 8.0.0, < 8.0.2
  • Any Ruby/Rack application deploying Puma with PROXY protocol v1 enabled

Discovery Timeline

  • 2026-07-14 - CVE-2026-47736 published to NVD
  • 2026-07-15 - Last updated in NVD database

Technical Details for CVE-2026-47736

Vulnerability Analysis

Puma optionally accepts the HAProxy PROXY protocol v1 preamble on incoming connections. This textual header terminates with \r\n and, per specification, cannot exceed 107 bytes. The pre-patch implementation performed no length bound on the read loop, deferring parsing until a CRLF appeared. A client that opens a TCP connection and dribbles bytes without ever transmitting CRLF forces Puma to continue appending data to its internal buffer indefinitely.

Two compounding costs result: memory grows in proportion to bytes received, and every additional read triggers a regex scan across the entire accumulated buffer to look for the terminator. Multiple concurrent malicious connections multiply both effects, degrading or halting request service.

Root Cause

The root cause is missing input length validation combined with an anchored-but-unbounded regex. The pre-patch constant PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/ uses ^ (line anchor) and lacks a maximum length constraint. Additionally, @read_proxy was re-armed on every request cycle for a persistent connection, allowing a client to remain in the PROXY-reading state beyond the first request.

Attack Vector

The attack is remote, unauthenticated, and requires no user interaction. An attacker opens a TCP connection to a Puma instance with PROXY v1 enabled and transmits bytes slowly, omitting CRLF. Puma buffers the stream and repeatedly scans it, consuming memory and CPU until the process is starved.

ruby
# Fix in lib/puma/const.rb - constrain regex to string start and cap length
# Banned keys of response header
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze

-PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
+PROXY_PROTOCOL_V1_REGEX = /\APROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
+PROXY_PROTOCOL_V1_MAX_LENGTH = 107

# Fix in lib/puma/client.rb - only read PROXY header on first request of a connection
@parser.reset
@io_buffer.reset
@read_header = true
-@read_proxy = !!@expect_proxy_proto
+@read_proxy = !!@expect_proxy_proto && @requests_served.zero?
@env = @proto_env.dup
@parsed_bytes = 0
@ready = false

Source: Puma commit 439c6136 (8.0.2 backport) and Puma commit ebe9db39 (7.2.1 backport)

Detection Methods for CVE-2026-47736

Indicators of Compromise

  • Puma worker processes exhibiting steadily growing resident memory (RSS) without a corresponding increase in served requests.
  • Long-lived TCP connections to Puma listener sockets that transmit data at low rates and never complete an HTTP request.
  • Elevated CPU on Puma workers correlated with a small number of stalled client connections.

Detection Strategies

  • Inventory Ruby application deployments and identify Puma configurations that call bind with :proxy_protocol => :v1 or set PROXY_PROTOCOL=1.
  • Alert on Puma processes whose memory growth exceeds a baseline threshold over a short window while request throughput remains flat.
  • Correlate TCP connections held open beyond a defined idle threshold with byte-transfer patterns below normal HTTP request sizes.

Monitoring Recommendations

  • Export Puma process metrics (RSS, worker restarts, backlog) to your observability stack and set anomaly alerts.
  • Enable connection-level telemetry at the load balancer or reverse proxy fronting Puma to flag slow-loris style patterns.
  • Track Puma version strings across the fleet using software inventory tooling to confirm patched releases are deployed.

How to Mitigate CVE-2026-47736

Immediate Actions Required

  • Upgrade Puma to 7.2.1 or 8.0.2 on all affected deployments.
  • If upgrading is not immediately possible, disable PROXY protocol v1 by removing :proxy_protocol => :v1 from bind directives.
  • Terminate PROXY v1 at an upstream load balancer that enforces the 107-byte header limit and forward plain TCP or HTTP to Puma.

Patch Information

The fix is available in Puma v7.2.1 and Puma v8.0.2. Full technical details are published in GitHub Security Advisory GHSA-qpgp-93vx-g8v8. The patches anchor the PROXY v1 regex to the start of the buffer with \A, introduce PROXY_PROTOCOL_V1_MAX_LENGTH = 107, and restrict PROXY header parsing to the first request of a connection via @requests_served.zero?.

Workarounds

  • Front Puma with a reverse proxy such as HAProxy or Nginx that validates PROXY v1 headers and drops non-conforming connections.
  • Apply strict client idle and read timeouts at the L4 or L7 tier to close connections that fail to deliver a complete PROXY header promptly.
  • Restrict inbound access to Puma listener ports to trusted upstream load balancer IP ranges only.
bash
# Example: upgrade Puma via Bundler and verify the installed version
bundle update puma --conservative
bundle exec puma --version

# Example: remove PROXY v1 from a Puma config as a temporary mitigation
# config/puma.rb
# Before:
#   bind 'tcp://0.0.0.0:9292?proxy_protocol=v1'
# After:
#   bind 'tcp://0.0.0.0:9292'

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.