Nov 10, 2026·10 min read·5 visits
An order-of-operations flaw in Rack::Static (CWE-180) causes path-specific header rules to be evaluated before dot-segment path normalization. Attackers can use traversal sequences like /css/../admin/secret.html to retrieve static files stripped of protective HTTP headers such as Content-Security-Policy, X-Frame-Options, and restrictive Cache-Control directives.
CVE-2026-125110 is a path canonicalization mismatch vulnerability in Rack (specifically within the Rack::Static middleware component). When serving static files, Rack normalizes the requested URL path to resolve the physical file on disk, but evaluates configured path-specific header rules against an unnormalized, raw path that has only undergone URL percent-decoding. Unauthenticated remote attackers can exploit this discrepancy by injecting raw or percent-encoded dot-segments into HTTP requests, causing Rack to serve restricted static assets without their intended security headers.
Rack (rack) is the foundational modular web server interface for the Ruby ecosystem, providing a unified API connecting web servers and frameworks such as Ruby on Rails, Sinatra, and Hanami. Among its built-in middleware components is Rack::Static, which intercepts incoming HTTP requests for static assets (such as stylesheets, JavaScript files, images, and static HTML documents) and serves them directly from a configured root directory without invoking the downstream Ruby application.
In addition to basic file resolution, Rack::Static allows developers to define custom HTTP response headers through the :header_rules configuration option. This mechanism maps specific URL path prefixes, regular expressions, or file extensions to sets of HTTP headers. Security teams rely on :header_rules to enforce critical browser-side security policies—including Content-Security-Policy (CSP), X-Frame-Options, Content-Disposition, and Cache-Control—on sensitive directory trees or user-uploaded static assets.
CVE-2026-125110 (also tracked as GitHub Security Advisory GHSA-wvg4-6p58-3gh3) is a path canonicalization mismatch vulnerability classified under CWE-180 (Incorrect Behavior Order: Validate Before Canonicalize). When processing an incoming request, Rack::Static resolves the target file on the filesystem using a normalized path where dot-segments (.. and .) are collapsed. However, the middleware evaluates :header_rules against a path string that has only been percent-decoded, omitting the dot-segment normalization step.
Because of this discrepancy between the file retrieval path and the policy evaluation path, an unauthenticated remote attacker can construct requests containing dot-segments (for example, /public/../admin/restricted.html) that resolve to the target file while evading path-specific rule matchers. Consequently, the web server returns the requested static asset stripped of its protective HTTP response headers.
The root cause of CVE-2026-125110 lies in an inconsistent application of path canonicalization functions within lib/rack/static.rb. Rack provides two distinct utility methods in Rack::Utils for processing URL paths: Rack::Utils.unescape_path, which decodes percent-encoded octets (converting %2F to / and %2E to .), and Rack::Utils.clean_path_info, which canonicalizes path strings by resolving relative dot-segments (.. and .) and collapsing redundant directory separators.
When an HTTP request enters Rack::Static#call, the middleware checks whether the request targets a configured static route and then delegates physical file lookup and delivery to Rack::Files (formerly Rack::File). During this file resolution phase, the path is sanitized using both unescape_path and clean_path_info. This ensures that a request containing relative traversal sequences is safely collapsed to its canonical form within the designated static root before interacting with the underlying operating system filesystem.
Concurrently, Rack::Static invokes its internal helper method applicable_rules(path) to iterate through the @header_rules array and compile the HTTP headers to attach to the response. Prior to the patch, applicable_rules(path) executed ::Rack::Utils.unescape_path(path) on the incoming PATH_INFO string, but failed to wrap the result in ::Rack::Utils.clean_path_info.
This creates a classic time-of-check to time-of-use (TOCTOU) impedance mismatch between policy evaluation and resource access. When an attacker submits a path beginning with an benign prefix followed by a parent directory traversal—such as /css/../admin/secret.html or its percent-encoded equivalent /css/%2E%2E/admin/secret.html—applicable_rules evaluates the literal string /css/../admin/secret.html. Any rule checking if the path starts with /admin/ (via String prefix matching or anchored Regexp matching like /^\/admin\//) evaluates to false, while the file server collapses the path to /admin/secret.html and serves the restricted file.
In lib/rack/static.rb, Rack::Static supports several rule formats within the :header_rules configuration array: :all (matches every file), :fonts (matches common web font extensions), String (matches if the request path starts with the given string prefix), Regexp (matches if the regular expression matches the request path), and Array (matches file extensions).
In affected versions of Rack (< 2.2.25, >= 3.0, < 3.1.23, and >= 3.2, < 3.2.8), the applicable_rules method was implemented as follows:
# File: lib/rack/static.rb (Vulnerable Implementation)
# Convert HTTP header rules to HTTP headers
def applicable_rules(path)
# FLAW: Only decodes percent-encoding; does not collapse '..' or '.' segments
path = ::Rack::Utils.unescape_path(path)
@header_rules.find_all do |rule, new_headers|
case rule
when :all
true
when :fonts
/\.(?:ttf|otf|eot|woff2|woff)\z/.match?(path)
when String
# Prefix check fails if 'path' starts with '/css/../' instead of '/admin/'
path.start_with?(rule)
when Regexp
rule.match?(path)
when Array
/\.(?:#{rule.join('|')})\z/.match?(path)
else
false
end
end
endBecause String rules rely on path.start_with?(rule) and Regexp rules frequently anchor against the leading directory path, any unnormalized dot-segment prepended to the path shifts the starting characters of path. Meanwhile, extension-based rules (:fonts and Array) match against the end of the string (\z) and remain largely unaffected unless dot-segments are appended to the end of the URI in a manner that still resolves on disk.
The official patch, committed across all maintained release branches (18a07fe553afb4d8a678d6a948cd6ba1e11e28be, 9d7f19484ffd5ed5e032678c26ee6eff96d3a0a3, and e88097ccb88be44d06d47b42daffe04929516ee4), resolves the issue by passing the unescaped path directly into ::Rack::Utils.clean_path_info before iterating over @header_rules:
File: lib/rack/static.rb
@@ -168,7 +168,7 @@ def call(env)
# Convert HTTP header rules to HTTP headers
def applicable_rules(path)
- path = ::Rack::Utils.unescape_path(path)
+ path = ::Rack::Utils.clean_path_info(::Rack::Utils.unescape_path(path))
@header_rules.find_all do |rule, new_headers|
case rule> [!NOTE]
> Fix Completeness Assessment: The fix is complete and directly eliminates the canonicalization discrepancy within Rack::Static. By invoking ::Rack::Utils.clean_path_info(::Rack::Utils.unescape_path(path)), applicable_rules now executes the exact same transformation sequence in the exact same order as the underlying file server. Whether the attacker supplies raw dot-segments (..), percent-encoded dot-segments (%2e%2e), or percent-encoded slashes (%2f), unescape_path first converts all encoded characters into literal ASCII characters, and clean_path_info immediately collapses the resulting traversal sequences prior to rule evaluation.
Exploiting CVE-2026-125110 requires no authentication, special privileges, or complex timing. The sole prerequisite is that the target Ruby web application uses Rack::Static configured with path-specific :header_rules (specifically String prefix rules or path-sensitive Regexp rules) to apply security or caching headers to specific static directories.
Consider a Rack application configured with the following middleware options, as demonstrated in the upstream regression test suite (test/spec_static.rb):
opts = {
urls: ["/css", "/admin"],
root: dir,
header_rules: [
[:all, { 'cache-control' => 'public, max-age=100' }],
['/admin/', {
'cache-control' => 'private, no-store',
'x-frame-options' => 'DENY'
}]
]
}In this configuration, any file served under /admin/ is intended to override the global public, max-age=100 cache directive with private, no-store and append x-frame-options: DENY to prevent framing. Note that when urls: ["/css", "/admin"] is configured, Rack::Static#can_serve checks if the incoming path begins with any prefix in urls. By starting the request path with /css, the attacker satisfies the outer middleware routing check (can_serve), ensuring Rack::Static intercepts the request.
An attacker can bypass the /admin/ rule using four distinct payload variations tested in the official regression suite:
Standard Unencoded Dot-Segments:
GET /css/../admin/secret.html HTTP/1.1
Host: target-rack-app.localNote: Because some HTTP clients, browsers, or upstream reverse proxies automatically collapse unencoded /../ sequences before transmitting the request over the wire, attackers operating through standard web browsers or behind normalizing proxies will typically use percent-encoded variants.
Percent-Encoded Dot-Segments:
GET /css/%2E%2E/admin/secret.html HTTP/1.1
Host: target-rack-app.localFully Percent-Encoded Path Segments:
GET /css%2F..%2Fadmin%2Fsecret.html HTTP/1.1
Host: target-rack-app.localMixed Encoded Path Segments:
GET /foo/..%2Fadmin/secret.html HTTP/1.1
Host: target-rack-app.localWhen the unpatched server processes any of these four requests, it returns HTTP status 200 OK with the body of /admin/secret.html. However, because only the :all rule matched during applicable_rules, the response headers contain cache-control: public, max-age=100 and completely omit x-frame-options: DENY.
CVE-2026-125110 is rated with a CVSS v3.1 base score of 5.3 (Medium) via the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N. The vulnerability does not grant arbitrary directory traversal outside the configured Rack::Static root directory (Confidentiality Impact: None), as Rack::Files still restricts file reads to the designated static root. Instead, the impact is centered on the bypass of HTTP response header integrity controls (Integrity Impact: Low).
The concrete security consequences depend directly on which headers an application relies upon Rack::Static to enforce:
Cache-Control: public, max-age=...) while overriding specific directories with Cache-Control: private, no-store, an attacker can request the sensitive asset via a dot-segment URL. Intermediate CDNs, reverse proxies, or shared caches will observe the fallback public directive and cache the confidential document, making it accessible to unauthorized users.X-Frame-Options: DENY or Content-Security-Policy: frame-ancestors 'none' rules can be loaded inside an attacker-controlled <iframe> by referencing the dot-segment URL.Content-Security-Policy (e.g., sandbox or default-src 'none') or X-Content-Type-Options: nosniff to the upload path. Bypassing these rules allows an attacker to upload a malicious SVG or HTML file and direct a victim to the dot-segment URL, executing arbitrary JavaScript in the context of the application's origin.Content-Disposition: attachment to prevent the browser from rendering potentially dangerous file types inline, bypassing the rule causes the browser to render the content directly inside the viewport.According to FIRST EPSS metrics, CVE-2026-125110 currently holds an EPSS score of 0.00251 (0.25% probability of exploitation within 30 days, placing it in the 16.56th percentile), and is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
The primary remediation is to upgrade the rack gem to a patched release that normalizes paths within Rack::Static#applicable_rules. The Rack maintenance team has released fixed versions across all supported branches:
2.2.25 or later.3.1.23 or later.3.2.8 or later.Developers can update their application dependencies using Bundler:
bundle update rackAfter updating, verify the installed version inside Gemfile.lock and restart all application server processes (such as Puma, Unicorn, or Passenger).
> [!NOTE] > If an immediate gem upgrade is not operationally feasible, security teams can implement compensating controls at the reverse proxy layer or within the application's middleware stack.
%2e%2e) before evaluating location blocks or forwarding requests to the Rack backend.Rack::Static: If Rack::Static must be used in an unpatched environment, avoid relying on path-prefix String or anchored Regexp rules to apply restrictive security headers. Instead, apply restrictive headers globally using :all (e.g., setting X-Frame-Options: DENY and Cache-Control: private, no-store as the default for :all), and only use path-specific rules to relax policies for explicitly safe public directories like /css/ or /assets/.Rack::Static. Move these files outside the public document root and serve them through authenticated application controllers where authorization and response headers are enforced programmatically.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
rack Rack | < 2.2.25 | 2.2.25 |
rack Rack | >= 3.0, < 3.1.23 | 3.1.23 |
rack Rack | >= 3.2, < 3.2.8 | 3.2.8 |
| Attribute | Detail |
|---|---|
| CVE / Advisory ID | CVE-2026-125110 / GHSA-wvg4-6p58-3gh3 |
| Weakness Class | CWE-180 (Incorrect Behavior Order: Validate Before Canonicalize) |
| Affected Component | Rack::Static#applicable_rules (lib/rack/static.rb) |
| Attack Vector | Network (Unauthenticated HTTP GET with raw or encoded dot-segments) |
| CVSS v3.1 Score | 5.3 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N) |
| EPSS Score | 0.25% (16.56th Percentile) |
| Security Impact | Bypass of path-specific HTTP security and caching headers (CSP, X-Frame-Options, Cache-Control) |
| Patched Versions | Rack 2.2.25, 3.1.23, 3.2.8 |
The product validates input before it is canonicalized, which prevents the product from detecting data that becomes invalid after the canonicalization step.
CVE-2026-125116 (GHSA-wr5m-5jph-8m96) is a type confusion vulnerability (CWE-843) in Mongoose, the MongoDB object modeling library for Node.js. When resolving discriminator models or schemas via user-supplied discriminator keys (defaulting to `__t`), Mongoose performs direct property indexing on plain JavaScript registry objects without verifying own-property ownership. Supplying inherited `Object.prototype` property names such as `'constructor'` causes Mongoose to resolve built-in functions rather than valid Mongoose constructors, resulting in unhandled runtime exceptions, schema validation bypasses, and persistent collection query denial of service.
A flaw in Mongoose's query condition casting pipeline causes the `sanitizeFilter` configuration option to be silently ignored when defined at the connection level via `mongoose.connect()` or `mongoose.createConnection()`. Because internal query methods inspect `Connection.options` rather than `Connection.config`, applications relying on connection-scoped filter sanitization remain vulnerable to NoSQL query selector injection (CWE-943), permitting authentication bypass and unauthorized data access across Mongoose versions 6.x through 9.x.
CVE-2026-118776 (also tracked as GHSA-2vgq-wchc-25w7) is an improper input and integrity check validation vulnerability affecting the node-forge cryptographic library prior to version 1.4.1. In vulnerable versions, the PKCS#7 unpadding implementations for Cipher Block Chaining (CBC) and Electronic Codebook (ECB) modes only examined the final byte of the decrypted buffer and permitted padding counts up to four times the cipher block size. This defect allowed malformed ciphertexts containing zero-length padding, oversized padding values, or mismatched preceding padding bytes to pass validation, resulting in silent plaintext truncation of up to four blocks and integrity verification bypass in applications relying on unpadding success.
CVE-2026-118774 (GHSA-9wg5-mj3p-v49q) is an improper cryptographic signature verification vulnerability (CWE-347) in node-forge versions prior to 1.4.1. When verifying an X.509 subordinate certificate via certificate.verify(child) or verifyCertificateChain(), the library erroneously selects the RSA padding scheme (e.g., PKCS#1 v1.5 vs. RSASSA-PSS) and RSASSA-PSS parameters from the issuer certificate rather than from the child certificate being verified. This enables an attacker to relabel a PKCS#1 v1.5-signed certificate as an RSASSA-PSS certificate without invalidating the signature check, bypassing downstream security policies that mandate RSASSA-PSS.
CVE-2026-118670 (GHSA-xwgp-w3pj-q5h3) is a high-severity DOM-based Cross-Site Scripting (XSS) vulnerability in OHIF Viewers affecting the dicom-pdf extension module. When processing DICOM Encapsulated Document objects, the viewer extracts the untrusted MIMETypeOfEncapsulatedDocument attribute (tag 0042,0012) and uses it directly to instantiate an in-memory Blob. This Blob is then converted to a same-origin Object URL and embedded into the document via an HTML <object> element. Because browsers prioritize the resource's underlying MIME type over the <object> element's type attribute, an attacker who supplies a crafted DICOM file with a MIME type such as text/html can execute arbitrary JavaScript within the application's origin, compromising session tokens, local storage, and Patient Health Information (PHI).
A Denial of Service (DoS) vulnerability exists in the web display module (urwid/display/web.py) of the Urwid console user interface library for Python in versions prior to 4.0.7. The handle_short_request() function unconditionally encodes HTTP POST input as ASCII without exception handling and opens a FIFO pipe file descriptor without a guaranteed cleanup block. Submitting non-ASCII characters triggers an unhandled UnicodeEncodeError that terminates CGI request processing and leaks the open file descriptor. Additionally, the screen update polling routine decodes Unix domain socket streams iteratively as UTF-8 without exception handling or deterministic socket closure, enabling uncaught UnicodeDecodeError exceptions and socket leaks.