{ "index": 178, "slug": "editorial-2023-01-field-threat-model", "title": "Модель угроз для одного потока: от симптома до проверяемого контроля", "excerpt": "Как разобрать security-sensitive изменение, когда команда предлагает контроль, но не называет актив, границу и злоупотребление. На примере одного API-потока — схема, код, таблица диагностики и критерий готовности.", "contentHtml": "
В ревью появляется знакомая фраза: «давайте добавим подпись», «закроем endpoint» или «поставим ограничение». Но никто не может ответить, какие данные защищаются и какой запрос должен быть отклонён. Команда выбирает технологию до того, как описывает угрозу. Ошибка стоит дороже лишней строки кода: контроль может сломать легитимный поток, не закрыть нужное злоупотребление и оставить владельца без доказательства результата.
\nМодель угроз нужна не для красивой схемы. Она связывает четыре вещи: ценный актив, источник запроса, границу доверия и действие, которое не должно пройти. Из этой связи следует контроль и проверка. Если связь не записана, «добавить безопасность» остаётся пожеланием. Если проверка не содержит отрицательного случая, команда не знает, работает ли защита.
\nОпишите не тревогу, а факт. Например: обработчик принимает запрос на изменение заявки, но контракт не говорит, как он отвергает неподтверждённый вход. Это не доказывает уязвимость. Факт только показывает пробел: у изменения нет явного условия отказа и нет артефакта, который его подтверждает.
\nЗатем зафиксируйте цену ошибки. Неподписанный запрос может изменить чужую заявку, если другая проверка не перекрывает этот путь. Слишком общий контроль может, наоборот, отвергать запросы клиентов и создавать обходной ручной процесс. В обоих случаях команда спорит о механизме, пока не назвала объект защиты и допустимое поведение.
\nДля первого прохода достаточно одного потока. Возьмём учебный пример: browser-client отправляет запрос в public-api-to-handler, handler записывает change-request в хранилище. Asset — заявка на изменение. Boundary — место, где публичный вход становится внутренним вызовом обработчика. Abuse — неподтверждённый запрос пытается изменить заявку. Это условная модель. Она не описывает конкретный продукт и не доказывает безопасность production-системы.
\nbrowser-client\n |\n | request\n v\n public-api-to-handler <-- boundary\n |\n v\n handler ----> change-request store (asset)\n\nabuse: неподтверждённый запрос меняет заявку\ncontrol: обработчик отклоняет вход без проверяемого подтверждения\nevidence: тест показывает отказ такого входа\nСхема полезна только тогда, когда каждый элемент ведёт к вопросу. Asset отвечает, какое свойство нельзя потерять. Boundary показывает, где меняются предположения о доверии. Abuse описывает действие нарушителя. Control формулирует решение. Evidence показывает, что решение проверили. Слово «система» не заменяет ни один из этих элементов.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Назван control, но нет asset | Выбрали привычный механизм | Что потеряет свойство при злоупотреблении? | Назвать один актив и его свойство |
| Есть threat, но нет boundary | Не указано место защитного решения | Где вход перестаёт быть доверенным? | Поставить границу на DFD и описать переход |
| Есть control, но нет отрицательной ветки | Проверяли только успешный путь | Какой вход обязан получить отказ? | Добавить тест на отказ и ожидаемый результат |
| В evidence написано «проверено» | Не назван метод и артефакт | Что увидит независимый проверяющий? | Указать test, log, trace или ручной шаг |
| Rollback означает «вернуть всё» | Модель смешана с поставкой | Какие файлы, права и данные меняются? | Разделить исходный snapshot и release-план |
Таблица отсекает ложную полноту. Заполненная строка не означает, что риск мал. Она означает, что следующий вопрос имеет адресата и ожидаемый ответ. Если ответ не находится, оставьте поле пустым и остановите выбор контроля. «Неизвестно» полезнее, чем выдуманное «защищено».
\nПроведите границу там, где меняются правила доверия. Для публичного API это может быть вход в handler, но не всегда. Если gateway уже проверяет подпись, а handler получает внутренний вызов, модель должна показать обе границы и владельца каждой проверки. Если вы назвали границей сеть только потому, что она видна на архитектурной схеме, контроль может оказаться не на том участке потока.
\nВ примере ниже signed-request — лишь учебная гипотеза. Её обещание узкое: handler принимает запрос только после проверяемого подтверждения. Это не синоним шифрования транспорта, аутентификации пользователя или авторизации операции. В настоящем API могут потребоваться другой протокол, nonce, защита от повторной отправки, проверка полномочий и журналирование. Статья не выбирает их за владельца системы.
\nМаленькая функция может поймать механические пропуски до обсуждения реализации. Она принимает actor, asset, boundary, abuse и один из заранее названных типов контроля. Для принятой записи возвращает evidence и snapshot для учебного отката. Такой код проверяет только структуру модели. Он не ходит в сеть, не проверяет ключ, не вызывает API и не оценивает риск.
\nconst plan = planTeachingThreatModel({\n actor: 'browser-client',\n asset: 'change-request',\n boundary: 'public-api-to-handler',\n abuse: 'unsigned request changes a request',\n control: 'signed-request'\n});\n\nif (!plan.accepted) throw new Error(plan.reason);\nif (plan.evidence.length !== 1) throw new Error('missing evidence');\nif (plan.rollback.snapshot.asset !== 'change-request') {\n throw new Error('rollback snapshot is incomplete');\n}\nВ этом фрагменте есть намеренный отрицательный путь. Пустой asset, неизвестный control или отсутствие boundary должны вернуть отказ, а не «почти принятую» модель. Название функции и ответ отражают учебный контракт. Не переносите его в production без отдельной проверки требований, реализации, секретов, прав и совместимости.
\nПосле успешной проверки контракта найдите реальную точку потока. Сопоставьте имя asset с полем документации или схемы, boundary — с middleware, gateway или handler, а evidence — с конкретным тестом или журналом. Если сопоставление не получается, локальный PASS ничего не говорит о приложении. Он только говорит, что четыре строки заполнены.
\nОстановите выбор контроля, если asset неизвестен или принадлежит нескольким владельцам. Нельзя оценить ущерб, пока не ясно, какое свойство защищается. Остановите работу, если boundary спорна и команда не может показать, где именно принимается решение. В этом случае уточните поток и ответственность, а не добавляйте второй механизм наугад.
\nОстановите объявление готовности, если есть только успешный тест. Контроль, который пропускает хороший запрос, ещё не показывает, что плохой запрос получает отказ. Нужны отрицательный вход, ожидаемый код или состояние, а также подтверждение, что запись не изменилась.
\nНе называйте локальную функцию security review, pentest или compliance evidence. Она не видит production, реальные роли, конфигурацию, ротацию ключей, повторную отправку, лимиты и операционные журналы. Учебный пример помогает проверить форму рассуждения. Он не заменяет анализ системы и согласование риска.
\nОдин поток готов к следующему этапу, когда независимый проверяющий по записи может назвать asset, actor, boundary и abuse; найти контроль в конкретном месте; запустить отрицательную проверку; увидеть ожидаемый отказ до изменения asset; определить сохранённый snapshot и условия отката. Если хотя бы один пункт требует устного пояснения автора, модель ещё не завершена.
\nКритерий не означает «угроз больше нет». Он означает, что команда понимает выбранный риск, границу утверждения и следующий реальный тест. Результат может быть «контроль не выбран», «проверка не выполнена» или «нужен владелец». Это корректные исходы. Они лучше фиктивного PASS, который не связан с поведением приложения.
\n