CVE-2026-47737 Overview
CVE-2026-47737 is a source IP spoofing vulnerability 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 set_remote_address proxy_protocol: :v1 is enabled and persistent (keep-alive) connections are used, Puma re-parses PROXY protocol headers after each request on the same connection. An attacker can inject a second PROXY header into subsequent requests and overwrite REMOTE_ADDR, effectively spoofing the client source IP address. This weakness is classified as Authentication Bypass by Spoofing [CWE-290].
Critical Impact
Attackers can forge arbitrary source IP addresses, bypassing IP-based access controls, rate limiting, audit logging, and geo-restrictions in applications behind Puma.
Affected Products
- Puma Ruby/Rack web server versions 5.5.0 through 7.2.0
- Puma 8.0.0 and 8.0.1
- Deployments using set_remote_address proxy_protocol: :v1 with keep-alive enabled
Discovery Timeline
- 2026-07-14 - CVE-2026-47737 published to NVD
- 2026-07-15 - Last updated in NVD database
Technical Details for CVE-2026-47737
Vulnerability Analysis
Puma supports the HAProxy PROXY protocol v1 to receive the true client IP from upstream load balancers. The PROXY header is intended to be sent once at the start of a TCP connection, before any HTTP data. Puma's client state machine, however, resets the @read_proxy flag on every request cycle when persistent connections are used. This causes the server to attempt to re-parse a PROXY header at the start of each pipelined or keep-alive HTTP request. An attacker who can open a direct connection to Puma, or who controls a request body on an existing connection, can prepend a crafted PROXY TCP4 ... line to a follow-up request. Puma treats the injected line as authoritative and overwrites REMOTE_ADDR for that request.
Root Cause
The root cause lies in lib/puma/client.rb, where the reset logic sets @read_proxy = !!@expect_proxy_proto without checking whether any request had already been served on the connection. Combined with a PROXY_PROTOCOL_V1_REGEX anchored with ^ (matching any line start) rather than \A (matching only the buffer start), Puma accepted PROXY headers appearing after the first request boundary.
Attack Vector
Exploitation requires network access to a Puma instance where PROXY protocol v1 parsing is enabled. The attacker establishes a keep-alive connection, sends a valid initial request, then sends a follow-up request whose bytes begin with a forged PROXY TCP4 <spoofed-ip> <dst-ip> <sport> <dport>\r\n line. Puma parses the forged header and reports the spoofed address to the Rack application via REMOTE_ADDR. This defeats controls that trust REMOTE_ADDR for authentication, allowlisting, or abuse mitigation.
# Security patch in lib/puma/client.rb (7.2.1 and 8.0.2 backports)
@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: GitHub Commit 439c6136
# Regex hardening in lib/puma/const.rb
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
Source: GitHub Commit ebe9db39
The fix restricts PROXY header parsing to the first request on a connection (@requests_served.zero?) and anchors the regex to the absolute start of the buffer to prevent mid-stream injection.
Detection Methods for CVE-2026-47737
Indicators of Compromise
- Application or Rack access logs showing sudden shifts in REMOTE_ADDR for the same underlying TCP session or user agent fingerprint.
- Requests where the client IP in application logs disagrees with upstream load balancer or WAF logs for the same request ID.
- Presence of raw PROXY TCP4 or PROXY TCP6 byte sequences inside HTTP request bodies or headers captured in packet traces.
Detection Strategies
- Compare REMOTE_ADDR values recorded by Puma against source IP fields logged by upstream reverse proxies, load balancers, or CDNs for the same request identifier.
- Alert on any request whose first bytes on a persistent connection match the PROXY protocol v1 signature after a prior HTTP request on the same connection.
- Inventory running Puma versions across Ruby application fleets and flag any instance in the 5.5.0 through 8.0.1 range that has set_remote_address proxy_protocol: :v1 configured.
Monitoring Recommendations
- Enable connection-level logging in the upstream proxy to correlate true TCP source IPs against application-reported client IPs.
- Track authentication events, rate-limit decisions, and admin actions keyed on REMOTE_ADDR for anomalous source distributions.
- Ingest Puma and reverse-proxy logs into a centralized analytics platform to run continuous cross-source correlation queries.
How to Mitigate CVE-2026-47737
Immediate Actions Required
- Upgrade Puma to version 7.2.1 or 8.0.2, which contain the official fix.
- If immediate patching is not possible, disable set_remote_address proxy_protocol: :v1 in Puma configuration until the upgrade is complete.
- Ensure Puma is not directly reachable from untrusted networks and only accepts connections from trusted upstream proxies.
Patch Information
The Puma maintainers released fixes in Puma v7.2.1 via Pull Request #3947 and Puma v8.0.2 via Pull Request #3944. Full technical detail is available in the GitHub Security Advisory GHSA-2vqw-3mp8-cgmx.
Workarounds
- Disable PROXY protocol v1 parsing in Puma and rely on trusted X-Forwarded-For handling at the application layer with a validated proxy allowlist.
- Terminate PROXY protocol at the upstream reverse proxy (for example, HAProxy or Nginx) and pass the client IP via a signed HTTP header that the application validates.
- Restrict Puma's listening socket to a private network segment where only vetted load balancers can connect.
# Upgrade Puma via Bundler to a patched release
bundle update puma --conservative
# Verify installed version is 7.2.1+ or 8.0.2+
bundle exec puma --version
# Temporary workaround: remove PROXY protocol v1 from puma config
# In config/puma.rb, comment out or remove:
# set_remote_address proxy_protocol: :v1
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

