Skip to content

🚨 [security] Update rack 3.2.5 → 3.2.6 (patch)#585

Open
depfu[bot] wants to merge 1 commit intomasterfrom
depfu/update/rack-3.2.6
Open

🚨 [security] Update rack 3.2.5 → 3.2.6 (patch)#585
depfu[bot] wants to merge 1 commit intomasterfrom
depfu/update/rack-3.2.6

Conversation

@depfu
Copy link
Copy Markdown
Contributor

@depfu depfu bot commented Apr 3, 2026


🚨 Your current dependencies have known security vulnerabilities 🚨

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request.

What changed?

↗️ rack (indirect, 3.2.5 → 3.2.6) · Repo · Changelog

Security Advisories 🚨

🚨 Rack::Static prefix matching can expose unintended files under the static root

Summary

Rack::Static determines whether a request should be served as a static file using a simple string prefix check. When configured with URL prefixes such as "/css", it matches any request path that begins with that string, including unrelated paths such as "/css-config.env" or "/css-backup.sql".

As a result, files under the static root whose names merely share the configured prefix may be served unintentionally, leading to information disclosure.

Details

Rack::Static#route_file performs static-route matching using logic equivalent to:

@urls.any? { |url| path.index(url) == 0 }

This checks only whether the request path starts with the configured prefix string. It does not require a path segment boundary after the prefix.

For example, with:

use Rack::Static, urls: ["/css", "/js"], root: "public"

the following path is matched as intended:

/css/style.css

but these paths are also matched:

/css-config.env
/css-backup.sql
/csssecrets.yml

If such files exist under the configured static root, Rack forwards the request to the file server and serves them as static content.

This means a configuration intended to expose only directory trees such as /css/... and /js/... may also expose sibling files whose names begin with those same strings.

Impact

An attacker can request files under the configured static root whose names share a configured URL prefix and obtain their contents.

In affected deployments, this may expose configuration files, secrets, backups, environment files, or other unintended static content located under the same root directory.

Mitigation

  • Update to a patched version of Rack that enforces a path boundary when matching configured static URL prefixes.
  • Match only paths that are either exactly equal to the configured prefix or begin with prefix + "/".
  • Avoid placing sensitive files under the Rack::Static root directory.
  • Prefer static URL mappings that cannot overlap with sensitive filenames.

🚨 Rack:: Static header_rules bypass via URL-encoded paths

Summary

Rack::Static#applicable_rules evaluates several header_rules types against the raw URL-encoded PATH_INFO, while the underlying file-serving path is decoded before the file is served. As a result, a request for a URL-encoded variant of a static path can serve the same file without the headers that header_rules were intended to apply.

In deployments that rely on Rack::Static to attach security-relevant response headers to static content, this can allow an attacker to bypass those headers by requesting an encoded form of the path.

Details

Rack::Static#applicable_rules matches rule types such as :fonts, Array, and Regexp directly against the incoming PATH_INFO. For example:

when :fonts
  /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
