critical · 10CVE-2026-85061Sep 8, 2026

CVE-2026-85061: maplibre-gl XSS Sanitizer Bypass via Live NamedNodeMap Iteration

Rohit Hatagale
AI Security Researcher, SecureLayer7

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…

Packagemaplibre-gl
Ecosystemnpm
Affected<= 6.4.0
Fixed in6.4.1
CVE-2026-85061: maplibre-gl XSS Sanitizer Bypass via Live NamedNodeMap Iteration

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.

html
<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.

References: [1][2][3][4][5][6]

Related research