CVE-2026-85061: maplibre-gl XSS Sanitizer Bypass via Live NamedNodeMap Iteration
A bug in MapLibre GL JS's HTML sanitizer lets attackers sneak a JavaScript event handler past the attribute-removal loop by placing two dangerous attributes back-to-back, leading to zero-click script…

The problem
DOM.sanitize() in src/util/dom.ts iterated elem.attributes, which is a live NamedNodeMap. Every time removeAttribute() was called inside that loop, the browser shifted all subsequent attribute indexes down by one. The iterator then advanced past the newly shifted attribute, leaving it untouched.
An attacker who can supply a map style attribution string can pair two consecutive event-handler attributes on a single element. The sanitizer strips the first, skips the second, and the surviving handler executes automatically when the attribution control inserts the content into innerHTML.
No user interaction is required beyond loading the map.
Proof of concept
A working proof-of-concept for CVE-2026-85061 in maplibre-gl, with the exact payload below.
<details open onload="alert(document.domain)" ontoggle="alert(document.domain)">XSS</details>When the sanitizer hits onload (index 1 in the live list), it calls removeAttribute('onload'). The list immediately contracts: ontoggle, previously at index 2, slides to index 1. The loop counter then moves to index 2, which is now empty, so ontoggle is never examined and survives.
The patch (PR #8189, commit 1da69f3) freezes the attribute list before the loop with Array.from(elem.attributes). The frozen array does not shift on removal, so every attribute is visited regardless of how many are deleted mid-loop. Root cause is CWE-79 (Improper Neutralization of Input During Web Page Generation).
The fix
Upgrade maplibre-gl to version 6.4.1 or later. As a short-term workaround, sanitize the attribution field of every map source before passing it to MapLibre. The one-line code fix is changing the iteration from for (const attr of elem.attributes) to for (const attr of Array.from(elem.attributes)) in src/util/dom.ts.
Reported by @0xKirisame.
Related research
- high · 8.2CVE-2026-84370CVE-2026-84370: svgo removeScripts Plugin XSS via Namespace and Control-Character Bypass
- highCVE-2026-54606CVE-2026-54606: suneditor Embed Plugin DOM XSS via External Script Element
- high · 8.7CVE-2026-55596CVE-2026-55596: @platejs/media Media Embed Stored XSS via Serialized Provider Metadata
- highCVE-2026-55090CVE-2026-55090: Etherpad Stored XSS via Unescaped HTML Export Attribute Values