MapLibre’s Sanitizer Skipped One Attribute. That Was Enough for Critical XSS
- The Mess: MapLibre GL JS had a critical XSS flaw in
DOM.sanitize()caused by modifying a liveNamedNodeMapwhile iterating over it. One dangerous attribute could survive sanitization and execute when the map rendered attacker-controlled attribution content. - The Damage: Applications using untrusted attribution strings could expose users to cross-site scripting without requiring a click.
- The Fix: Upgrade
maplibre-glto 6.4.1 or later and audit any application that feeds untrusted attribution content into MapLibre.
MapLibre GL JS is supposed to make interactive maps easier to build.
Instead, one tiny JavaScript implementation mistake turned its HTML sanitizer into a bypass.
The vulnerability is tracked as CVE-2026-85061 and affects MapLibre GL JS versions before 6.4.1. The GitHub security advisory rates it Critical, CVSS 10.0 with network attack vector, low complexity, no privileges required and no user interaction.
The bug isn’t some exotic browser engine corruption.
It’s an indexing mistake.
And that’s exactly why it is interesting.
The sanitizer was modifying the collection it was walking
The vulnerable code lived in:
src/util/dom.ts
inside:
DOM.sanitize()
The function was intended to inspect HTML attributes and remove dangerous ones.
The problem was the data structure.
elem.attributes is a live NamedNodeMap.
It changes immediately when an attribute is removed.
So imagine the sanitizer sees:
attribute 0 → safe
attribute 1 → dangerous
attribute 2 → dangerous
attribute 3 → safe
The function examines attribute 1.
It decides:
Remove it.
The browser removes attribute 1.
Everything after it shifts left.
What was attribute 2 becomes attribute 1.
But the loop increments its index.
The sanitizer moves to attribute 2.
The newly shifted dangerous attribute at index 1 is never examined.
It survives.
That is the vulnerability.
The official MapLibre advisory explicitly identifies this live-collection behavior as the root cause.
The attack does not need a fancy payload
The interesting part is that the attacker doesn’t need to completely defeat the sanitizer.
They only need to arrange dangerous attributes next to each other.
The advisory provides an example involving consecutive attributes such as:
<details open onload="1" ontoggle="...">
The sanitizer removes one attribute.
The collection shifts.
The second dangerous attribute escapes inspection.
Later, MapLibre inserts the resulting HTML into innerHTML.
The surviving event handler executes.
That’s a classic sanitizer bypass.
The sanitizer technically worked.
It just didn’t work on everything it was supposed to inspect.
Why NamedNodeMap matters
This is a nasty JavaScript footgun.
Developers often expect an attribute collection to behave like a static array.
It doesn’t.
A NamedNodeMap returned by element.attributes is live.
Change the DOM and the collection changes underneath the iterator.
That creates a class of bugs that can be incredibly difficult to spot during ordinary testing.
The dangerous sequence is:
iterate
↓
inspect
↓
remove
↓
collection changes
↓
index moves
↓
element skipped
Nothing crashes.
No exception is required.
The program continues normally.
It simply fails to inspect something that it was supposed to inspect.
For security code, that is enough.
Why the impact is XSS
MapLibre provides an attribution control for displaying attribution information associated with map content.
The vulnerable path becomes dangerous when an application allows untrusted or third-party attribution content to reach the affected rendering code.
An attacker can provide crafted HTML containing consecutive dangerous attributes.
The sanitizer removes the first one.
The second survives.
MapLibre eventually inserts the sanitized content into innerHTML.
The browser interprets the surviving event handler as executable markup.
That’s the important distinction:
The vulnerability isn’t “MapLibre executes arbitrary HTML.”
The vulnerability is:
MapLibre attempts to sanitize HTML and can be tricked into leaving dangerous markup behind.
That difference matters because the entire security boundary depends on the sanitizer actually being complete.
Zero-click does not mean zero conditions
There is a tendency to describe this kind of vulnerability as:
zero-click XSS
That is broadly fair, but there is an important qualification.
The victim still has to render the affected map content.
The attacker doesn’t need the victim to click a malicious link or explicitly interact with the payload, but the vulnerable application must process the attacker-controlled attribution content.
The CVE is therefore rated with:
UI:N
and:
PR:N
meaning the attacker does not need privileges and no explicit victim interaction is required under the CVSS model.
That’s considerably worse than an XSS that requires someone to click an injected element.
The fix is almost embarrassingly simple
The patch did not require rewriting the entire sanitizer.
The fix creates a static snapshot of the attributes before iteration:
Array.from(elem.attributes)
The sanitizer can then safely remove attributes from the DOM without modifying the collection it is currently traversing.
Conceptually:
live NamedNodeMap
↓
unsafe iteration
↓
remove attribute
↓
collection shifts
↓
attribute skipped
becomes:
live NamedNodeMap
↓
Array.from(...)
↓
static snapshot
↓
iterate snapshot
↓
modify DOM safely
That tiny change eliminates the specific index-skipping bug.
MapLibre fixed the vulnerability in 6.4.1.
The dependency problem
This is where the story gets more interesting for developers.
MapLibre GL JS is an npm dependency.
A developer may never have written a line of sanitization code.
They may simply have:
{
"dependencies": {
"maplibre-gl": "^6.3.0"
}
}
in a project.
The vulnerable security boundary is now part of their application.
This is why dependency security isn’t just about finding packages with spectacular RCE vulnerabilities.
A tiny frontend library can introduce a browser-side security boundary that developers assume is safe because the package is widely used.
And XSS in a frontend dependency can be particularly nasty because the vulnerable code executes inside the application’s origin.
Why the CVSS score is 10.0
The official advisory gives CVSS 3.1:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N
That translates to:
- Network attack vector
- Low attack complexity
- No privileges required
- No user interaction
- Changed scope
- High confidentiality impact
- High integrity impact
- No direct availability impact
Result:
10.0 — Critical.
The score makes sense if the vulnerable application accepts attacker-controlled attribution content.
An XSS running under the target application’s origin can potentially access application data available to JavaScript, manipulate the application’s interface and perform actions available to the victim’s browser session.
The exact impact still depends on how the affected application uses MapLibre.
The library itself doesn’t magically turn every map into an Internet-wide RCE.
Context matters.
Who is actually exposed?
Not every MapLibre deployment is automatically exploitable.
The key question is:
Can an attacker influence the attribution HTML processed by the vulnerable path?
Applications using only hard-coded, trusted attribution strings are in a much safer position.
Applications that render:
- user-generated attribution;
- third-party style metadata;
- dynamically supplied map content;
- externally controlled attribution strings;
deserve immediate investigation.
The official advisory specifically identifies applications rendering untrusted or third-party style attribution strings and user-supplied custom attributions as affected scenarios.
That distinction should prevent unnecessary panic.
But it should also prevent the classic:
“We’re using MapLibre, therefore we’re fine.”
The dependency still needs to be patched.
The real lesson is not about maps
This vulnerability could have appeared anywhere.
The fundamental mistake was:
mutate a live collection while iterating over it.
That’s dangerous in ordinary software.
Inside security-sensitive code, it can become a vulnerability.
Sanitizers deserve especially aggressive testing because developers often treat them as security boundaries.
A sanitizer that removes 99 out of 100 dangerous things is not “mostly secure.”
The one thing it misses can be the payload.
Dependency scanners should catch this
The GitHub advisory identifies:
Affected:
maplibre-gl <= 6.4.0
Fixed:
maplibre-gl 6.4.1
The vulnerability is also catalogued under CWE-79: Improper Neutralization of Input During Web Page Generation.
Developers should therefore check their dependency tree rather than searching only the application’s source code.
For npm:
npm ls maplibre-gl
Then upgrade:
npm install maplibre-gl@latest
For projects using lockfiles, make sure the resulting lockfile actually resolves to 6.4.1 or newer.
A version declaration in package.json is not proof that the deployed application contains the patched code.
The lockfile and built artifact matter.
The browser doesn’t care why the attribute survived
This is perhaps the nastiest aspect of sanitizer bugs.
The browser doesn’t know that a developer intended to remove:
ontoggle=
It sees valid HTML.
If that attribute survives into innerHTML, the browser follows its normal parsing and event-processing rules.
Security code has to prevent the dangerous markup from reaching that stage.
Once it does, the browser isn’t the broken component.
The sanitizer is.
Bugstoday’s take
CVE-2026-85061 is a perfect example of why security bugs don’t need thousands of lines of code.
The entire failure came from a developer modifying a live attribute collection while walking through it.
One removal shifted the collection.
One index moved.
One dangerous attribute survived.
Then innerHTML did exactly what innerHTML always does.
The fix is tiny.
The consequences aren’t.
If your application uses MapLibre GL JS, update it. If your application accepts external attribution data, inspect that path carefully. And if you’re writing a sanitizer yourself, never assume a live DOM collection behaves like a frozen array.
Security boundaries don’t get partial credit.
Today’s Bugs. Tomorrow’s Breaches.
Technical Sources
- MapLibre GL JS — official GitHub Security Advisory: GHSA-jrc7-96c5-q579
- GitHub Advisory Database — CVE-2026-85061
- NVD / MITRE — CVE-2026-85061
- MapLibre GL JS — patch commit
1da69f3 - MapLibre GL JS — release
v6.4.1




