Sep 30, 2026·6 min read·17 visits
A regular expression flaw in league/commonmark allows attackers to bypass the DisallowedRawHtml filter using unclosed tags at block boundaries, resulting in Stored Cross-Site Scripting (XSS).
A security vulnerability in league/commonmark versions 1.3.0 through 2.10.1 allows remote attackers to bypass Stored Cross-Site Scripting (XSS) protections in the DisallowedRawHtml extension. Due to an validation logic flaw in the regular expression parser, specifically handling bare, unclosed HTML blocks ending at the string boundary, raw HTML tags can be passed to the rendered output. When combined with browser-side parsing heuristics, an attacker can execute arbitrary JavaScript in the context of the user session.
The PHP Markdown parser library league/commonmark is widely used to convert Markdown text into compliant HTML. To secure applications from malicious raw HTML insertion, the library provides the DisallowedRawHtml extension (which is enabled automatically under GitHub Flavored Markdown config). This extension is designed to sanitize and escape hazardous HTML tags, such as <script>, <iframe>, or <style>, preventing potential script execution in a client browser.
However, a severe design flaw exists within the input validation structure of the DisallowedRawHtml extension. The module employs a regular expression pattern to intercept and escape dangerous tags. When processing highly malformed or truncated HTML blocks, the parser can fail to match and escape these elements, leaving raw script tags intact within the rendered HTML payload.
This flaw represents a Stored Cross-Site Scripting (XSS) vulnerability. Since the processed Markdown is stored in the database (e.g., comments, forum content, wiki pages) and rendered to other users, attackers can execute arbitrary JavaScript in the sessions of unsuspecting users who view the generated page.
The root cause of this vulnerability lies in an impedance mismatch between the CommonMark HTML block parser and the DisallowedRawHtmlRenderer validation regex. According to the CommonMark and GFM specifications, a line starting with an HTML tag name (such as <script or <iframe) with no trailing brackets or whitespace is interpreted as the beginning of a valid HTML block.
When league/commonmark compiles this into an HtmlBlock node, it retains the exact text segment. This output text ends abruptly at the tag name boundary itself (e.g., <script). It does not append a closing bracket or space. Prior to the fix, the DisallowedRawHtmlRenderer executed the following regular expression to intercept the disallowed tag names:
$regex = \sprintf('/<(\/?(?:%s)[\s\/>])/i', \implode('|', \array_map('preg_quote', $tags)));The trailing character class [\s\/>] requires that the matched tag name be immediately followed by either a space, a forward slash, or a closing angle bracket. Because the compiled HtmlBlock ends exactly with the last character of the tag name (the letter 't' in <script), there is no trailing character available. The regex match fails entirely, and the unescaped tag is passed directly into the final document.
When a browser parses the resulting HTML document, it encounters the raw <script string. To ensure high compatibility with poorly structured web pages, modern web browsers automatically attempt to repair unclosed tags. The browser treats subsequent HTML attributes on the next lines as the attributes of the open <script tag, leading to script execution.
The vulnerability was corrected in commit 411afcc2a7402756d96c89af8882c724d12d47ca by extending the regular expression to support end-of-string boundaries. Below is a comparison of the vulnerable and patched regex implementations in DisallowedRawHtmlRenderer.php.
- $regex = \sprintf('/<(\/?(?:%s)[\s\/>])/i', \implode('|', \array_map('preg_quote', $tags)));
+ // The tag name may also end the rendered HTML: the block parser accepts a bare
+ // `<script` line, and browsers treat the following block as its attributes.
+ $regex = \sprintf('/<(\/?(?:%s)(?:[\s\/>]|$))/i', \implode('|', \array_map('preg_quote', $tags)));In the vulnerable implementation, the regex required an explicit trailing delimiter character. In the patched code, a non-capturing group (?:[\s\/>]|$) was introduced. This alternation ensures that the pattern matches the tag name if it is followed by a standard delimiter OR if it is situated at the absolute end of the target string ($).
This correction is robust and complete. It prevents the exploitation of any block structure where the tag terminates the string, regardless of whether the tag is at the end of the entire document or nested within structural blocks. Variant attacks trying to bypass the check by abusing boundary conditions are neutralized because the regex engine now treats the string boundary as a valid terminator.
To exploit this vulnerability, an attacker must have permission to submit Markdown text that is subsequently rendered to other users. The configuration parameter html_input must be set to allow (the library default), and the DisallowedRawHtml extension must be active (which is typical when using GFM).
An attacker can construct a payload where the disallowed tag is isolated on its own line without any trailing characters, thereby triggering the HTML block parsing rules. The attacker then places the execution context or external resource link on a subsequent block, which the browser's loose parsing engine will merge.
An example payload utilizes a <script tag followed by a blank line and an element that looks like an attribute list:
<div>
<script
<span src="/evil.js">This input translates to the following rendered HTML output:
<div>
<script
<span src="/evil.js">When a browser reads this, it parses the open <script tag, ignores the intermediate whitespace, and interprets the src="/evil.js" attribute as belonging directly to the script element, downloading and executing the remote script.
The impact of this vulnerability is Stored Cross-Site Scripting (XSS). If successfully exploited, the attacker can execute arbitrary client-side JavaScript within the browser context of any user who views the page containing the malicious rendered Markdown.
This execution capability enables several critical attack paths. Attackers can steal session identifiers, session cookies (if not marked with the HttpOnly flag), or access tokens stored in LocalStorage. They can also perform unauthorized actions on behalf of the victim, alter the DOM to phish for user credentials, or deploy client-side keyloggers to capture sensitive user inputs.
The vulnerability is classified with a CVSS v3.1 score of 6.1 (Medium). The vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N highlights that while exploitation requires zero privileges (PR:N) and is of low complexity (AC:L), it does require a victim user to navigate to and view the page containing the injected content (UI:R), and triggers a scope change (S:C) because execution happens in the browser context.
The primary and recommended mitigation is upgrading the league/commonmark package to version 2.10.2 or later. This release updates the core regex logic to handle string boundaries gracefully, ensuring that bare tag names are properly matched and escaped.
To update the dependency in a PHP application utilizing Composer, execute the following command:
composer update league/commonmarkIf upgrading the library is not immediately possible due to dependency constraints, you can temporarily mitigate the vulnerability by adjusting the library configuration. Setting the html_input option to escape or strip prevents the parser from rendering any raw HTML tags, neutralizing the bypass vector.
use League\CommonMark\MarkdownConverter;
$config = [
'html_input' => 'escape', // Escapes all user HTML, neutralizing XSS bypasses
];
$converter = new MarkdownConverter($config);CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
league/commonmark thephpleague | >= 1.3.0, <= 2.10.1 | 2.10.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network (AV:N) |
| CVSS | 6.1 (Medium) |
| EPSS Score | N/A (No CVE assigned) |
| Exploit Status | poc |
| KEV Status | false |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
CVE-2026-128087 (GHSA-9264-jvgf-6w2f) is an incorrect behavior order (CWE-696) and improper authorization (CWE-863) vulnerability in the russh Rust SSH library, affecting versions 0.55.0 through 0.65.0. During the initial SSH key exchange (KEX) handshake, the client state machine invokes the client::Handler::kex_done() callback—providing the negotiated raw shared secret bytes—prior to validating the remote server's host key or certificate via check_server_key(). Consequently, an untrusted or rogue SSH server can complete cryptographic key agreement and force a connecting client to execute side-effecting application logic inside kex_done() before the server's identity is checked and rejected.
CVE-2026-128082 (GHSA-6vhm-j9h7-f9gh) is an uncontrolled recursion vulnerability (CWE-674) in AutoMapper, a convention-based object-object mapper for .NET. When mapping nested or self-referential types into destination models that rely on parameterized constructors, C# positional records, or recursive ConstructUsing expressions, AutoMapper evaluates child constructor arguments prior to incrementing the parent's recursion depth counter. This ordering flaw bypasses both explicit MaxDepth limits and the default depth limit of 64. An unauthenticated remote attacker can supply deeply nested input payloads to affected endpoints, exhausting the thread call stack and triggering an uncatchable System.StackOverflowException that terminates the hosting .NET process.
An information exposure vulnerability in the @opentelemetry/instrumentation-http package for OpenTelemetry JavaScript versions 0.204.0 prior to 0.222.0 causes incoming HTTP server spans to record raw, unredacted query string parameters under the url.query attribute. While outgoing client spans applied built-in and user-configured query parameter redaction, incoming server spans bypassed this sanitization completely, resulting in the transmission of sensitive credentials such as pre-signed cloud URLs and API tokens to external observability backends.
CVE-2026-128089 is an uncontrolled resource consumption vulnerability in the dnspython toolkit for Python, affecting all versions prior to 2.9.2. When parsing Well-Known Service (WKS) records from text via dns.rdtypes.IN.WKS.WKS.from_text(), the parser fails to validate that numeric service port identifiers fall within the RFC 1035 valid range of 0 through 65535. An attacker capable of supplying untrusted zone files or RDATA text strings containing an arbitrarily large service port integer can force the parser to allocate an unbounded bitmap array using an inefficient linear append loop, resulting in severe CPU exhaustion, application hangs, or Out-Of-Memory (OOM) process termination.
CVE-2026-126347 (GHSA-5cjw-f85j-cv6r) is an open redirect vulnerability (CWE-601) in Tornado, an asynchronous Python networking library and web framework. The flaw resides in tornado.web.RedirectHandler when dynamic URL redirection templates incorporate unvalidated user input. Due to a parser differential between RFC 3986 (used by Python's urllib.parse.urlsplit) and the WHATWG URL Standard (used by modern web browsers), inputs containing three or more leading slashes bypass Tornado 6.6.0's validation logic, allowing unauthenticated attackers to redirect users to arbitrary external domains.
A Denial of Service (DoS) vulnerability exists in @sveltejs/adapter-node versions 5.5.7 and 6.0.0-next.0 through 6.0.0. When a SvelteKit server route returns a direct response from a fetch() call with a Content-Type of text/event-stream, the Node.js adapter attempts to mutate the response headers to disable proxy buffering. Because headers returned directly by the WHATWG Fetch API carry an immutable guard, this mutation throws an unhandled TypeError, terminating the underlying Node.js process and denying service to all active and subsequent clients.