XSS защита

kepler.gl работает поверх React и deck.gl, обрабатывая большие объёмы геоданных, пользовательские конфигурации слоёв, стили визуализации и параметры фильтрации, которые часто поступают из внешних источников: URL-параметров, JSON-конфигураций, API ответов и файлов импорта. Именно эта архитектура делает вопрос XSS-защиты не формальным дополнением, а критическим элементом безопасности.

XSS в контексте Kepler.gl возникает не только через классические HTML-инъекции, но и через менее очевидные каналы:

  • конфигурации слоёв (layer config), где могут присутствовать строковые поля
  • названия датасетов и колонок
  • всплывающие подсказки (tooltip) и popover-элементы
  • кастомные визуализации через React-компоненты
  • сериализованные состояния карты (map state), передаваемые через URL или localStorage

Особенность подобных систем заключается в том, что они часто воспринимаются разработчиками как «чисто визуальные», хотя на практике они могут рендерить данные в DOM, включая SVG и HTML-оверлеи.

Основной принцип защиты: отсутствие доверия к данным карты

Любая строка, пришедшая извне, должна рассматриваться как потенциально опасная. Это относится даже к:

  • названиям колонок CSV
  • идентификаторам слоёв
  • значениям фильтров
  • подписям легенд

Визуализационные библиотеки нередко автоматически отображают эти значения в интерфейсе, что создаёт риск внедрения скриптов через неэкранированный вывод.

Ключевой принцип: данные не должны интерпретироваться как HTML ни при каких условиях.

Опасность HTML-инъекций в tooltip и popover

Наиболее уязвимые зоны — элементы, где текст отображается динамически:

  • hover tooltip над точками
  • всплывающие карточки объектов
  • кастомные React-компоненты для атрибутов

Если используется конструкция вида:

<div dangerouslySetInnerHTML={{ __html: userValue }} />

то любое значение userValue, пришедшее из датасета, становится точкой XSS-эксплуатации.

Безопасная альтернатива заключается в использовании текстового рендера:

<div>{userValue}</div>

React автоматически экранирует содержимое, предотвращая интерпретацию HTML.

Санитизация данных перед визуализацией

Если по архитектурным причинам требуется поддержка ограниченного HTML (например, для форматирования описаний), применяется строгая санитизация:

import DOMPurify from 'dompurify';

const safeHtml = DOMPurify.sanitize(userInput, {
  USE_PROFILES: { html: true }
});

Важно понимать, что даже санитизация не должна применяться к геоданным по умолчанию. В системах визуализации предпочтительнее полностью отказаться от HTML в данных слоя.

URL-параметры и сериализация состояния карты

Kepler.gl активно использует сериализацию состояния карты, включая:

  • позицию камеры
  • выбранные слои
  • фильтры
  • стили

При хранении состояния в URL возникает риск инъекций через параметры, которые затем десериализуются и отображаются в интерфейсе.

Проблемная схема:

  1. пользователь передаёт JSON через query string
  2. приложение парсит JSON без проверки
  3. значения попадают в UI-компоненты

Безопасный подход включает:

  • строгую JSON-схему валидации
  • ограничение типов значений
  • отказ от произвольных HTML-полей

Пример валидации:

function validateMapConfig(config) {
  return (
    typeof config === 'object' &&
    typeof config.visState === 'object' &&
    Array.isArray(config.visState.layers)
  );
}

Контроль React-компонентов и кастомных слоёв

Расширяемость Kepler.gl через кастомные компоненты увеличивает поверхность атаки. Особенно опасны:

  • кастомные tooltips
  • пользовательские render-функции слоёв
  • подключаемые визуальные плагины

Каждый кастомный слой должен соблюдать правило: не выполнять интерпретацию строк как кода или HTML.

Пример безопасного render-функционала:

const renderTooltip = (object) => {
  return (
    <div>
      <div>{object.name}</div>
      <div>{String(object.value)}</div>
    </div>
  );
};

Нежелательно использовать:

  • шаблонизацию через innerHTML
  • динамическое создание DOM через строки
  • eval-подобные конструкции

Content Security Policy как второй слой защиты

Даже при корректной реализации на уровне React важно ограничить выполнение скриптов через CSP:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  object-src 'none';
  base-uri 'self';

Для геовизуальных приложений критично:

  • запрет inline-script
  • запрет eval
  • ограничение внешних источников

CSP снижает ущерб даже при ошибке в обработке данных.

SVG как скрытый вектор XSS

В Kepler.gl визуализация часто использует SVG через deck.gl и overlay-слои. SVG может содержать:

  • <script> теги
  • event-атрибуты (onload, onerror)
  • внешние ссылки

Если SVG генерируется из пользовательских данных без фильтрации, он становится полноценным XSS-вектором.

Безопасная стратегия:

  • генерация SVG только из числовых данных
  • запрет вставки строковых узлов в SVG DOM
  • отключение внешних ссылок внутри SVG

Опасность цветовых и стилевых конфигураций

Даже параметры визуального стиля могут быть использованы для атак в редких сценариях, если они интерполируются в CSS без проверки.

Пример риска:

style={{ backgroundImage: `url(${userInput})` }}

Если userInput содержит jav * ascript: или data-URI, возможна эксплуатация.

Правильный подход:

  • whitelist протоколов (https, http)
  • запрет data/javascript URI
  • валидация URL через парсер

Изоляция данных слоя от UI-логики

Архитектурно важно разделять:

  • геоданные (числа, координаты, категории)
  • UI-метаданные (лейблы, описания)
  • рендер-логику

Смешивание этих слоёв приводит к тому, что пользовательские данные начинают влиять на DOM-структуру.

Правильная модель:

  • data layer — только примитивы
  • presentation layer — строго контролируемый рендер
  • config layer — валидируемый JSON без HTML

Безопасная обработка CSV и внешних датасетов

CSV и JSON импорты — частый источник XSS в визуализациях. Опасные сценарии:

  • колонка содержит <img src=x oner ror=alert(1)>
  • значения интерпретируются как HTML
  • автоматическое отображение tooltip без экранирования

Решение:

  • принудительное приведение всех значений к строке или числу
  • экранирование перед рендером
  • запрет HTML-интерпретации на уровне парсинга
const sanitizeRow = (row) =>
  Object.fromEntries(
    Object.entries(row).map(([k, v]) => [k, String(v)])
  );

Минимизация доверенной зоны в приложении

Наиболее устойчивые реализации Kepler.gl-подобных систем строятся по принципу минимального доверия:

  • UI не знает происхождение данных
  • данные не содержат исполняемых конструкций
  • любые строки считаются недоверенными
  • рендер строго декларативный

Такой подход превращает XSS из вероятной угрозы в практически исключённый класс уязвимостей даже при работе с внешними источниками данных.