{"id":"GHSA-97jj-33gv-5xf9","title":"league/commonmark: DisallowedRawHtml bypassed when a disallowed tag name ends the raw-HTML literal","summary":"league/commonmark: DisallowedRawHtml bypassed when a disallowed tag name ends the raw-HTML literal","severity":"medium","cvss":6.1,"cwe":["CWE-78","CWE-79"],"vendor":"league","product":"league/commonmark","ecosystem":"composer","affected":["league/commonmark >= 1.3.0, <= 2.10.1"],"patched":["league/commonmark 2.10.2"],"published":"2026-09-30","updated":"2026-09-30","sourceUpdated":"2026-09-30T15:36:37Z","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-97jj-33gv-5xf9","references":[{"url":"https://github.com/thephpleague/commonmark/security/advisories/GHSA-97jj-33gv-5xf9"},{"url":"https://github.com/thephpleague/commonmark/commit/411afcc2a7402756d96c89af8882c724d12d47ca"},{"url":"https://github.com/thephpleague/commonmark/releases/tag/2.10.2"},{"url":"https://github.com/advisories/GHSA-97jj-33gv-5xf9"}],"tags":["ghsa","composer"],"ingestedAt":"2026-09-30T16:10:06.892Z","slug":"GHSA-97jj-33gv-5xf9","body":"## Overview\n\n﻿## Summary\n\nThe `DisallowedRawHtml` extension does not escape a disallowed tag when the tag name is the last thing in the raw HTML. A Markdown line containing just `<script` is emitted unchanged, and the next block can supply its attributes. With the shipped GFM defaults this allows stored XSS by anyone who can post Markdown.\n\n## Details\n\n`DisallowedRawHtmlRenderer` escapes tags with this regex:\n\n```\n/<(\\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)[\\s\\/>])/i\n```\n\nThe trailing character class requires one character after the tag name. The block parser does not: `RegexHelper::PARTIAL_HTMLBLOCKOPEN` accepts end of line after a tag name, so `<script` alone opens an HTML block. Because a rendered `HtmlBlock` has no trailing newline, the regex has nothing to match and the `<` passes through.\n\nIn the browser the newline is still present, so the tag name terminates there and whatever follows becomes attributes.\n\nThis is the same filter that GHSA-4v6x-c7xx-hw9f fixed in 2.8.1. That fix widened the character class but still requires one character, so this case was not covered.\n\n## Reproduction\n\nRender this with `GithubFlavoredMarkdownConverter` and default settings:\n\n```\n<div>\n<script\n\n<span src=\"/evil.js\">\n```\n\nOutput:\n\n```html\n<div>\n<script\n<span src=\"/evil.js\">\n```\n\nA browser parses that as `<script src=\"/evil.js\">` with a junk `<span` attribute, and the script runs. `<iframe` with `<span onload=\"...\">` works the same way and does not need a later `</script>` in the page.\n\nControl: `<script src=\"/evil.js\"></script>` is correctly escaped to `&lt;script src=\"/evil.js\">&lt;/script>`.\n\n## Affected versions\n\n1.3.0 (when the extension was added) through the current release. The `</style` and mid-line forms are only affected as continuation lines inside an already-open HTML block.\n\n## Preconditions\n\n- `html_input` is `allow` (the default)\n- The `DisallowedRawHtml` extension is active, which the GFM extension enables automatically\n- Untrusted users can post Markdown\n\nSetting `html_input` to `escape` or `strip` fully mitigates this.\n\n## Suggested fix\n\nAllow end of string after the tag name:\n\n```php\n$regex = \\sprintf('/<(\\/?(?:%s))([\\s\\/>]|$)/i', \\implode('|', \\array_map('preg_quote', $tags)));\n\nreturn \\preg_replace($regex, '&lt;$1$2', $rendered);\n```\n\nThis escapes every bypass shape above and leaves `<div>`, `<scripts>` and `<span class=\"a\">` untouched. The existing unit test only covers tag names followed by another character, so a case for a bare tag name should be added.\n\n## Affected packages\n\n- `league/commonmark >= 1.3.0, <= 2.10.1`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `league/commonmark 2.10.2`","depth":"sunlit","depthScore":34,"depthScoreParts":{"impact":33.6,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}