Update dependency league/commonmark to v2.9.0 [SECURITY] - #6192
Open
renovate[bot] wants to merge 1 commit into
Open
Update dependency league/commonmark to v2.9.0 [SECURITY]#6192renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
SanderMuller
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
2.8.2→2.9.0Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
league/commonmark has an embed extension allowed_domains bypass
CVE-2026-33347 / GHSA-hh8v-hgvp-g3f5
More information
Details
Impact
The
DomainFilteringAdapterin the Embed extension is vulnerable to an allowlist bypass due to a missing hostname boundary assertion in the domain-matching regex. An attacker-controlled domain likeyoutube.com.evilpasses the allowlist check whenyoutube.comis an allowed domain.This enables two attack vectors:
OscaroteroEmbedAdaptermakes server-side HTTP requests to the embed URL via theembed/embedlibrary. A bypassed domain filter causes the server to make outbound requests to an attacker-controlled host, potentially probing internal services or exfiltrating request metadata.EmbedRendereroutputs the oEmbed response HTML directly into the page with no sanitization. An attacker controlling the bypassed domain can return arbitrary HTML/JavaScript in their oEmbed response, which is rendered verbatim.Any application using the
Embedextension and relying onallowed_domainsto restrict domains when processing untrusted Markdown input is affected.Patches
This has been patched in version 2.8.2. The fix replaces the regex-based domain check with explicit hostname parsing using
parse_url(), ensuring exact domain and subdomain matching only.Workarounds
Embedextension, or restrict its use to trusted usersEmbedAdapterInterfaceSeverity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:L/SI:L/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
CommonMark has DisallowedRawHtml extension bypass via whitespace in HTML tag names
CVE-2026-30838 / GHSA-4v6x-c7xx-hw9f
More information
Details
Impact
The
DisallowedRawHtmlextension can be bypassed by inserting a newline, tab, or other ASCII whitespace character between a disallowed HTML tag name and the closing>. For example,<script\n>would pass through unfiltered and be rendered as a valid HTML tag by browsers. This is a cross-site scripting (XSS) vector for any application that relies on this extension to sanitize untrusted user input.All applications using the
DisallowedRawHtmlextension to process untrusted markdown are affected. Applications that use a dedicated HTML sanitizer (such as HTML Purifier) on the rendered output are not affected.Patches
Fixed in 2.8.1. The regex character class
[ \/>]was changed to[\s\/>]to match all whitespace characters that browsers accept as valid tag name terminators.Workarounds
html_inputconfiguration option to'escape'or'strip'to disable all raw HTML, though this is a broader restriction than theDisallowedRawHtmlextension provides.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:L/VA:N/SC:L/SI:L/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
league/commonmark: AttributesExtension href/src unsafe-link filter bypass via embedded control bytes
CVE-2026-71478 / GHSA-29pj-957v-52mc
More information
Details
## Summary
The
AttributesExtension'shref/srcunsafe-link filter (AttributesHelper::filterAttributes()) can be bypassed by embedding control bytes in ajavascript:URL that browsers discard before parsing the scheme. Two variants:java<TAB>script:alert(1). Per the WHATWG URL Standard's "basic URL parser" step 3, browsers "remove all ASCII tab or newline from input".<0x01>javascript:alert(1). Per step 1 of the same algorithm, browsers remove any leading or trailing C0 control or space. (A leading space alone does not bypass, becauseparseAttributes()alreadytrim()s the value; other C0 bytes are not trimmed.)The filter is a literal anchored-prefix regex (
RegexHelper::isLinkPotentiallyUnsafe()/REGEX_UNSAFE_PROTOCOL) that matches neither obfuscated form, so in both cases the browser still executesjavascript:alert(1).This is confirmed reproducible even with
allow_unsafe_links => falseset — i.e. even applications that have followed the library's own documented hardening guidance for untrusted input remain exploitable.This is a sibling gap in the same defense that CVE-2025-46734 (GHSA-3527-qv2q-pfvx) fixed in v2.7.0 — that fix made
href/srcrespectallow_unsafe_links, but did not normalize control bytes before checking, so these obfuscation techniques were never covered.Vulnerability
Files:
src/Util/RegexHelper.php:69(REGEX_UNSAFE_PROTOCOL),:239-242(isLinkPotentiallyUnsafe())src/Extension/Attributes/Util/AttributesHelper.php:149-179(filterAttributes())CWE: CWE-79 (Improper Neutralization of Input During Web Page Generation / XSS) — primary
REGEX_UNSAFE_PROTOCOLis incomplete. This is a composite of CWE-184 and CWE-79, so it captures the full "incomplete denylist → XSS" chain on its own.Root Cause
The Attributes extension's own quote-value grammar (
PARTIAL_DOUBLEQUOTEDVALUE = '"[^"]*"') accepts any byte except"inside quotes, including raw tab/CR/LF and other C0 controls, andparseAttributes()onlytrim()s (leading/trailing, and only the default charlist" \t\n\r\0\x0B"— so a leading\x01survives). Critically, the core Markdown link-destination path (LinkParserHelper→UrlEncoder::unescapeAndEncode()) percent-encodes every control byte before this same safety check ever runs — but the Attributes extension'shref/srchandling has no equivalent normalization step, so the raw control byte reaches both the check and the final HTML output (Xml::escape()only escapes& < > " ', not tab/CR/LF, since they're legal bytes inside an HTML attribute).Attack Scenario
AttributesExtensionand setsallow_unsafe_links => false— the project's own documented hardening step for untrusted input.[Click me](javascript:alert(0)){href="java<TAB>script:alert(document.cookie)"}(TAB is one literal 0x09 byte).<a href="java<TAB>script:alert(document.cookie)">Click me</a>—isLinkPotentiallyUnsafe()doesn't match the tab-split scheme, so the filter takes no action.javascript:alert(document.cookie)in the victim's session — stored XSS, cookie theft, account takeover potential.Why the payload needs an unsafe core destination. Step 2 above deliberately uses
[Click me](javascript:alert(0))rather than a normal link.LinkRendereroverwritesattrs['href']with the node's own URL unless that URL is itself judged unsafe — so[x](https://example.com){href="java<TAB>script:..."}renders the harmlesshref="https://example.com", and an empty destination[x](){href="..."}rendershref="". The attacker therefore supplies a core destination that the filter does catch, which suppresses the overwrite and lets the attribute-suppliedhrefreach the final tag. This is no obstacle in practice — the attacker writes the entire Markdown document.Two related forms that are not exploitable, noted so the fix isn't over-scoped:
hi {href="java<TAB>script:alert(1)"}— does bypass the filter and emits<p href="java<TAB>script:alert(1)">, buthrefon a<p>is inert: there is nothing to navigate. (An earlier draft of this report described this as a "simpler, unconditional variant" of the attack; it is a filter bypass, not an XSS.)<img src>is unaffected, sinceImageRendererunconditionally overwritessrcfrom the core URL regardless of the safety verdict.Recommended Fix
Normalize inside
RegexHelper::isLinkPotentiallyUnsafe()before testing, mirroring the WHATWG URL parser's own normalization. This covers both variants, fixes every call site at once (LinkRenderer,ImageRenderer, and any third-party callers), and needs no changes in the Attributes extension.Affected Versions
>= 1.5.0, <= 2.8.3- every release that ships theAttributesExtension. Verified by installing each version and rendering the payloads withallow_unsafe_links => false. The attribute-value grammar (PARTIAL_DOUBLEQUOTEDVALUE = '"[^"]*"') has accepted raw control bytes since the extension was introduced, and none of the intervening parser rewrites narrowed it.Prior Related Advisories
GHSA-3527-qv2q-pfvx / CVE-2025-46734 fixed a different Attributes-extension XSS (unallowlisted
on*handlers,href/srcnot respectingallow_unsafe_linksat all) in v2.7.0. This issue bypasses the specifichref/srcprotection that fix introduced (the control-byte normalization gap was not part of that fix) - but the obfuscated inputs also work on older versions.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
league/commonmark: Quadratic-time denial of service when parsing crafted Markdown
CVE-2026-71488 / GHSA-2q4p-g7hv-5rgv
More information
Details
Impact
Affected versions of
league/commonmarkcan have quadratic time complexity when parsing specially crafted Markdown lines. In practical terms, doubling the length of an affected line can make the parser perform roughly four times as much work. The parser identifies locations using character positions, but regular-expression matches report byte positions. These positions differ when a UTF-8 character uses more than one byte. Several parsing paths repeatedly rescan growing portions of the line to translate between the two positions. The Autolink extension can also copy and validate the remaining line at every URL-like prefix.In current 2.x releases, a single non-ASCII character anywhere on a line can place that whole line on the slower multibyte path. An attacker can combine it with a long run of leading whitespace or repeated Markdown punctuation, causing increasingly large rescans. When the Autolink extension is enabled, repeated URL-like prefixes provide another trigger, even on ASCII-only lines. Each trigger fits within one long line, so complex Markdown structure is unnecessary.
An attacker who can submit Markdown for conversion can use a comparatively small request to consume disproportionate CPU time and allocation activity. Repeated or concurrent requests can occupy all available PHP workers and prevent legitimate requests from completing. The core paths affect
CommonMarkConverter,GithubFlavoredMarkdownConverter, and custom environments. The autolink-specific path affects applications usingAutolinkExtensionorGithubFlavoredMarkdownExtension. Applications that process only trusted Markdown are not remotely exploitable. The impact is limited to availability: it does not disclose data, change rendered output, or bypass rendering restrictions. Settings such ashtml_inputandallow_unsafe_linksdo not mitigate the issue because the expensive work occurs before rendering.Patches
The issue is patched in
2.9.0and later. Starting in that release, the parser records UTF-8 character-to-byte positions incrementally, converts ordered regular-expression match positions without restarting from the beginning of the line, and matches autolinks against the original line instead of copying every remaining suffix. The affected work then grows in direct proportion to the input size while preserving existing Markdown output and configuration behavior. Versions from0.6.0through2.8.3are affected. The 0.x and 1.x release lines are no longer supported, so their users must upgrade to2.9.0or later.Workarounds
If you cannot upgrade immediately, reject or truncate inputs with excessively long individual lines before passing them to the converter. A total request-size limit is also useful, but a per-line limit is important because every demonstrated trigger fits on one line. Choose limits appropriate for the application and enforce them before Markdown parsing begins. Restricting conversion to trusted users, applying strict execution-time limits, rate-limiting requests, and limiting concurrent conversions can further reduce exposure, but these measures are not complete substitutes for upgrading.
Disabling
AutolinkExtensionand avoidingGithubFlavoredMarkdownExtensionremoves the autolink-specific trigger, but the core multibyte parsing paths remain reachable in the standard parser. Existing nesting, delimiter, raw-HTML, and unsafe-link configuration options do not eliminate all affected paths. Applications that must continue processing untrusted Markdown should therefore enforce input limits even when autolinking is disabled.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
league/commonmark: Denial of service via adjacent inline attribute blocks
GHSA-g2gp-3wwq-f4ph
More information
Details
Impact
With the Attributes extension enabled,
AttributesListener::findTargetAndDirection()resolves each attribute node's target by walking outward through its siblings. For a run of N adjacent inline attribute blocks placed at the start of a block (with nothing to their left), each node scans the entire sibling list to the far-right end before giving up and falling back to the parent. Each resolution is therefore Θ(N) and the whole run is Θ(N²).Reaching the path requires
AttributesExtension(opt-in, but first-party:League\CommonMark\Extension\Attributes\AttributesExtension). No other configuration matters — the quadratic walk runs unconditionally during parsing and is not gated by theattributes/allowallow-list, theon*hardening added in 2.7.0, orallow_unsafe_links. An unauthenticated attacker can submit a ~32 KB input ({#a}repeated 8,000 times) that takes over 5 seconds to convert, with time growing quadratically in input length — a cheap denial of service. Availability impact only. The Attributes extension was introduced in 1.5.0 (May 2020) with this outward-walk resolver present from the first commit, so all releases from 1.5.0 onward (including every 2.x) are affected.Workarounds
There is no library-level configuration that gates the quadratic walk. Integrators who cannot upgrade can only reduce exposure indirectly:
Upgrading to a release containing the fix is recommended.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
league/commonmark: Denial of service via colliding heading slugs
GHSA-mh25-x5hq-wrqp
More information
Details
Impact
UniqueSlugNormalizer::normalize()makes each slug document-unique by searching for an unused numeric suffix, but restarts that search from1on every collision. The k-th heading that collapses to the same base slug performs k−1 array lookups, so K colliding slugs cost Σ(k−1) = O(K²). An attacker can force every heading onto a single base slug trivially — many empty ATX headings, identical heading text, or punctuation-only headings that normalize to the empty string.The path is reached whenever the shared slug normalizer runs over attacker-controlled text. That happens when
HeadingPermalinkExtensionis registered (itsHeadingPermalinkProcessornormalizes every heading), independently throughFootnoteExtension(itsAnonymousFootnoteRefParsernormalizes every^[label]reference), and on anyTableOfContentsExtensionsite (which requiresHeadingPermalinkExtensionto be co-registered). The defaultslug_normalizer/uniquesetting (UniqueSlugNormalizerInterface::PER_DOCUMENT) accumulates collisions across the whole document. No authentication is required — a small document body turns into seconds of CPU and denies service. Availability impact only.UniqueSlugNormalizerwas introduced in 2.0.0 (first shipped in 2.0.0-beta1, May 2021); the 1.x heading-permalink slug generator performed no de-duplication and is not affected. All 2.x releases (including 2.8.x) are affected.Workarounds
Integrators who cannot upgrade immediately can:
slug_normalizer/uniquetofalse/UniqueSlugNormalizerInterface::DISABLED, which stops the de-duplication scan entirely — at the cost of losing id uniqueness (colliding headings then share an anchor).HeadingPermalinkExtension(andTableOfContentsExtension, which depends on it), andFootnoteExtensionwhere anonymous footnotes reach the same normalizer, for untrusted Markdown.Each of these trades off functionality or correctness; upgrading to the patched release (which removes the quadratic behavior while keeping unique ids and identical output) is the recommended remediation.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
league/commonmark: Denial of service via duplicate footnote definitions
GHSA-jfm3-95jq-q3rf
More information
Details
Impact
The Footnote extension records one backref per footnote reference and then appends the entire backref list for every footnote definition block in the document, without ever de-duplicating or removing repeated definitions of the same label (
GatherFootnotesListener, populated byNumberFootnotesListener). A document that references a single label N times and also supplies N duplicate[^a]:definitions of that label therefore produces N × NFootnoteBackrefnodes, so output size, parse time, and peak memory are all O(N²).Reaching the vulnerable path requires
FootnoteExtensionto be registered on theEnvironment. This is opt-in, but is a commonly enabled GFM-style feature; no other non-default configuration is required. An unauthenticated attacker can expand a ~10 KB request into a ~62 MB HTML response, ~3 s of CPU, and ~440 MB of peak memory — enough to OOM-kill a default 128 MB PHP worker and deny service. Availability impact only; no confidentiality or integrity effect. The Footnote extension was introduced in 1.5.0 (May 2020) with this backref logic present from the first commit, so all releases from 1.5.0 onward (including every 2.x through 2.8.x) are affected.Workarounds
There is no library-level option to cap the number of footnotes, references, or definitions, so no configuration switch prevents the amplification. Integrators who cannot upgrade should:
Upgrading to the patched release is the recommended remediation.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
league/commonmark: Denial of service via deeply nested XML output
GHSA-mj63-m3rc-8ppr
More information
Details
Impact
XmlRendererpretty-prints XML by emitting depth-proportional indentation whitespace for every opening and closing tag. For a tree of depth n, the indentation alone sums to O(n²) bytes of output (and corresponding memory), reachable throughMarkdownToXmlConverter— e.g.str_repeat('> ', $depth) . "x\n", a single line of nested blockquotes — or through a directXmlRenderer::renderDocument()call on an attacker-influenced AST.This affects applications that convert untrusted Markdown to XML, which is an opt-in output path. The parser's
max_nesting_levelbounds the depth of parser-created trees, but its default is high enough to reach damaging sizes, can be raised by the host application, and does not constrain custom or programmatically built ASTs handed straight to the renderer. The result is a memory / output-size amplification rather than a hard crash, which is why this issue is rated Medium rather than High. No confidentiality or integrity impact. XML rendering was introduced in 2.0.0 (first shipped in 2.0.0-beta1, June 2021) and has emitted depth-proportional indentation ever since, so all 2.x releases are affected (verified against 2.8.x, clean upstream1902f60f). 1.x has no XML renderer and is not affected.Workarounds
Applications converting untrusted Markdown to XML should:
max_nesting_levelto a conservative value appropriate to expected content, so the parser refuses to build extremely deep trees. This is the most direct lever for parser-produced ASTs, but does not protect trees built programmatically and passed straight toXmlRenderer.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
thephpleague/commonmark (league/commonmark)
v2.9.0Compare Source
This is a security release to address five denial of service vulnerabilities and one cross-site scripting (XSS) vulnerability.
Added
NormalizeHeadingsExtensionto constrain headings to a configured level range (#989)normalize_headings/rebase_to_min_level- rebases each document so its headings begin atmin_levelfootnote/enable_inline_footnotesconfig option to disable the inline^[Footnote text]syntax (#1112)Cursor::getBytePosition()for obtaining the cursor's current byte offset within the linexml/max_indentation_levelconfig option to control how farXmlRendererindents nested elements (default:16; set to0for unindented output)Changed
FootnoteExtensionnow uses only the first definition of a footnote label, removing any duplicate definitions instead of rendering them in placeNumberFootnotesListenernow stores footnote backrefs under a singlefootnote/backrefskey in the document data instead of one key per footnote destinationCursorto translate character positions to byte offsets in constant time instead of re-decoding the line withmb_substr()Cursor::match()to match against the line at the cursor's byte offset instead of copying the remaining line on every callInlineParserEngineandUrlAutolinkParserto work with byte offsets directlyFixed
java<TAB>script:), which allowed theallow_unsafe_linksprotection to be bypassed viahrefandsrcattributes (GHSA-29pj-957v-52mc).//-delimited key paths when storing backrefs, which allowed distinct labels such as[^a.b]and[^a/b]to share a single backref list (GHSA-jfm3-95jq-q3rf)[^a]and[^a.b]1on every collision, causing headings or inline footnotes which normalize to the same slug to be de-duplicated in quadratic time, which could be abused to cause a denial of service (GHSA-mh25-x5hq-wrqp)AttributesExtensionscanning the remaining siblings of an inline attribute which can only apply to its parent block, causing long runs of adjacent inline attributes to be resolved in quadratic time, which could be abused to cause a denial of service (GHSA-g2gp-3wwq-f4ph)XmlRendererindenting every element by its full nesting depth without any upper bound, causing deeply-nested documents to render as quadratically-sized XML, which could be abused to cause a denial of service (GHSA-mj63-m3rc-8ppr)MarkDelimiterProcessornot being declared as aCacheableDelimiterProcessorInterface, preventing the delimiter stack from caching the opener search for==runs (#1133)v2.8.3Compare Source
Fixed
vbscript:,file:, ordata:anywhere after the start (#1131)Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.