when Array
  /\.(#{rule.join('|')})\z/.match?(path)
when Regexp
  rule.match?(path)

These checks operate on the raw request path. If the request contains encoded characters such as %2E in place of ., the rule may fail to match even though the file path is later decoded and served successfully by the static file server.

For example, both of the following requests may resolve to the same file on disk:

/fonts/test.woff
/fonts/test%2Ewoff

but only the unencoded form may receive the headers configured through header_rules.

This creates a canonicalization mismatch between the path used for header policy decisions and the path ultimately used for file serving.

Impact

Applications that rely on Rack::Static header_rules to apply security-relevant headers to static files may be affected.

In affected deployments, an attacker can request an encoded variant of a static file path and receive the same file without the intended headers. Depending on how header_rules are used, this may bypass protections such as clickjacking defenses, content restrictions, or other response policies applied to static content.

The practical impact depends on the configured rules and the types of files being served. If header_rules are only used for non-security purposes such as caching, the issue may have limited security significance.

Mitigation

  • Update to a patched version of Rack that applies header_rules to a decoded path consistently with static file resolution.
  • Do not rely solely on Rack::Static header_rules for security-critical headers where encoded path variants may reach the application.
  • Prefer setting security headers at the reverse proxy or web server layer so they apply consistently to both encoded and unencoded path forms.
  • Normalize or reject encoded path variants for static content at the edge, where feasible.

🚨 Rack's multipart byte range processing allows denial of service via excessive overlapping ranges

Summary

Rack::Utils.get_byte_ranges parses the HTTP Range header without limiting the number of individual byte ranges. Although the existing fix for CVE-2024-26141 rejects ranges whose total byte coverage exceeds the file size, it does not restrict the count of ranges. An attacker can supply many small overlapping ranges such as 0-0,0-0,0-0,... to trigger disproportionate CPU, memory, I/O, and bandwidth consumption per request.

This results in a denial of service condition in Rack file-serving paths that process multipart byte range responses.

Details

Rack::Utils.get_byte_ranges accepts a comma-separated list of byte ranges and validates them based on their aggregate size, but does not impose a limit on how many individual ranges may be supplied.

As a result, a request such as:

Range: bytes=0-0,0-0,0-0,0-0,...

can contain thousands of overlapping one-byte ranges while still satisfying the total-size check added for CVE-2024-26141.

When such a header is processed by Rack’s file-serving code, each range causes additional work, including multipart response generation, per-range iteration, file seek and read operations, and temporary string allocation for response size calculation and output. This allows a relatively small request header to trigger disproportionately expensive processing and a much larger multipart response.

The issue is distinct from CVE-2024-26141. That fix prevents range sets whose total byte coverage exceeds the file size, but does not prevent a large number of overlapping ranges whose summed size remains within that limit.

Impact

Applications that expose file-serving paths with byte range support may be vulnerable to denial of service.

An unauthenticated attacker can send crafted Range headers containing many small overlapping ranges to consume excessive CPU time, memory, file I/O, and bandwidth. Repeated requests may reduce application availability and increase pressure on workers and garbage collection.

Mitigation

  • Update to a patched version of Rack that limits the number of accepted byte ranges.
  • Reject or normalize multipart byte range requests containing excessive range counts.
  • Consider disabling multipart range support where it is not required.
  • Apply request filtering or header restrictions at the reverse proxy or application boundary to limit abusive Range headers.

🚨 Rack's multipart header parsing allows Denial of Service via escape-heavy quoted parameters

Summary

Rack::Multipart::Parser#handle_mime_head parses quoted multipart parameters such as Content-Disposition: form-data; name="..." using repeated String#index searches combined with String#slice! prefix deletion. For escape-heavy quoted values, this causes super-linear processing.

An unauthenticated attacker can send a crafted multipart/form-data request containing many parts with long backslash-escaped parameter values to trigger excessive CPU usage during multipart parsing.

This results in a denial of service condition in Rack applications that accept multipart form data.

Details

Rack::Multipart::Parser#handle_mime_head parses quoted parameter values by repeatedly:

  1. Searching for the next quote or backslash,
  2. Copying the preceding substring into a new buffer, and
  3. Removing the processed prefix from the original string with slice!.

An attacker can exploit this by sending a multipart request with many parts whose name parameters contain long escape-heavy values such as:

name="a\\a\\a\\a\\a\\..."

Under default Rack limits, a request can contain up to 4095 parts. If many of those parts use long quoted values with dense escape characters, the parser performs disproportionately expensive CPU work while remaining within normal request size and part-count limits.

Impact

Any Rack application that accepts multipart/form-data requests may be affected, including file upload endpoints and standard HTML form handlers.

An unauthenticated attacker can send crafted multipart requests that consume excessive CPU time during request parsing. Repeated requests can tie up application workers, reduce throughput, and degrade or deny service availability.

Mitigation

  • Update to a patched version of Rack that parses quoted multipart parameters without repeated rescanning and destructive prefix deletion.
  • Apply request throttling or rate limiting to multipart upload endpoints.
  • Where operationally feasible, restrict or isolate multipart parsing on untrusted high-volume endpoints.

🚨 Rack's greedy multipart boundary parsing can cause parser differentials and WAF bypass.

Summary

Rack::Multipart::Parser extracts the boundary parameter from multipart/form-data using a greedy regular expression. When a Content-Type header contains multiple boundary parameters, Rack selects the last one rather than the first.

In deployments where an upstream proxy, WAF, or intermediary interprets the first boundary parameter, this mismatch can allow an attacker to smuggle multipart content past upstream inspection and have Rack parse a different body structure than the intermediary validated.

Details

Rack identifies the multipart boundary using logic equivalent to:

MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni

Because the expression is greedy, it matches the last boundary= parameter in a header such as:

Content-Type: multipart/form-data; boundary=safe; boundary=malicious

As a result, Rack parses the request body using malicious, while another component may interpret the same header using safe.

This creates an interpretation conflict. If an upstream WAF or proxy inspects multipart parts using the first boundary and Rack later parses the body using the last boundary, a client may be able to place malicious form fields or uploaded content in parts that Rack accepts but the upstream component did not inspect as intended.

This issue is most relevant in layered deployments where security decisions are made before the request reaches Rack.

Impact

Applications that accept multipart/form-data uploads behind an inspecting proxy or WAF may be affected.

In such deployments, an attacker may be able to bypass upstream filtering of uploaded files or form fields by sending a request with multiple boundary parameters and relying on the intermediary and Rack to parse the request differently.

The practical impact depends on deployment architecture. If no upstream component relies on a different multipart interpretation, this behavior may not provide meaningful additional attacker capability.

Mitigation

  • Update to a patched version of Rack that rejects ambiguous multipart Content-Type headers or parses duplicate boundary parameters consistently.
  • Reject requests containing multiple boundary parameters.
  • Normalize or regenerate multipart metadata at the trusted edge before forwarding requests to Rack.
  • Avoid relying on upstream inspection of malformed multipart requests unless duplicate parameter handling is explicitly consistent across components.

🚨 Rack's improper unfolding of folded multipart headers preserves CRLF in parsed parameter values

Summary

Rack::Multipart::Parser unfolds folded multipart part headers incorrectly. When a multipart header contains an obs-fold sequence, Rack preserves the embedded CRLF in parsed parameter values such as filename or name instead of removing the folded line break during unfolding.

As a result, applications that later reuse those parsed values in HTTP response headers may be vulnerable to downstream header injection or response splitting.

Details

Rack::Multipart::Parser accepts folded multipart header values and unfolds them during parsing. However, the unfolding behavior does not fully remove the embedded line break sequence from the parsed value.

This means a multipart part header such as:

Content-Disposition: form-data; name="file"; filename="test\r\n foo.txt"

can result in a parsed parameter value that still contains CRLF characters.

The issue is not that Rack creates a second multipart header field. Rather, the problem is that CRLF remains embedded in the parsed metadata value after unfolding. If an application later uses that value in a security-sensitive context, such as constructing an HTTP response header, the preserved CRLF may alter downstream header parsing.

Affected values may include multipart parameters such as filename, name, or similar parsed header attributes.

Impact

Applications that accept multipart form uploads may be affected if they later reuse parsed multipart metadata in HTTP headers or other header-sensitive contexts.

In affected deployments, an attacker may be able to supply a multipart parameter value containing folded line breaks and cause downstream header injection, response splitting, cache poisoning, or related response parsing issues.

The practical impact depends on application behavior. If parsed multipart metadata is not reused in HTTP headers, the issue may be limited to incorrect parsing behavior rather than a direct exploit path.

Mitigation

  • Update to a patched version of Rack that removes CRLF correctly when unfolding folded multipart header values.
  • Avoid copying upload metadata such as filename directly into HTTP response headers without sanitization.
  • Sanitize or reject carriage return and line feed characters in multipart-derived values before reusing them in response headers, logs, or downstream protocol contexts.
  • Where feasible, normalize uploaded filenames before storing or reflecting them.

🚨 Rack: Forwarded Header semicolon injection enables Host and Scheme spoofing

Summary

Rack::Utils.forwarded_values parses the RFC 7239 Forwarded header by splitting on semicolons before handling quoted-string values. Because quoted values may legally contain semicolons, a header such as:

Forwarded: for="127.0.0.1;host=evil.com;proto=https"

can be interpreted by Rack as multiple Forwarded directives rather than as a single quoted for value.

In deployments where an upstream proxy, WAF, or intermediary validates or preserves quoted Forwarded values differently, this discrepancy can allow an attacker to smuggle host, proto, for, or by parameters through a single header value.

Details

Rack::Utils.forwarded_values processes the header using logic equivalent to:

forwarded_header.split(';').each_with_object({}) do |field, values|
  field.split(',').each do |pair|
    pair = pair.split('=').map(&:strip).join('=')
    return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
    (values[$1.downcase.to_sym] ||= []) << $2
  end
end

The method splits on ; before it parses individual name=value pairs. This is inconsistent with RFC 7239, which permits quoted-string values, and quoted strings may contain semicolons as literal content.

As a result, a header value such as:

Forwarded: for="127.0.0.1;host=evil.com;proto=https"

is not treated as a single for value. Instead, Rack may interpret it as if the client had supplied separate for, host, and proto directives.

This creates an interpretation conflict when another component in front of Rack treats the quoted value as valid literal content, while Rack reparses it as multiple forwarding parameters.

Impact

Applications that rely on Forwarded to derive request metadata may observe attacker-controlled values for host, proto, for, or related URL components.

In affected deployments, this can lead to host or scheme spoofing in derived values such as req.host, req.scheme, req.base_url, or req.url. Applications that use those values for password reset links, redirects, absolute URL generation, logging, IP-based decisions, or backend requests may be vulnerable to downstream security impact.

The practical security impact depends on deployment architecture. If clients can already supply arbitrary trusted Forwarded parameters directly, this bug may not add meaningful attacker capability. The issue is most relevant where an upstream component and Rack interpret the same Forwarded header differently.

Mitigation

  • Update to a patched version of Rack that parses Forwarded quoted-string values before splitting on parameter delimiters.
  • Avoid trusting client-supplied Forwarded headers unless they are normalized or regenerated by a trusted reverse proxy.
  • Prefer stripping inbound Forwarded headers at the edge and reconstructing them from trusted proxy metadata.
  • Avoid using req.host, req.scheme, req.base_url, or req.url for security-sensitive operations unless the forwarding chain is explicitly trusted and validated.

🚨 Rack has quadratic complexity in Rack::Utils.select_best_encoding via wildcard Accept-Encoding header

Summary

Rack::Utils.select_best_encoding processes Accept-Encoding values with quadratic time complexity when the header contains many wildcard (*) entries. Because this method is used by Rack::Deflater to choose a response encoding, an unauthenticated attacker can send a single request with a crafted Accept-Encoding header and cause disproportionate CPU consumption on the compression middleware path.

This results in a denial of service condition for applications using Rack::Deflater.

Details

Rack::Utils.select_best_encoding expands parsed Accept-Encoding values into a list of candidate encodings. When an entry is *, the method computes the set of concrete encodings by subtracting the encodings already present in the request:

if m == "*"
  (available_encodings - accept_encoding.map(&:first)).each do |m2|
    expanded_accept_encoding << [m2, q, preference]
  end
else
  expanded_accept_encoding << [m, q, preference]
end

Because accept_encoding.map(&:first) is evaluated inside the loop, it is recomputed for each wildcard entry. If the request contains N wildcard entries, this produces repeated scans over the full parsed header and causes quadratic behavior.

After expansion, the method also performs additional work over expanded_accept_encoding, including per-entry deletion, which further increases the cost for large inputs.

Rack::Deflater invokes this method for each request when the middleware is enabled:

Utils.select_best_encoding(ENCODINGS, Utils.parse_encodings(accept_encoding))

As a result, a client can trigger this expensive code path simply by sending a large Accept-Encoding header containing many repeated wildcard values.

For example, a request with an approximately 8 KB Accept-Encoding header containing about 1,000 *;q=0.5 entries can cause roughly 170 ms of CPU time in a single request on the Rack::Deflater path, compared to a negligible baseline for a normal header.

This issue is distinct from CVE-2024-26146. That issue concerned regular expression denial of service during Accept header parsing, whereas this issue arises later during encoding selection after the header has already been parsed.

Impact

Any Rack application using Rack::Deflater may be affected.

An unauthenticated attacker can send requests with crafted Accept-Encoding headers to trigger excessive CPU usage in the encoding selection logic. Repeated requests can consume worker time disproportionately and reduce application availability.

The attack does not require invalid HTTP syntax or large payload bodies. A single header-sized request is sufficient to reach the vulnerable code path.

Mitigation

  • Update to a patched version of Rack in which encoding selection does not repeatedly rescan the parsed header for wildcard entries.
  • Avoid enabling Rack::Deflater on untrusted traffic.
  • Apply request filtering or header size / format restrictions at the reverse proxy or application boundary to limit abusive Accept-Encoding values.

🚨 Rack has a root directory disclosure via unescaped regex interpolation in Rack::Directory

Summary

Rack::Directory interpolates the configured root path directly into a regular expression when deriving the displayed directory path. If root contains regex metacharacters such as +, *, or ., the prefix stripping can fail and the generated directory listing may expose the full filesystem path in the HTML output.

Details

Rack::Directory::DirectoryBody#each computes the visible path using code equivalent to:

show_path = Utils.escape_html(path.sub(/\A#{root}/, ''))

Here, root is a developer-configured filesystem path. It is normalized earlier with File.expand_path(root) and then inserted directly into a regular expression without escaping.

Because the value is treated as regex syntax rather than as a literal string, metacharacters in the configured path can change how the prefix match behaves. When that happens, the expected root prefix is not removed from path, and the absolute filesystem path is rendered into the HTML directory listing.

Impact

If Rack::Directory is configured to serve a directory whose absolute path contains regex metacharacters, the generated directory listing may disclose the full server filesystem path instead of only the request-relative path.

This can expose internal deployment details such as directory layout, usernames, mount points, or naming conventions that would otherwise not be visible to clients.

Mitigation

  • Update to a patched version of Rack in which the root prefix is removed using an escaped regular expression.
  • Avoid using Rack::Directory with a root path that contains regular expression metacharacters.

🚨 Rack's multipart parsing without Content-Length header allows unbounded chunked file uploads

Summary

Rack::Multipart::Parser only wraps the request body in a BoundedIO when CONTENT_LENGTH is present. When a multipart/form-data request is sent without a Content-Length header, such as with HTTP chunked transfer encoding, multipart parsing continues until end-of-stream with no total size limit.

For file parts, the uploaded body is written directly to a temporary file on disk rather than being constrained by the buffered in-memory upload limit. An unauthenticated attacker can therefore stream an arbitrarily large multipart file upload and consume unbounded disk space.

This results in a denial of service condition for Rack applications that accept multipart form data.

Details

Rack::Multipart::Parser.parse applies BoundedIO only when content_length is not nil:

io = BoundedIO.new(io, content_length) if content_length

When CONTENT_LENGTH is absent, the parser reads the multipart body until EOF without a global byte limit.

Although Rack enforces BUFFERED_UPLOAD_BYTESIZE_LIMIT for retained non-file parts, file uploads are handled differently. When a multipart part includes a filename, the body is streamed to a Tempfile, and the retained-size accounting is not applied to that file content. As a result, file parts are not subject to the same upload size bound.

An attacker can exploit this by sending a chunked multipart/form-data request containing a file part and continuously streaming data without declaring a Content-Length. Rack will continue writing the uploaded data to disk until the client stops or the server exhausts available storage.

Impact

Any Rack application that accepts multipart/form-data uploads may be affected if no upstream component enforces a request body size limit.

An unauthenticated attacker can send a large chunked file upload to consume disk space on the application host. This may cause request failures, application instability, or broader service disruption if the host runs out of available storage.

The practical impact depends on deployment architecture. Reverse proxies or application servers that enforce upload limits may reduce or eliminate exploitability, but Rack itself does not impose a total multipart upload limit in this code path when CONTENT_LENGTH is absent.

Mitigation

  • Update to a patched version of Rack that enforces a total multipart upload size limit even when CONTENT_LENGTH is absent.
  • Enforce request body size limits at the reverse proxy or application server.
  • Isolate temporary upload storage and monitor disk consumption for multipart endpoints.

🚨 Rack::Sendfile header-based X-Accel-Mapping regex injection enables unauthorized X-Accel-Redirect

Summary

Rack::Sendfile#map_accel_path interpolates the value of the X-Accel-Mapping request header directly into a regular expression when rewriting file paths for X-Accel-Redirect. Because the header value is not escaped, an attacker who can supply X-Accel-Mapping to the backend can inject regex metacharacters and control the generated X-Accel-Redirect response header.

In deployments using Rack::Sendfile with x-accel-redirect, this can allow an attacker to cause nginx to serve unintended files from configured internal locations.

Details

Rack::Sendfile#map_accel_path processes header-supplied mappings using logic equivalent to:

mapping.split(',').map(&:strip).each do |m|
  internal, external = m.split('=', 2).map(&:strip)
  new_path = path.sub(/\A#{internal}/i, external)
  return new_path unless path == new_path
end

Here, internal comes from the HTTP_X_ACCEL_MAPPING request header and is inserted directly into a regular expression without escaping. This gives the header value regex semantics rather than treating it as a literal prefix.

As a result, an attacker can supply metacharacters such as .* or capture groups to alter how the path substitution is performed. For example, a mapping such as:

X-Accel-Mapping: .*=/protected/secret.txt

causes the entire source path to match and rewrites the redirect target to a clean attacker-chosen internal path.

This differs from the documented behavior of the header-based mapping path, which is described as a simple substitution. While application-supplied mappings may intentionally support regular expressions, header-supplied mappings should be treated as literal path prefixes.

The issue is only exploitable when untrusted X-Accel-Mapping headers can reach Rack. One realistic case is a reverse proxy configuration that intends to set X-Accel-Mapping itself, but fails to do so on some routes, allowing a client-supplied header to pass through unchanged.

Impact

Applications using Rack::Sendfile with x-accel-redirect may be affected if the backend accepts attacker-controlled X-Accel-Mapping headers.

In affected deployments, an attacker may be able to control the X-Accel-Redirect response header and cause nginx to serve files from internal locations that were not intended to be reachable through the application. This can lead to unauthorized file disclosure.

The practical impact depends on deployment architecture. If the proxy always strips or overwrites X-Accel-Mapping, or if the application uses explicit configured mappings instead of the request header, exploitability may be eliminated.

Mitigation

  • Update to a patched version of Rack that treats header-supplied X-Accel-Mapping values as literal strings rather than regular expressions.
  • Strip or overwrite inbound X-Accel-Mapping headers at the reverse proxy so client-supplied values never reach Rack.
  • Prefer explicit application-configured sendfile mappings instead of relying on request-header mappings.
  • Review proxy sub-locations and inherited header settings to ensure X-Accel-Mapping is consistently set on all backend routes.

🚨 Rack has Content-Length mismatch in Rack::Files error responses

Summary

Rack::Files#fail sets the Content-Length response header using String#size instead of String#bytesize. When the response body contains multibyte UTF-8 characters, the declared Content-Length is smaller than the number of bytes actually sent on the wire.

Because Rack::Files reflects the requested path in 404 responses, an attacker can trigger this mismatch by requesting a non-existent path containing percent-encoded UTF-8 characters.

This results in incorrect HTTP response framing and may cause response desynchronization in deployments that rely on the incorrect Content-Length value.

Details

Rack::Files#fail constructs error responses using logic equivalent to:

def fail(status, body, headers = {})
  body += "\n"
  [
    status,
    {
      "content-type" => "text/plain",
      "content-length" => body.size.to_s,
      "x-cascade" => "pass"
    }.merge!(headers),
    [body]
  ]
end

Here, body.size returns the number of characters, not the number of bytes. For multibyte UTF-8 strings, this produces an incorrect Content-Length value.

Rack::Files includes the decoded request path in 404 responses. A request containing percent-encoded UTF-8 path components therefore causes the response body to contain multibyte characters, while the Content-Length header still reflects character count rather than byte count.

As a result, the server can send more bytes than declared in the response headers.

This violates HTTP message framing requirements, which define Content-Length as the number of octets in the message body.

Impact

Applications using Rack::Files may emit incorrectly framed error responses when handling requests for non-existent paths containing multibyte characters.

In some deployment topologies, particularly with keep-alive connections and intermediaries that rely on Content-Length, this mismatch may lead to response parsing inconsistencies or response desynchronization. The practical exploitability depends on the behavior of downstream proxies, clients, and connection reuse.

Even where no secondary exploitation is possible, the response is malformed and may trigger protocol errors in strict components.

Mitigation

  • Update to a patched version of Rack that computes Content-Length using String#bytesize.
  • Avoid exposing Rack::Files directly to untrusted traffic until a fix is available, if operationally feasible.
  • Where possible, place Rack behind a proxy or server that normalizes or rejects malformed backend responses.
  • Prefer closing backend connections on error paths if response framing anomalies are a concern.

🚨 Rack::Request accepts invalid Host characters, enabling host allowlist bypass

Summary

Rack::Request parses the Host header using an AUTHORITY regular expression that accepts characters not permitted in RFC-compliant hostnames, including /, ?, #, and @. Because req.host returns the full parsed value, applications that validate hosts using naive prefix or suffix checks can be bypassed.

For example, a check such as req.host.start_with?("myapp.com") can be bypassed with Host: myapp.com@evil.com, and a check such as req.host.end_with?("myapp.com") can be bypassed with Host: evil.com/myapp.com.

This can lead to host header poisoning in applications that use req.host, req.url, or req.base_url for link generation, redirects, or origin validation.

Details

Rack::Request parses the authority component using logic equivalent to:

AUTHORITY = /
  \A
  (?<host>
    \[(?<address>#{ipv6})\]
    |
    (?<address>[[[:graph:]&&[^\[\]]]]*?)
  )
  (:(?<port>\d+))?
  \z
/x

The character class used for non-IPv6 hosts accepts nearly all printable characters except [ and ]. This includes reserved URI delimiters such as @, /, ?, and #, which are not valid hostname characters under RFC 3986 host syntax.

As a result, values such as the following are accepted and returned through req.host:

myapp.com@evil.com
evil.com/myapp.com
evil.com#myapp.com

Applications that attempt to allowlist hosts using string prefix or suffix checks may therefore treat attacker-controlled hosts as trusted. For example:

req.host.start_with?("myapp.com")

accepts:

myapp.com@evil.com

and:

req.host.end_with?("myapp.com")

accepts:

evil.com/myapp.com

When those values are later used to build absolute URLs or enforce origin restrictions, the application may produce attacker-controlled results.

Impact

Applications that rely on req.host, req.url, or req.base_url may be affected if they perform naive host validation or assume Rack only returns RFC-valid hostnames.

In affected deployments, an attacker may be able to bypass host allowlists and poison generated links, redirects, or origin-dependent security decisions. This can enable attacks such as password reset link poisoning or other host header injection issues.

The practical impact depends on application behavior. If the application or reverse proxy already enforces strict host validation, exploitability may be reduced or eliminated.

Mitigation

  • Update to a patched version of Rack that rejects invalid authority characters in Host.
  • Enforce strict Host header validation at the reverse proxy or load balancer.
  • Do not rely on prefix or suffix string checks such as start_with? or end_with? for host allowlisting.
  • Use exact host allowlists, or exact subdomain boundary checks, after validating that the host is syntactically valid.
Release Notes

3.2.6

Full Changelog: v3.2.5...v3.2.6

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 19 commits:


Depfu Status

Depfu will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with @depfu rebase.

All Depfu comment commands
@​depfu rebase
Rebases against your default branch and redoes this update
@​depfu recreate
Recreates this PR, overwriting any edits that you've made to it
@​depfu merge
Merges this PR once your tests are passing and conflicts are resolved
@​depfu cancel merge
Cancels automatic merging of this PR
@​depfu close
Closes this PR and deletes the branch
@​depfu reopen
Restores the branch and reopens this PR (if it's closed)
@​depfu pause
Ignores all future updates for this dependency and closes this PR
@​depfu pause [minor|major]
Ignores all future minor/major updates for this dependency and closes this PR
@​depfu resume
Future versions of this dependency will create PRs again (leaves this PR as is)

@depfu depfu bot added the depfu label Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants