Nov 13, 2026·9 min read·3 visits
A quadratic Regular Expression Denial of Service (ReDoS) vulnerability exists in rails-html-sanitizer (>= 1.0.3, < 1.7.2) when scrubbing SVG href and xlink:href attributes. Applications running on Ruby 3.1 or earlier (or JRuby) that customize their HTML sanitizer allowlist to permit SVG reference tags can suffer worker thread exhaustion when processing crafted multiline whitespace payloads.
CVE-2026-128068 (GHSA-cpgf-5mjf-73p7) is a Regular Expression Denial of Service (ReDoS) vulnerability in the rails-html-sanitizer Ruby gem affecting versions 1.0.3 through 1.7.1. The flaw resides in the Rails::HTML::PermitScrubber#scrub_attribute method, which uses the multiline regular expression /^\s*[^#\s].*/m to enforce local-only URI fragment references on SVG elements. On Ruby runtimes prior to version 3.2 (CRuby <= 3.1 and JRuby), evaluating an attribute value composed of repeated newline and whitespace characters without a hash character triggers quadratic backtracking O(n^2). Applications are only vulnerable if their sanitization allowlist is explicitly configured to permit SVG reference elements such as <use> alongside href or xlink:href attributes.
The rails-html-sanitizer gem is the standard library responsible for sanitizing HTML fragments in Ruby on Rails applications, powering Action View helpers such as sanitize. Internally, the gem builds upon Loofah and Nokogiri to parse HTML trees and filter elements and attributes against configurable allowlists via scrubber classes, primarily Rails::HTML::PermitScrubber and Rails::HTML::SafeListSanitizer.
CVE-2026-128068 (also tracked as GHSA-cpgf-5mjf-73p7) is an Inefficient Regular Expression Complexity vulnerability (CWE-1333) leading to Uncontrolled Resource Consumption (CWE-400) within Rails::HTML::PermitScrubber#scrub_attribute. The vulnerability is triggered when the scrubber validates href or xlink:href attributes on allowed SVG elements to ensure they only contain local fragment identifiers (values starting with #).
Because the validation logic uses a line-anchored regular expression (/^\s*[^#\s].*/m), an attacker can supply an attribute value consisting of thousands of newline characters (\n) without a # character. On Ruby runtimes lacking linear-time regular expression memoization—specifically CRuby 3.1 and earlier, as well as JRuby—the regular expression engine repeatedly rescans the trailing whitespace sequence from every newline boundary, resulting in quadratic $\mathcal{O}(n^2)$ CPU consumption.
> [!NOTE]
> Default configurations of rails-html-sanitizer and Rails Action View are not affected. Exploitation requires the application to override the default tag and attribute allowlists to permit both an SVG reference element (such as <use>) and URI reference attributes (href or xlink:href).
To prevent SVG elements from loading external resources—which can introduce Cross-Site Scripting (XSS) or Server-Side Request Forgery (SSRF) vectors—Rails::HTML::PermitScrubber#scrub_attribute checks whether an element is listed in Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF and whether the attribute is in Loofah::HTML5::SafeList::SVG_HREF_ATTRIBUTES. If both conditions hold, the scrubber tests the attribute value against the regular expression /^\s*[^#\s].*/m. If the pattern matches, the attribute is considered a non-local reference and is removed from the DOM node.
The root cause of the algorithmic complexity flaw lies in how Ruby's regular expression engine (Onigmo) interprets anchors and character classes in multiline mode:
^) vs. String Anchor (\A): In Ruby regular expressions, the caret ^ matches the beginning of any line (immediately following any \n character) by default, whereas \A matches exclusively at the beginning of the string.\s): The character class shorthand \s matches spaces, tabs, and newline characters ([ \t\r\n\f\v]).[^#\s]): After ^\s* consumes all contiguous whitespace and newlines to the end of the string, the engine attempts to match [^#\s] (any character that is neither # nor whitespace).When the input string consists exclusively of $n$ newline characters (or whitespace interspersed with newlines) and contains no non-whitespace characters, the initial attempt starting at index $0$ consumes all $n$ bytes via \s* and fails at [^#\s]. Because \s* is greedy, the engine backtracks one character at a time down to index $0$, testing each position against [^#\s] (which fails because every character is whitespace).
Once backtracking at index $0$ is exhausted, the engine does not terminate. Because the pattern begins with ^ rather than \A, the engine advances to the next line boundary (index $1$, following the first \n) and repeats the entire scan across the remaining $n - 1$ characters. Across $n$ newline characters, the total number of character comparisons scales quadratically:
$$\sum_{i=1}^{n} (n - i) = \frac{n(n - 1)}{2} = \mathcal{O}(n^2)$$
In CRuby 3.2 and later, the Onigmo engine incorporates a memoization optimization for non-backreferencing regular expressions, bounding execution to $\mathcal{O}(n)$ linear time. However, CRuby 3.1 and earlier, as well as JRuby, execute the full backtracking and line-rescanning loop.
Prior to version 1.7.2, lib/rails/html/scrubbers.rb maintained its own attribute-scrubbing logic inside PermitScrubber#scrub_attribute, duplicating functionality that also existed upstream in Loofah. The vulnerable implementation checked URI attributes, SVG reference attributes, and SVG local href attributes sequentially:
# Vulnerable code in lib/rails/html/scrubbers.rb (< 1.7.2)
def scrub_attribute(node, attr_node)
attr_name = if attr_node.namespace
"#{attr_node.namespace.prefix}:#{attr_node.node_name}"
else
attr_node.node_name
end
return if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name) &&
Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node)
if Loofah::HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name)
Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node)
end
# VULNERABLE CHECK: ^ anchors to every line start, causing O(n^2) rescanning
if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) &&
Loofah::HTML5::SafeList::SVG_HREF_ATTRIBUTES.include?(attr_name) &&
attr_node.value =~ /^\s*[^#\s].*/m
attr_node.remove
end
node.remove_attribute(attr_node.name) if attr_name == "src" && attr_node.value !~ /[^[:space:]]/
Loofah::HTML5::Scrub.force_correct_attribute_escaping!(node)
endIn commit f776f963b9d9346271aade9cb903e793fd38e865, the maintainers resolved the vulnerability by removing the custom regular expression and delegating attribute value validation to Loofah::HTML5::Scrub.scrub_attribute_value, coupled with bumping the minimum required loofah dependency to >= 2.25.3 (which Fixed the equivalent regex in Loofah):
# Patched code in lib/rails/html/scrubbers.rb (1.7.2)
def scrub_attribute(node, attr_node)
# Extract normalized attribute name including namespace prefix via Loofah
attr_name = Loofah::HTML5::Scrub.prefixed_attribute_name(attr_node)
# Delegate URI and SVG local-reference scrubbing to Loofah >= 2.25.3
# Returns true only if the attribute was completely removed by URI scrubbing
return if Loofah::HTML5::Scrub.scrub_attribute_value(node, attr_node, attr_name)
node.remove_attribute(attr_node.name) if attr_name == "src" && attr_node.value !~ /[^[:space:]]/
Loofah::HTML5::Scrub.force_correct_attribute_escaping!(node)
endThe patch in rails-html-sanitizer 1.7.2 completely eliminates the vulnerable /^\s*[^#\s].*/m regular expression from the gem's codebase and consolidates sanitization rules into upstream Loofah (>= 2.25.3). In addition, the patch adds unit tests verifying edge cases where a local reference is followed by a newline and an external URL (e.g., href="#p\nhttp://bad.com/x"), ensuring that removing the ^ line-anchor trick does not reintroduce a bypass allowing multiline external references.
Furthermore, in commit c5252d798b283f29bf0a590ee1a63e9cb16bafa2 (also included in the 1.7.2 release), the gem updated spec.required_ruby_version = ">= 3.2.0", ensuring that future installations of rails-html-sanitizer run on Ruby versions with built-in linear-time regular expression matching.
Exploiting CVE-2026-128068 requires three distinct conditions to be satisfied simultaneously:
Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF (such as <use>, <animate>, <feImage>, <filter>, <linearGradient>, <mpath>, <pattern>, <radialGradient>, <set>, or <textPath>) and at least one attribute in Loofah::HTML5::SafeList::SVG_HREF_ATTRIBUTES (href or xlink:href).rails-html-sanitizer versions >= 1.0.3 and < 1.7.2.When a Rails controller or view passes user-controlled HTML to sanitize or Rails::HTML::SafeListSanitizer with SVG elements enabled, an unauthenticated attacker can submit an HTTP request containing an <svg><use href="..."></use></svg> fragment filled with newline characters (0x0A or HTML entity ). Note that Nokogiri decodes numerical character references inside attribute values prior to invoking the scrubber's scrub_attribute method.
require "rails-html-sanitizer"
require "benchmark"
sanitizer = Rails::HTML::SafeListSanitizer.new
# Generate a 20 KB payload of newline characters without a '#' character
payload_size = 20_000
malicious_input = "<svg><use href=\"#{"\n" * payload_size}\"></use></svg>"
time = Benchmark.realtime do
sanitizer.sanitize(
malicious_input,
tags: %w[svg use],
attributes: %w[href xlink:href]
)
end
puts "Sanitization took #{time.round(3)} seconds"On CRuby 3.1.7, a 20 KB attribute payload consumes approximately 3.4 seconds of CPU time on a single thread. Because quadratic complexity scales by $4\times$ for every $2\times$ increase in payload size:
Because Ruby's regular expression engine holds the Global VM Lock (GVL) during C-level regex evaluation on CRuby 3.1, a single request processing an 80 KB payload blocks all other Ruby threads within the same OS process for nearly a minute. In typical multi-process web servers like Puma or Unicorn, an attacker sending a small burst of concurrent requests can saturate all available worker processes, causing request queuing and HTTP 502/504 gateway timeouts across the entire application.
The security impact of CVE-2026-128068 is restricted to Availability. There is no mechanism through this vulnerability to bypass HTML sanitization rules, inject arbitrary scripts, read application memory, or modify server data.
The vulnerability is rated 5.3 (Medium) with vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L:
As of disclosure, CVE-2026-128068 is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and no active exploitation in the wild or ransomware integration has been observed.
Upgrade rails-html-sanitizer to version 1.7.2 or later, which automatically pulls in loofah >= 2.25.3 and removes the vulnerable regular expression:
bundle update rails-html-sanitizer loofahNote that rails-html-sanitizer 1.7.2 requires Ruby >= 3.2.0. If your application is locked to Ruby 3.1 or earlier and cannot upgrade rails-html-sanitizer to 1.7.2, apply the runtime or configuration mitigations below.
Upgrading the Ruby interpreter to CRuby 3.2.0 or newer resolves the quadratic backtracking behavior at the language engine level. Ruby 3.2's memoized regular expression matcher evaluates /^\s*[^#\s].*/m in $\mathcal{O}(n)$ linear time even if rails-html-sanitizer remains on a pre-1.7.2 release.
If neither the gem nor the Ruby runtime can be immediately upgraded, apply one or more of the following workarounds:
use, animate, feImage, filter, linearGradient, mpath, pattern, radialGradient, set, textPath) or remove href and xlink:href from custom sanitizer allowlists (config.action_view.sanitized_allowed_tags and config.action_view.sanitized_allowed_attributes).PermitScrubber, strip or truncate attribute values containing excessive whitespace runs, or override scrub_attribute in a subclass to replace /^\s*[^#\s].*/m with an anchored check (!attr_node.value.strip.start_with?('#')).CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
rails-html-sanitizer Rails | >= 1.0.3, < 1.7.2 | 1.7.2 |
loofah flavorjones | < 2.25.3 | 2.25.3 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-128068 (GHSA-cpgf-5mjf-73p7) |
| Weakness Class | CWE-1333 (Inefficient Regular Expression Complexity) / CWE-400 |
| CVSS v3.1 Score | 5.3 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L) |
| Affected Component | Rails::HTML::PermitScrubber#scrub_attribute (rails-html-sanitizer >= 1.0.3, < 1.7.2) |
| Affected Runtimes | CRuby <= 3.1 and JRuby (CRuby >= 3.2 is unaffected) |
| Impact | Quadratic O(n^2) CPU exhaustion / Worker Thread Denial of Service |
| Exploit Status | PoC / Algorithmic Complexity (Not in CISA KEV) |
| Patched Version | rails-html-sanitizer 1.7.2 (with loofah >= 2.25.3) |
The software uses a regular expression with an inefficient, possibly exponential or quadratic worst-case computational complexity that allows an attacker to cause a denial of service by crafting input that triggers excessive backtracking.
CVE-2026-126360 (GHSA-mf5v-w3w3-f6cg) is an algorithmic complexity vulnerability (Regular Expression Denial of Service) in the trace context propagation logic of the opentelemetry-api package within the OpenTelemetry Python ecosystem. In versions prior to 1.45.2, the TraceContextTextMapPropagator.extract() method utilizes a regular expression susceptible to quadratic-time backtracking when parsing malformed W3C traceparent headers containing long sequences of whitespace followed by an embedded newline character. In Python applications—particularly those using single-threaded asynchronous event loops—an unauthenticated remote attacker transmitting payloads over newline-preserving carriers can monopolize CPU execution and induce application-level Denial of Service.
CVE-2026-126359 (GHSA-3gg2-335m-rxc6) is an improper input neutralization vulnerability (CWE-79) in the Hono web framework (`honojs/hono`). Versions from 3.12.0 prior to 4.13.16 evaluate and escape interpolated strings in the `hono/css` helper individually before concatenating template chunks and stripping CSS comments. When a `<` character and a `/style>` sequence are split across template boundaries, adjacent interpolations, or separated by a CSS comment, the sanitizer fails to escape the closing tag sequence. During Server-Side Rendering (SSR), the `<Style />` component outputs an unescaped `</style>` delimiter, permitting an attacker to break out of the CSS raw text context and execute arbitrary HTML and JavaScript in the victim's browser.
An incorrect authorization vulnerability (CWE-863) exists in the Hono web application framework (npm package `hono`) from version 4.1.4 prior to 4.13.16. Flaws in the registration-time coverage calculation and regular expression compilation within `RegExpRouter` cause wildcard middleware to be omitted under three specific routing conditions: parameter pattern equivalence mismatches, wildcard boundary collisions, and request paths containing percent-encoded line terminators. Because Hono's default `SmartRouter` selects `RegExpRouter` for most standard web applications, unauthenticated remote clients can bypass authentication, authorization, or security middleware registered on wildcard paths to directly access protected route handlers or the application's not-found handler.
CVE-2026-126357 (GHSA-4m76-xfj5-7975) is an authentication bypass vulnerability in the Hono web application framework prior to version 4.13.16. The flaw resides in the prefix-stripping and URL reconstruction logic of `app.mount()` and the `mount()` middleware. Two distinct path transformation errors—static route-pattern length slicing on dynamic prefixes and URL normalization of decoded pathnames—create a routing discrepancy between the outer Hono router and the mounted sub-application. An unauthenticated attacker can exploit these discrepancies to bypass outer authentication or authorization middleware and access protected inner routes directly.
A parser differential vulnerability exists in the Hono web application framework versions 4.5.1 prior to 4.13.16. The built-in form validation middleware (`validator('form')`) utilizes an overly restrictive regular expression to evaluate the HTTP `Content-Type` header for `multipart/form-data` payloads. When a client supplies additional valid MIME parameters (such as `charset=utf-8`) or multiple whitespace characters following a semicolon delimiter, the regular expression fails to match. Consequently, the validator passes an empty object to the validation callback instead of the parsed form fields. If the validation logic permits empty objects or only checks for disallowed values on optional properties, the request passes validation, allowing downstream route handlers calling `c.req.parseBody()` or `c.req.formData()` to process unvalidated user input.
CVE-2026-126355 (GHSA-46wx-v44x-9hh2) is a Cross-Site Request Forgery (CSRF) validation bypass in Hono, a web application framework for JavaScript runtimes. Prior to version 4.13.16, Hono's built-in csrf() middleware only evaluated origin headers for requests matching standard HTML form MIME types (application/x-www-form-urlencoded, multipart/form-data, and text/plain). By leveraging the HTML anchor ping attribute in Chromium-based browsers, an attacker can initiate an unprompted, credentialed cross-origin HTTP POST request with Content-Type: text/ping without triggering a CORS preflight request. Because text/ping was omitted from the middleware regular expression, requests bypassed Origin and Sec-Fetch-Site verification, allowing unauthorized state-changing operations.