XSS (Cross-Site Scripting) возникает, когда пользовательский ввод попадает в DOM без корректной фильтрации или экранирования. В контексте клиентского роутинга, включая использование Page.js, источником уязвимости часто становится:
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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Использование:
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();
});
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>`;
});
Рекомендуется использовать безопасный парсинг:
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:
const template = Handlebars.compile("<h1>{{name}}</h1>");
page('/user/:name', (ctx) => {
document.body.innerHTML = template({ name: ctx.params.name });
});
По умолчанию Handlebars экранирует HTML.
Дополнительный уровень защиты, предотвращающий выполнение инлайн-скриптов.
Пример заголовка:
Content-Security-Policy: default-src 'self'; script-src 'self';
Даже при наличии XSS-кода он не выполнится без разрешения.
При использовании 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;
});
Необходимо:
Вместо удаления “опасных” символов следует разрешать только допустимые:
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:
v-html).Опасность возникает при явном использовании небезопасных API:
<div dangerouslySetInnerHTML={{ __html: userInput }} />
Методы выявления XSS:
<script>alert(1)</script>);Безопасный роутинг строится на:
innerHTMLtextContent или шаблонизаторыТакая модель делает использование Page.js безопасным даже в сложных SPA-приложениях.