DarkRiDDeR15 мин

Контроль без маршрута атаки — это инвентарь, а не защита: практическая карта для веба

БезопасностьИнженерная практика

Проблема списка «CSP включён, cookies настроены, проверки есть» в удобной иллюзии готовности. Он не отвечает, какой именно шаг атаки должен быть прерван и какое наблюдение это подтверждает. Цена — во время разбора команда находит настройки, но не может отличить работающую границу от декоративной строки в конфигурации; время уходит на спор о смысле control вместо локализации открытого пути.

Практический минимум проще: для одного риска записать asset, precondition и упорядоченный attack path; рядом назвать control, его точку interruption, допустимое evidence и остаток. Не доказывать всё сразу. Действие на текущий review — не выдавать «безопасно», а передать ровно один synthetic-security-review hand-off, когда все четыре связи видимы в фиксированном учебном объекте.

Карта начинается с глагола атакующего

Начинать с control неудобно, зато начинать с действия атакующего ещё полезнее. В fixed example путь имеет три именованные ступени: untrusted-fragment, unsafe-render-sink, script-capable-document. Это не описание конкретного сайта и не отчёт о найденной уязвимости. Это маленькая модель вопроса: какой объект ценен, при каком условии фрагмент доходит до sink и какой следующий шаг должен стать невозможным.

Такое сужение защищает от бесконечной таксономии. Не нужно в одной карточке перечислять XSS, CSRF, clickjacking, supply chain и все заголовки. Один путь — одна проверяемая гипотеза. Если новый риск использует иной asset или иное условие, ему нужна новая карточка. Иначе одно evidence случайно начинают читать как ответ на несколько атак, а residual risk превращается в строку «остальное учтено».

Карта из пяти связанных узлов: asset и упорядоченный путь атаки ведут к точке interruption в control; разрешённое evidence подтверждает только эту связь, а отдельная ветка перечисляет оставшийся риск.
Карта заставляет читать control как ограниченное прерывание конкретного маршрута, а evidence — как наблюдение с собственной границей.
Карточка одного пути атаки
СлойЧто назватьFixed примерЧто это не доказывает
threat modelasset, precondition, ordered stepsfragment → sink → documentчто такой путь есть в реальном продукте
controlместо и способ interruptionreject script without fixed nonceкорректность всех источников HTML
evidenceразрешённое наблюдение и bindingpolicy plus negative caseполитику каждого браузера или стенда
residual riskоставшийся шаг и вопрос владельцуencoding before boundaryчто риск обнулён
resultединственно допустимый выходsynthetic review hand-offрелиз, разрешение или deployment

Control должен назвать точку прерывания

Фраза «используем CSP» слабее, чем reject-script-without-fixed-nonce. Во второй фразе есть объект решения: script без named nonce не получает авторизацию в synthetic модели. W3C CSP3 прямо описывает CSP как defence-in-depth против content injection, а не как замену validation и output encoding. Поэтому здесь browser policy не закрывает происхождение фрагмента и не делает unsafe sink допустимым; она ограничивает один последующий шаг.

OWASP ASVS 5.0.0 полезен как язык проверки web frontend: он просит документировать ожидаемые browser security features, использовать CSP response header с ограничениями и проверять их присутствие. Но ASVS requirement — не свидетельство, что конкретная атака остановлена. Требование становится полезным в review только после привязки к пути и к наблюдению. Иначе мы получаем аккуратный checklist, который нельзя использовать для принятия следующего инженерного решения.

Исполняемая карта, не имитация стенда

import { createFixedSyntheticSecurityReview, mapFixedAttackControlEvidence } from './upgrade-2026-04.mjs';

const item = createFixedSyntheticSecurityReview('mapped-injection-path-v1');
const map = mapFixedAttackControlEvidence(item);
console.log({ path: map.attackPath, interruption: map.interruption, evidence: map.allowedEvidence });
// { path: ['untrusted-fragment', 'unsafe-render-sink', 'script-capable-document'], interruption: 'reject-script-without-fixed-nonce', evidence: ['named-directives-present', 'synthetic-negative-script-is-not-authorized'] }

Пример вызывает public export и читает named fixed literal в памяти. Он не формирует header, не запускает browser, не запрашивает страницу и не сообщает результат наружу. Выход — карта связей, а не измерение. Именно это позволяет выполнить snippet буквально и не выдать упражнение за production evidence: буквальное исполнение проверяет логику модели, но не свойства среды.

Evidence не бывает «вообще достаточно»

В карте разрешены только два наблюдения: named directives присутствуют и synthetic negative script не авторизован. Они связаны одновременно с fixed-html-injection-path-v1 и fixed-csp-nonce-boundary-v1. Если evidence знает имя control, но ссылается на другой threat id, его нельзя переносить в эту карточку. Результат должен быть stop-unbound-evidence, а не попытка дописать объяснение после факта.

Это правило экономит время в ревью. Вместо длинного перечня артефактов reviewer спрашивает четыре коротких вещи: что наблюдали, к какому пути это относится, какой control проверяет наблюдение и что наблюдение не умеет утверждать. NISTIR 8397 рекомендует threat modeling, automated testing, static analysis и другие техники как минимальный набор подходов, но не говорит, что один тип проверки заменяет остальные. Наше evidence намеренно узкое и не присваивает себе чужую работу.

