В приложениях на Mapbox GL JS значительная часть логики строится вокруг пользовательского ввода: координаты поиска, фильтры объектов, параметры стилей, GeoJSON-данные, HTML-контент всплывающих окон и параметры URL. Любая точка, где данные приходят извне и попадают в рендеринг карты или управление состоянием, становится потенциальным вектором атаки.
Особенность Mapbox GL JS заключается в том, что он сочетает в себе три чувствительных слоя:
Каждый из этих слоёв требует строгого контроля входящих данных, поскольку некорректная или специально сформированная структура может привести к XSS, инъекциям выражений, перегрузке рендера или утечке данных.
В реальных приложениях данные, требующие санитизации, поступают из нескольких каналов:
?lng=, ?lat=,
?filter=)Каждый из этих источников должен рассматриваться как недоверенный, даже если он формируется внутри собственного фронтенда.
Классическая ошибка — прямое использование значений из query string или формы без проверки диапазонов:
const lng = parseFloat(params.get('lng'));
const lat = parseFloat(params.get('lat'));
map.setCenter([lng, lat]);
Без проверки возможно:
Корректный подход включает строгую проверку:
function isValidLat(lat) {
return Number.isFinite(lat) && lat >= -90 && lat <= 90;
}
function isValidLng(lng) {
return Number.isFinite(lng) && lng >= -180 && lng <= 180;
}
if (isValidLat(lat) && isValidLng(lng)) {
map.setCenter([lng, lat]);
}
Дополнительно полезно ограничивать зум и bounds:
map.setMaxBounds([
[-180, -85],
[180, 85]
]);
GeoJSON часто поступает из пользовательских форм или сторонних
источников. Опасность заключается не только в структуре данных, но и в
свойствах properties, которые могут попасть в UI.
Типичный небезопасный сценарий:
map.addSource('user-data', {
type: 'geojson',
data: userInputGeojson
});
Проблемные места:
Минимальная проверка структуры:
function validateFeature(feature) {
return (
feature &&
feature.type === 'Feature' &&
feature.geometry &&
Array.isArray(feature.geometry.coordinates)
);
}
Для коллекций:
function sanitizeGeoJSON(data) {
if (data.type !== 'FeatureCollection') return null;
return {
type: 'FeatureCollection',
features: data.features
.filter(validateFeature)
.slice(0, 1000)
.map(f => ({
type: 'Feature',
geometry: f.geometry,
properties: {}
}))
};
}
Политика «обнуления properties» часто используется как наиболее безопасная стратегия при работе с пользовательскими данными.
Mapbox GL JS позволяет создавать всплывающие окна с HTML-контентом:
new mapboxgl.Popup()
.setHTML(userInput)
.addTo(map);
Это прямой путь к XSS.
Любой пользовательский текст должен проходить экранирование:
function escapeHTML(str) {
return str
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
Использование:
popup.setHTML(`<div>${escapeHTML(title)}</div>`);
Для более строгого подхода применяется политика «только текст»:
popup.setText(userInput);
Этот вариант полностью исключает HTML-инъекции и предпочтителен в большинстве случаев.
Mapbox GL JS использует expression syntax для фильтрации и стилей. Неправильная подстановка пользовательских данных может привести к логическим инъекциям или утечке данных через фильтры.
Опасный пример:
map.setFilter('points', ['==', ['get', 'type'], userInput]);
Если userInput невалиден, возможны неожиданные поведения
фильтра.
Безопасный подход — whitelist:
const allowedTypes = new Set(['park', 'school', 'hospital']);
if (allowedTypes.has(userInput)) {
map.setFilter('points', ['==', ['get', 'type'], userInput]);
}
Часто состояние карты синхронизируется с URL:
const params = new URLSearchParams(window.location.search);
Проблемы возникают при отсутствии нормализации:
Пример безопасной десериализации:
function parseNumber(value) {
const n = Number(value);
return Number.isFinite(n) ? n : null;
}
const zoom = parseNumber(params.get('z'));
if (zoom && zoom >= 0 && zoom <= 24) {
map.setZoom(zoom);
}
Также важно использовать encodeURIComponent при
генерации URL:
const url = `?lng=${encodeURIComponent(lng)}&lat=${encodeURIComponent(lat)}`;
Mapbox Style Spec может приходить с сервера или формироваться динамически. Прямая подстановка пользовательских значений в style JSON опасна.
Рискованные элементы:
sourceslayerspaint свойстваПример потенциально опасного сценария:
map.setStyle(userStyle);
Без валидации пользователь получает контроль над всей картой.
Подходы к защите:
Пример ограничения:
const allowedStyles = {
streets: 'mapbox://styles/mapbox/streets-v12',
dark: 'mapbox://styles/mapbox/dark-v11'
};
if (allowedStyles[userChoice]) {
map.setStyle(allowedStyles[userChoice]);
}
Mapbox GL JS чувствителен к объёму GeoJSON и количеству объектов.
Риски:
Базовые ограничения:
const MAX_FEATURES = 500;
const MAX_COORDS = 5000;
Фильтрация:
data.features = data.features.slice(0, MAX_FEATURES);
При создании кастомных маркеров:
const el = document.createElement('div');
el.innerHTML = userInput;
Это создаёт XSS-вектор.
Безопасная альтернатива:
el.textContent = userInput;
Или генерация структурированного DOM:
const el = document.createElement('div');
const title = document.createElement('span');
title.textContent = userInput;
el.appendChild(title);
Mapbox-приложения часто агрегируют данные:
Любой ответ API должен рассматриваться как недоверенный, особенно если он проксируется напрямую в карту.
Рекомендуется:
Безопасная обработка данных в Mapbox GL JS строится на последовательности:
Пренебрежение любым из этих этапов переносит контроль над картой в пользовательский ввод, что особенно критично в приложениях с публичным доступом или интеграцией внешних источников данных.