MapLibre GL JS активно использует декларативную модель отображения данных (стили, источники, слои, всплывающие окна), где значительная часть информации может приходить из внешних или динамических источников. Именно это создаёт несколько характерных зон риска для XSS:
Ключевая особенность заключается в том, что MapLibre GL JS не является «веб-приложением с формами», но активно отображает внешние данные, которые часто интерпретируются как текст или HTML.
Popups в MapLibre GL JS позволяют задавать HTML напрямую:
new maplibregl.Popup()
.setHTML(`<b>${feature.properties.name}</b>`)
.setLngLat([lng, lat])
.addTo(map);
Опасность возникает при прямой вставке данных из
feature.properties, особенно если они приходят из GeoJSON
или API.
map.on('click', 'places', (e) => {
const feature = e.features[0];
new maplibregl.Popup()
.setHTML(feature.properties.description)
.setLngLat(e.lngLat)
.addTo(map);
});
Если description содержит:
<img src=x oner ror=alert(1)>
происходит выполнение JavaScript в контексте страницы.
Базовый принцип — отказ от интерпретации HTML там, где это не требуется.
textContent через DOM-узлыconst popupContent = document.createElement('div');
popupContent.textContent = feature.properties.description;
new maplibregl.Popup()
.setDOMContent(popupContent)
.setLngLat(e.lngLat)
.addTo(map);
textContent гарантирует, что любые HTML-теги будут
экранированы.
Если HTML необходим (например, для форматирования карточек объектов), применяется строгая фильтрация.
import DOMPurify from 'dompurify';
const cleanHTML = DOMPurify.sanitize(feature.properties.description);
new maplibregl.Popup()
.setHTML(cleanHTML)
.setLngLat(e.lngLat)
.addTo(map);
Важно учитывать конфигурацию:
onerror, onclick,
onloadscript, iframe,
objectGeoJSON часто поступает из внешних API или пользовательских загрузок.
map.addSource('points', {
type: 'geojson',
data: geojsonData
});
Далее свойства используются в стилях или popups:
map.setLayoutProperty('label', 'text-field', ['get', 'name']);
Сам по себе text-field безопасен, но риск возникает
при:
feature-state + DOM-рендерингаMapLibre позволяет использовать кастомные DOM-элементы:
const el = document.createElement('div');
el.innerHTML = feature.properties.iconHtml;
new maplibregl.Marker(el)
.setLngLat([lng, lat])
.addTo(map);
Это прямой XSS-вектор.
const el = document.createElement('div');
el.textContent = feature.properties.label;
new maplibregl.Marker(el)
.setLngLat([lng, lat])
.addTo(map);
Стиль MapLibre — это JSON, который может загружаться удалённо.
Опасность возникает, если:
map.setStyle(userProvidedStyleUrl);
Если URL контролируется атакующим, он может:
MapLibre GL JS поддерживает expression-синтаксис:
['concat', ['get', 'name'], ' ', ['get', 'type']]
Сам по себе он не исполняет JS, но становится опасным при:
const expr = ['concat', userInput];
map.setPaintProperty('layer', 'text-color', expr);
Если userInput не контролируется, возможно:
При работе с внешними данными важно вводить строгие контракты:
<, >,
&) в текстовых поляхПример валидации:
function sanitizeFeature(f) {
return {
...f,
properties: {
name: String(f.properties.name ?? '').slice(0, 100),
description: String(f.properties.description ?? '').slice(0, 300)
}
};
}
Архитектурный подход заключается в разделении:
Основное правило — MapLibre не должен получать «сырые HTML-данные».
Sprites и icon-image могут быть подменены через внешние URL.
Риск:
CSP значительно снижает риск XSS даже при ошибках в коде.
Рекомендуемые ограничения:
unsafe-inlinescript-src только на доверенные доменыimg-src и worker-srcОсобенно важно:
Обработчики событий (click, mousemove)
часто становятся местом сборки UI:
map.on('click', layerId, (e) => {
document.getElementById('panel').innerHTML =
e.features[0].properties.name;
});
Проблема возникает при использовании innerHTML.
document.getElementById('panel').textContent =
e.features[0].properties.name;
Безопасное использование MapLibre GL JS строится на сочетании:
innerHTML в пользу
textContentТакая модель снижает риск XSS до уровня, при котором MapLibre выступает только как рендеринг-движок, не участвующий в интерпретации пользовательского HTML-кода.