{"id":"GHSA-6h8r-xr42-gp59","summary":"xmldom: Parser silently accepts a not-well-formed end tag whose name is followed by a line break and trailing content","details":"## Summary\n\nxmldom's parser silently accepts a **not-well-formed end tag** whose valid name is followed by\ntrailing content — e.g. `\u003c/a⏎junk\u003e`. The element is closed, the trailing content is discarded, and no\nerror is reported, even though the XML end-tag production allows only optional whitespace after the\nname and both Chromium and Firefox reject such input as `application/xml`. An application that relies\non xmldom to reject not-well-formed input therefore receives a false \"valid\" result for a document the\nspecification and browsers consider malformed.\n\n## Details\n\nAcross every affected version, an end tag whose valid `Name` is followed by trailing content before\n`\u003e` is silently accepted: the element is closed, the residue is dropped, and no error is reported. How\nmuch leaks differs by line (see Affected Versions), but the observable weakness is the same.\n\nOn the current (`0.9.x`) line, the parser validates the end-tag name against the XML `ETag` production\nwith an anchored regular expression (`^ QName S? $`). That expression is compiled with the `m`\n(multiline) flag by a shared builder, so `$` matches at an interior line terminator: a valid name on\nthe first line satisfies the anchored production and any content after the line break escapes the\ncheck. On 0.9.x the whitespace-separated variant (`\u003c/a junk\u003e`) is already rejected; only the\nline-terminator variant leaks. Older lines have no anchored end-tag validator at all, so they accept\nboth the line-terminator and the whitespace variant.\n\nThis is **not** content injection — the trailing content is dropped, and the resulting DOM is a normal\nsingle-root document (`\u003ca/\u003e`). The security-relevant property is the silent acceptance of\nnot-well-formed input: xmldom's parse result disagrees with the specification and with browser XML\nparsers, so any control that treats \"xmldom parsed it without error\" as \"well-formed\" is bypassed.\n\n### Root Cause\n\nOn the `0.9.x` line, where the line-terminator variant specifically leaks:\n\n1. A shared regexp builder compiles anchored productions with the `m` flag.\n2. `^…$` under `m` are line anchors, not string anchors.\n3. The anchored end-tag production `^ QName S? $` is therefore satisfied by the first line alone, so\n   trailing content after a line terminator is neither matched nor rejected — the malformed end tag is\n   accepted and the residue silently discarded.\n\nThe triggering line terminators are the ECMAScript `LineTerminator` set: U+000A, U+000D, U+2028, U+2029.\nU+2028 and U+2029 are not XML whitespace, so they are non-conforming trailing content that nonetheless\nleaks because the JavaScript `$` anchor treats them as line boundaries under `m`.\n\n## Affected Versions\n\nBoth maintained versions are affected and fixed:\n\n- **`0.9.x`** (`\u003e= 0.9.0, \u003c= 0.9.11`) → **`0.9.12`**: the anchored end-tag validator's `m` flag leaks\n  the line-terminator variant. The whitespace variant (`\u003c/a junk\u003e`) is already rejected on this line.\n- **`0.8.x`** (`\u003e= 0.8.0, \u003c= 0.8.14`) → **`0.8.15`**: no anchored end-tag validator at all — both the\n  line-terminator and the whitespace variant are silently accepted; the fix adds a residue check.\n\n`release-0.7.x` (`\u003c= 0.7.13`) and the unscoped `xmldom` package (range `*`, last release 0.6.0) are\n**affected but will not be patched** — they are end-of-life / unmaintained. They share the older-line\nbehavior (both variants silently accepted, residue dropped).\n\n## Proof of Concept\n\n```js\nconst { DOMParser, XMLSerializer } = require('@xmldom/xmldom');\nconst doc = new DOMParser().parseFromString('\u003ca\u003e\u003c/a\\njunk\u003e', 'text/xml'); // no error\nconsole.log(new XMLSerializer().serializeToString(doc));\n// Observed: \u003ca/\u003e   — the malformed end tag is accepted, \"junk\" silently discarded, no error reported.\n// Expected (per XML spec / Chromium / Firefox): a parse error — the input is not well-formed.\n```\n\n## Impact\n\n- **Silent acceptance of not-well-formed XML**: a document the specification and browser XML parsers\n  reject is parsed without error.\n- **Bypass of a well-formedness / parse-before-trust gate**: an application relying on xmldom to\n  reject malformed input treats such a document as valid. Note this is an input-validation /\n  parser-differential issue, not content injection — the trailing content is discarded.\n\n## Fix Applied\n\nThe parser now reports a not-well-formed end tag whose valid name is followed by trailing content, on both `0.9.12` and `0.8.15` — previously it was accepted silently — and parsing recovers to the byte-identical DOM as before.\\\nOn `0.9.12` the anchored end-tag validator is corrected so a line break followed by trailing content no longer satisfies it, reported as a recoverable `error` when parsing as XML and a `warning` when parsing as HTML.\\\nOn `0.8.15`, which previously performed no end-tag residue validation, an equivalent residue check is added, reported as a recoverable `error` in both XML and HTML.\\\nWell-formed documents are unaffected.\n\nBecause the report is recoverable, the fix is non-breaking: no previously-parsed document begins to\nthrow and no serialized output changes. Consumers that want strict rejection can escalate the reported\n`error` to a fatal one via the parser's error handler (`onError` in `0.9.12`, `errorHandler` in\n`0.8.15`). The two versions also differ in the whitespace-separated variant (`\u003c/a junk\u003e`): `0.9.12`\nalready rejected it with a fatal error in XML and continues to, while `0.8.15` — which validated\nneither variant — now emits the same recoverable report for both the whitespace and line-break\nvariants.\n\n### Residual limitation\n\nBy default the parser still **recovers** (it does not reject the document); the reported condition is a\nrecoverable `error`/`warning`, not a fatal error, to avoid changing the parsed output in a patch\nrelease. Converting this and the other not-well-formed-acceptance cases to a consistent `fatalError` —\nincluding the broader end-tag leniency on the older versions — is deferred to the next breaking release,\ntracked at [xmldom/xmldom#1074](https://github.com/xmldom/xmldom/issues/1074).","aliases":["CVE-2026-83611"],"modified":"2026-09-08T21:15:04.108579606Z","published":"2026-09-08T21:02:13Z","database_specific":{"cwe_ids":["CWE-1286"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-09-08T21:02:13Z","nvd_published_at":"2026-09-01T15:17:39Z"},"references":[{"type":"WEB","url":"https://github.com/xmldom/xmldom/security/advisories/GHSA-6h8r-xr42-gp59"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-83611"},{"type":"WEB","url":"https://github.com/xmldom/xmldom/pull/1071"},{"type":"WEB","url":"https://github.com/xmldom/xmldom/pull/1072"},{"type":"WEB","url":"https://github.com/xmldom/xmldom/commit/4430189660b0d380ee9c9ee7550a1358688e8828"},{"type":"WEB","url":"https://github.com/xmldom/xmldom/commit/7b2ec67e1750daadd0bb06c92e875e726544a362"},{"type":"PACKAGE","url":"https://github.com/xmldom/xmldom"},{"type":"WEB","url":"https://github.com/xmldom/xmldom/releases/tag/0.8.15"},{"type":"WEB","url":"https://github.com/xmldom/xmldom/releases/tag/0.9.12"}],"affected":[{"package":{"name":"@xmldom/xmldom","ecosystem":"npm","purl":"pkg:npm/%40xmldom/xmldom"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0.7.0"},{"fixed":"0.8.15"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-6h8r-xr42-gp59/GHSA-6h8r-xr42-gp59.json","last_known_affected_version_range":"\u003c= 0.8.14"}},{"package":{"name":"@xmldom/xmldom","ecosystem":"npm","purl":"pkg:npm/%40xmldom/xmldom"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0.9.0"},{"fixed":"0.9.12"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 0.9.11","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-6h8r-xr42-gp59/GHSA-6h8r-xr42-gp59.json"}},{"package":{"name":"xmldom","ecosystem":"npm","purl":"pkg:npm/xmldom"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"0.6.0"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-6h8r-xr42-gp59/GHSA-6h8r-xr42-gp59.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"}]}