Остаток надо писать раньше успешной ветки

Residual risk — не послесловие к зелёной ячейке. В fixed record остаются source untrusted fragment, correctness output encoding и named sink, который данный evidence не покрывает. Рядом есть owner question: какой отдельный review трассирует validation и encoding до browser-policy boundary? Эта запись сохраняет границу: CSP не должен превращаться в замену контроля данных только потому, что он удобен как заголовок.

У residual risk должен быть не абстрактный владелец «security», а следующий проверяемый вопрос. Если вопрос пустой или remainsOpen пуст, fixture останавливается на stop-unbounded-residual-risk. Это не утверждение, что не бывает контроля с нулевым риском. Это защита нашей модели от ложного вывода: в учебной карточке нельзя назвать путь закрытым, пока не видно, какие части маршрута специально оставлены за её пределами.

Последовательность сборки карты

  1. Выбрать один asset. Не объединять пользовательские данные, сессию и документ в общий «веб».
  2. Записать precondition. Указать, при каком именованном условии первый шаг пути становится возможен.
  3. Разложить маршрут. Дать шагам порядок и глаголы; два пути не склеивать ради компактности.
  4. Назвать interruption. Control обязан указывать, какой шаг он ограничивает и каким решением.
  5. Ограничить evidence. Разрешить только наблюдения, привязанные к threat id и control id.
  6. Оставить остаток. Выписать открытый участок и owner question до получения hand-off.

Почему поле «control: enabled» не подходит

Статус enabled сообщает лишь, что кто-то считает control включённым. В нём нет scope: на каких представлениях, для какого маршрута и с каким исключением. В нём нет mechanism: что именно должно быть запрещено или разрешено. И в нём нет evidence boundary: наблюдалась строка, отрицательный пример или поведение. Даже если список controls полный, он может покрывать одинаковую часть path трижды и оставить критический переход без владельца.

М9-голос здесь намеренно сухой: не продавать confidence, а уменьшать неопределённость. Карта может показать, что одного control недостаточно; это полезный результат. Сильнее сказать «из этого evidence нельзя выводить корректность encoding», чем добавить ещё один зелёный бейдж. Тогда следующий инженер получает не настроение предыдущего review, а точную точку, в которой нужен другой метод проверки.

Как не потерять карту при изменении control

Изменение policy не должно переписывать историю одним статусом «обновлено». Сначала нужно спросить, сохраняется ли interruption. Если новый control по-прежнему ограничивает script без named nonce, карта может получить новую версию control id и сравнение старого evidence с новым пределом. Если же меняется сам момент прерывания — например, защита переносится из browser policy в обработку входа, — это уже другой маршрут и чужое evidence нельзя переносить автоматически. Название инструмента здесь не спасает: решающим остаётся шаг, который он ограничивает.

Это же правило работает с исключениями. Добавленный trusted source, новый render sink или дополнительная форма контента не являются мелкой правкой, пока не ясно, какую ступень path они затрагивают. Карта может честно вернуть незаполненный residual block и попросить отдельный review. Так изменение не превращается в полемику о том, достаточно ли «разумной настройки». У неё появляется узкий технический вопрос: совпадает ли старое evidence с новой парой threat/control, и что теперь осталось вне связи.

Границы и следующий шаг

Этот пакет не моделирует настоящий HTTP response, browser parser, CSP enforcement, заголовки, cookie, пользователя, сеть, repository или секрет. Он не утверждает, что CSP3 или ASVS автоматически защищают какую-либо систему. Источники используются только для узких формулировок: CSP — defence-in-depth; ASVS задаёт проверяемые web-frontend requirements; NIST перечисляет техники verification. Остальное — явно synthetic reasoning over fixed literals.

Следующий шаг не «включить control». Возьмите один уже существующий список мер и выберите из него одну строку. Для неё добавьте attack path, interruption и evidence boundary. Если связь не получается, верните stop-unnamed-attack-path или stop-unbound-evidence. Такой stop дешевле ложного закрытия: он показывает, какую запись надо сделать до того, как control начнёт жить отдельной легендой.

Проверяемые источники

  • W3C Content Security Policy Level 3 — версия: W3C Working Draft, 21 April 2026, dated immutable snapshot. CSP3 от 21.04.2026 называет CSP defense-in-depth, уменьшающей вред content injection, но не заменяющей validation и output encoding. Граница: Working Draft не является утверждением о настройке или поведении какого-либо браузера, сайта либо среды.
  • OWASP Application Security Verification Standard 5.0.0 — версия: release tag v5.0.0_release, commit 5cf9b032440be53ce345ab3c130fda46ba1ce7a2, 30 May 2025, immutable commit pin. Pinned ASVS 5.0.0 формулирует проверяемые требования для web frontend, включая CSP response header и ожидаемые browser security features. Граница: ASVS requirement не является результатом assessment и не доказывает interruption конкретного attack path.
  • NISTIR 8397: Guidelines on Minimum Standards for Developer Verification of Software — версия: NIST IR 8397, 6 October 2021, immutable DOI publication. NISTIR 8397 перечисляет threat modeling и несколько техник developer verification как рекомендации минимального уровня. Граница: Документ не задаёт эту synthetic data model и не заменяет evidence для отдельного control.