Защита от XSS через роутинг

XSS (Cross-Site Scripting) возникает, когда пользовательский ввод попадает в DOM без корректной фильтрации или экранирования. В контексте клиентского роутинга, включая использование Page.js, источником уязвимости часто становится:

  • динамическая генерация HTML на основе параметров маршрута;
  • использование query-параметров без валидации;
  • вставка данных в innerHTML;
  • неконтролируемая работа с location.hash или location.search.

Page.js сам по себе не предотвращает XSS — он лишь управляет маршрутизацией. Безопасность полностью зависит от того, как обрабатываются данные внутри обработчиков маршрутов.


Потенциальные точки внедрения вредоносного кода

1. Параметры маршрута

page('/user/:name', (ctx) => {
  document.body.innerHTML = `<h1>${ctx.params.name}</h1>`;
});

Если :name содержит <script>alert(1)</script>, скрипт выполнится.

2. Query-параметры

page('/search', (ctx) => {
  const query = ctx.querystring.split('=')[1];
  document.body.innerHTML = `<p>${query}</p>`;
});

Любой ввод через URL становится частью DOM.

3. Прямое использование innerHTML

Это основной источник XSS при работе с Page.js.


Принципы защиты

Экранирование данных

Любые данные, поступающие извне, должны быть экранированы перед вставкой в HTML.

function escapeHTML(str) {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

Использование:

page('/user/:name', (ctx) => {
  const safeName = escapeHTML(ctx.params.name);
  document.body.innerHTML = `<h1>${safeName}</h1>`;
});

Отказ от innerHTML в пользу безопасных API

Предпочтительно использовать textContent:

page('/user/:name', (ctx) => {
  const h1 = document.createElement('h1');
  h1.textContent = ctx.params.name;
  document.body.appendChild(h1);
});

Такой подход автоматически предотвращает выполнение HTML/JS.


Валидация параметров маршрута

Ограничение допустимых значений снижает риск атак.

page('/user/:name', (ctx, next) => {
  const valid = /^[a-zA-Z0-9_-]+$/.test(ctx.params.name);
  if (!valid) return page.redirect('/error');
  next();
});

Middleware как слой безопасности

Page.js поддерживает middleware, что позволяет централизовать защиту.

function sanitizeParams(ctx, next) {
  for (let key in ctx.params) {
    ctx.params[key] = escapeHTML(ctx.params[key]);
  }
  next();
}

page('/user/:name', sanitizeParams, (ctx) => {
  document.body.innerHTML = `<h1>${ctx.params.name}</h1>`;
});

Работа с query-параметрами

Рекомендуется использовать безопасный парсинг:

function getQueryParams(queryString) {
  return Object.fromEntries(new URLSearchParams(queryString));
}

Пример:

page('/search', (ctx) => {
  const params = getQueryParams(ctx.querystring);
  const safeQuery = escapeHTML(params.q || '');
  document.body.innerHTML = `<p>${safeQuery}</p>`;
});

Контроль шаблонов и рендеринга

Использование шаблонизаторов с автоматическим экранированием:

  • Handlebars
  • Mustache
  • Pug (с осторожностью)

Пример с Handlebars:

const template = Handlebars.compile("<h1>{{name}}</h1>");

page('/user/:name', (ctx) => {
  document.body.innerHTML = template({ name: ctx.params.name });
});

По умолчанию Handlebars экранирует HTML.


CSP (Content Security Policy)

Дополнительный уровень защиты, предотвращающий выполнение инлайн-скриптов.

Пример заголовка:

Content-Security-Policy: default-src 'self'; script-src 'self';

Даже при наличии XSS-кода он не выполнится без разрешения.


Обработка hash-маршрутов

При использовании hash (#/route) важно не доверять location.hash.

page('*', (ctx, next) => {
  ctx.hash = escapeHTML(location.hash);
  next();
});

Защита при динамической загрузке компонентов

Если маршруты подгружают HTML-фрагменты:

fetch('/template.html')
  .then(res => res.text())
  .then(html => {
    document.body.innerHTML = html;
  });

Необходимо:

  • проверять источник;
  • исключать пользовательский ввод;
  • использовать DOMParser с фильтрацией.

Белые списки вместо черных

Вместо удаления “опасных” символов следует разрешать только допустимые:

function whitelist(str) {
  return str.replace(/[^a-zA-Z0-9 ]/g, '');
}

Защита редиректов

Page.js поддерживает page.redirect, но редиректы на основе пользовательских данных опасны:

page('/go', (ctx) => {
  const url = ctx.querystring.split('=')[1];
  page.redirect(url); // опасно
});

Безопасный вариант:

const allowed = ['/home', '/about'];

if (allowed.includes(url)) {
  page.redirect(url);
} else {
  page.redirect('/error');
}

Интеграция с фреймворками

При использовании Page.js вместе с React/Vue:

  • React автоматически экранирует JSX;
  • Vue экранирует шаблоны (если не используется v-html).

Опасность возникает при явном использовании небезопасных API:

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

Проверка и тестирование

Методы выявления XSS:

  • ручное тестирование через URL (<script>alert(1)</script>);
  • автоматические сканеры (OWASP ZAP);
  • unit-тесты для middleware.

Архитектурный подход

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

  • централизованной обработке данных;
  • минимизации прямой работы с DOM;
  • строгом разделении данных и представления;
  • использовании middleware для очистки входящих параметров.

Ключевые правила

  • Никогда не вставлять пользовательский ввод в innerHTML
  • Всегда экранировать данные маршрутов
  • Использовать textContent или шаблонизаторы
  • Валидировать параметры до обработки
  • Ограничивать редиректы
  • Применять CSP

Такая модель делает использование Page.js безопасным даже в сложных SPA-приложениях.