Глава 10 / 51
Том 2 · Конфигурация и сборка

Четыре ручки статического слоя и защита от обхода путей

После XS_FALLBACK управление принимает статический слой. Он безопасен по умолчанию и быстр по умолчанию — четырёх ручек уровня хоста хватает большинству статических сайтов, а защиту от обхода путей сторожат восемь тестовых векторов.

Обзор

Позиционирование: статический слой — вторая половина связки «сначала скрипт, статика как подстраховка». Глава разбирает границы его поведения и четыре ручки; читателям с уклоном в безопасность — в первую очередь 10.4 с восемью векторами.

10.1 Введение

Главная масса ресурсов веб-приложения обычно статична: фронтенд-сборки, картинки, шрифты. Писать код для статики — расточительно, внешний статический сервер ломает «развёртывание одним файлом». Ответ XServer — встроенный статический слой: необработанные скриптом GET/HEAD попадают сюда и ищут файлы в path хоста — не написав ни строчки кода, вы уже владеете безопасным статическим сервером.

10.2 Поведение статического слоя

  • Методы: GET отдаёт содержимое, HEAD — только заголовки; остальные методы вне семантики статического слоя
  • Пути: percent-decode, затем нормализация; запрос каталога перебирает массив index (по умолчанию index.html)
  • Передача: SendFile кусками по 512 КБ; под TLS — 64-килобайтный насос открытого текста (с объединением AGAIN/повторных входов) — на противодавление больших файлов в func_test есть реальный кейс 4 МиБ в несколько раундов
  • MIME: 18 встроенных сопоставлений типов
  • Страницы ошибок: без кастомной страницы — встроенный минималистичный вариант

10.3 Четыре ручки (Custom уровня хоста)

Пример ручек статического слоя
"host_default": {
    "name": "app",
    "path": "wwwroot",
    "static": {
        "headers": {                       /* ≤8 полей; 8 зарезервированных заголовков запрещены */
            "X-Frame-Options": "DENY",
            "Cache-Control": "public, max-age=3600"
        },
        "index": ["index.html", "home.html"],  /* перебор по порядку */
        "error_pages": { "404": "404.html", "500": "5xx.html" },
        "deny_dotfiles": true              /* по умолчанию true: файлы с точкой отвергаются всегда */
    }
}
РучкаОграничениеЭффект
static.headers≤8 полей; Transfer-Encoding / Connection и ещё 6 зарезервированных заголовков запрещены (fail-fast)Прикрепляются к каждому статическому ответу. Предрендерятся при сборке в массив полей — на пути запроса только memcpy, ноль строковой обработки
static.indexмассив строкКандидаты индекса каталога, перебор по порядку
static.error_pagesключи 400–599; значения относительно pathДля соответствующего кода — своя страница ошибки (работает при существовании файла)
static.deny_dotfilesbool · trueФайлы с точкой отвергаются всегда — даже на обычном пути

10.4 Восемь векторов обхода

Красная линия статического слоя: URL никогда не выходит за path. func_test сторожит эту линию восемью векторами — все должны давать 403/404, а не содержимое файла:

GET /../secret → отказ GET /..%2Fsecret → отказ GET /%2e%2e/secret → отказ GET /%2E%2E/secret → отказ /* регистр процентов */ GET //../secret → отказ GET /..%5Csecret → отказ /* обратный слэш */ GET /%00secret → отказ symlink из корня → отказ

Ключи реализации: percent-decode выполняется до нормализации пути, поэтому закодированного обхода не существует; deny_dotfiles — дополнительный слой (попытки обхода с файлом с точкой попадают дважды); символические ссылки из корня отвергаются якорем корня.

10.5 Контракт

  • Граница: статический слой обслуживает только GET/HEAD, только внутри path хоста, файлы с точкой по умолчанию отвергаются.
  • Производительность: headers предрендерятся при сборке — ноль строковой обработки на пути запроса, ключевое решение, держащее высокую конкурентность.
  • Откат: если у кастомной страницы ошибки нет файла — встроенная страница, но не 500.

Частые ошибки

В headers попал Content-Length
Симптом
Старт падает: static headers содержит зарезервированный заголовок.
Причина
Content-Length / Transfer-Encoding / Connection и ещё пять управляются слоем протокола; перекрытие из конфигурации сломало бы корректность сообщений — потому fail-fast, а не рантайм-переопределение.
Ключом error_pages написали "404.html"
Симптом
Старт падает: ключ error_pages вне диапазона.
Причина
Ключ — это код состояния (числовая строка 400–599), значение — путь страницы. Перепутанные местами или не-коды — fail-fast.

Упражнения

БазовыйСвой 404

Дайте демо 404.html, зарегистрируйте в error_pages и проверьте на несуществующем пути.

Приёмка: код 404 с содержимым вашей страницы; после удаления файла — возврат к встроенной.
ПродвинутыйВсе восемь векторов

Положите secret.txt вне wwwroot и попробуйте прочитать каждым из восьми векторов 10.4.

Приёмка: ни один вектор не отдал содержимое; зафиксируйте коды ответов.
ВызовБазовые заголовки безопасности

Настройте для статического сайта набор защитных заголовков (CSP, X-Content-Type-Options, Referrer-Policy…) и проверьте нулевую стоимость предрендеренного пути (разница нагрузочных тестов с конфигурацией без headers должна быть ничтожной).

Приёмка: все статические ответы несут заголовки; разница RPS между конфигурациями < 2%.

Итоги

ТезисВывод
ТриггерGET/HEAD с XS_FALLBACK попадают в статический слой
Четыре ручкиheaders (предрендер) / index / error_pages / deny_dotfiles
Защитаdecode до нормализации; восемь сторожевых векторов; якорь для symlink
ПередачаSendFile кусками 512 КБ; 64-КБ насос TLS; 18 типов MIME
Что дальше по xrt