Разбор · защитные меры
Атлас отказов · четырнадцать записей

Защита, которой нет.

Четырнадцать мест, где мера стояла, была отревьюена и считалась работающей — и не работала. Для каждого — как выглядит симптом и чем ловится.

У защиты два состояния, которые легко перепутать: мера стоит — и мера работает. Почти ничего из нижеследующего не нашлось инцидентом. Каждое нашлось вопросом «что должно быть правдой, если это работает» — и попыткой это сделать.

Чтением кода они тоже не находятся. Чтение говорит, что контроль есть. Оно не говорит, что до контроля доходит управление, что его ветка отказа когда-нибудь выполнялась и что право, на которое он опирается, действительно выдано.

Проверка, ни разу не показавшая отказ, не проверена.

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

01

Сервис поднялся не в том режимеОтсутствие — не опечатка

Симптом
Прод работает как дев, и ни в логах, ни в проверках здоровья ничего не выглядит неправильным.

Поле конфигурации, объявленное как набор допустимых значений, ловит опечатку. Ловит пустую строку. Отсутствие — не ловит: у отсутствия берётся умолчание, а умолчанием обычно стоит послабляющий режим.

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

Как ловится
У имени окружения нет умолчания, и без него сервис не поднимается. Тест проверяет отказ, а не разбор значения: разбор никогда и не был тем местом, которое ломалось.
02

Смена настройки сделала нечитаемым то, что уже в базеСхему выбирает шифротекст, а не конфигурация

Симптом
После правки конфигурации записанное раньше перестаёт расшифровываться, и ошибка говорит про неверный ключ.

Если схему расшифровки выбирает конфигурация, то её смена задним числом переосмысляет данные, записанные по старым правилам.

Дорого здесь не это, а диагноз. Тот, кто разбирается, идёт искать проблему с ключом, которой нет: данные целы, их просто читают не тем способом. Каждая минута этого поиска тратится не в том репозитории.

Как ловится
Схему называет префикс самого шифротекста, отсутствие префикса означает наследие. Конфигурация не может переосмыслить историю, которую писала не она.
03

Риск описан и остался открытымНазвать — не значит закрыть

Симптом
Дефект задокументирован, все о нём знают, и он на месте.

Тест может нести риск в своём имени и ничего про него не утверждать. Комментарий может назвать дыру точно и оставить её ровно там, где она была.

Это отказ команд, которые хорошо документируют. Описанная дыра ощущается закрытой не так, как неописанная, и получает странный иммунитет: её все читали, поэтому никто ей не удивляется, поэтому никто не тревожится. Она стоит на виду неделями, и никто ничего не прячет.

Как ловится
У открытого риска есть утверждение, падающее до его закрытия, или строка в списке, которую документ отказывается опустить. Пустая строка читается как «покрыто» — единственное, что здесь никогда нельзя было предполагать.
04

Механизм написан и ни разу не выполнялсяНаписано, право выдано, вызвать некому

Симптом
Функция есть, права на неё выданы отдельной строкой политики — и она не вызывается ниоткуда.

Мёртвый код обычно безвреден. Этот — нет: все, кто ниже по течению, уже записали операцию, к которой он относится, как подтверждённую. Существование механизма делает ту работу, которую должно было делать его выполнение.

Отдельная строка в политике доступа — не случайность. Она означает, что как минимум двое про этот механизм думали, и всё равно ничего не выполнялось.

Как ловится
Проверка, что у каждого публичного механизма есть вызывающий, поставленная в CI: иначе новый такой же добавится незамеченным.
05

Зелёная проверка, которая иначе и не моглаПризнак — не диагноз

Симптом
Проверка зелёная, и была бы зелёной в любом случае.

Скан исходников по именам переменных находит те имена, которые знает. Он не находит значение, приехавшее под другим именем, чужое тело ответа, залогированное целиком одним вызовом, и переменную, которую на прошлой неделе переименовали.

Его зелёный означает «этим способом не нашлось». Это заметно меньшее утверждение, чем то, которое в него прочитают, и в зазоре между ними правило тихо перестаёт соблюдаться, продолжая отчитываться.

Как ловится
Ограничение публикуется рядом с проверкой, в той же таблице, что и утверждение, которое она подпирает. Признак, выданный за диагноз, хуже отсутствия проверки: он закрывает вопрос, который должен был держать открытым.
06

Контроль перестал применяться и промолчалТихий отказ — дефект безопасности, а не удобства

Симптом
Мера больше не действует, и никто об этом не сообщил.

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

Каждое — контроль, переставший работать и продолжающий выглядеть на месте. Каждое обнаружится ровно тем событием, ради предотвращения которого ставилось. Тишина здесь не нейтральна: она и есть отчёт о том, что всё в порядке.

Как ловится
Отсутствие обязательного входа — отказ, а не умолчание. И у отказа есть собственный тест: отказ, который никто ни разу не вызвал, — это отказ, работу которого никто не видел.
07

Здоров, готов и не может делать свою работуГотовность проверяла зависимость, а не способность

Симптом
Обе пробы зелёные, каждый вход — пятисотка.

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

Как ловится
Готовность выполняет саму способность и отказывает, когда не может. И парное правило, которое важнее: то же запечатанное состояние обязано ломать выпуск и обязано не ломать проверку — иначе штатная пауза превращается в остановку всего, что ниже.
08

Запрос сам выбрал, сколько будет стоитьЦену назначает отправитель, если её не назначили вы

Симптом
Один запрос останавливает весь процесс, включая ручку здоровья.

Тело запроса без потолка доезжает до разборщика, а стоимость разбора выбирает тот, кто прислал байты.

Измерено на своём коде: 640 КБ структурированного двоичного формата стоили 29,5 секунды процессорного времени внутри однопоточного событийного цикла. Всё остальное ждало — включая проверку здоровья, и поэтому проблема разбора предъявляется как недоступность сервиса.

Как ловится
Потолок на размер тела, выбранный как решение о стоимости, а не выставленный как настройка. И точность в том, чем он не является: это не ограничение частоты, и он не означает, что байты не долетели.
09

Сервис ответил на вопрос, который не егоОтвечать фактом, а не решением

Симптом
Один сервис решает, что можно другому.

«Сессия есть» и «можно» — разные утверждения, и слой идентичности вправе произносить только первое.

Когда он произносит второе, его представление о достаточности оказывается вшито в каждого вызывающего. В день, когда одному из них понадобится порог строже — перевод крупнее, новая юрисдикция, клиент, который сам попросил, — правку придётся делать в чужой кодовой базе, руками команды, которая этот риск не несёт и оценить его не может.

Как ловится
Отдавать факты: чем именно подтверждена личность, когда, с какой стойкостью. Решать — вызывающему. Выглядит менее услужливо. Именно это держит решение рядом с деньгами.
10

Сильный фактор снимался слабымКонструкция прочна настолько, насколько дёшев самый дешёвый способ её разобрать

Симптом
Аккаунт защищён сильным вторым фактором, и снять этот фактор можно предъявлением слабого.

Добавление сильного второго фактора поднимает планку ровно до того места, где путь восстановления её опускает.

Если сильный фактор снимается предъявлением более слабого, то аккаунт защищён слабым — что бы ни было написано на экране подключения. Арифметика здесь не сложение, а минимум по всем путям, которыми фактор можно снять. А пути восстановления обычно проектирует тот, кто оптимизирует нагрузку на поддержку.

Как ловится
Снятие фактора требует того же фактора, а не уровня стойкости, которому удовлетворяет и что-то более слабое. Разница — между тем, **каким способом** подтвердили, и тем, **насколько стойко** подтвердили. Два утверждения, которые сливаются в одно при первом удобном случае.
11

Ключ ротировали, и старый продолжал работатьРотация — это смена и вывод из обращения

Симптом
Ротация проведена, обе стороны считают её завершённой, старый ключ принимается.

Ротация — две операции, и теряется вторая.

Вывод старого ключа из обращения обязан произойти всюду, где читают проверки, а таких мест обычно больше одного: публикуемый набор ключей для внешних и прямой поиск для своих маршрутов. Отфильтруй в одном — второе продолжит принимать. Это хуже, чем не ротировать вовсе: теперь все уверены, что сделано, и за скомпрометированным материалом никто не смотрит.

Та же форма — слоем ниже. Хранилище отчитывается о ротации, и ротация действительно произошла; не произошло поднятие минимальной версии, принимаемой к расшифровке, и снятая версия продолжает разворачивать старые дампы. Обе стороны прочитали успех операции как наступление свойства.

Как ловится
Вывод из обращения живёт в хранилище, которое читают все проверки, а не внутри каждой. Команда возвращает не ноль, пока не сделаны обе половины: команда, вышедшая с нулём на середине, говорит «готово» ровно там, где сделано наполовину. И тест ротирует, а потом предъявляет старый ключ каждому пути, какой есть.
12

Журнал, который мог править тот, о ком он написанСвидетельство определяется правами, а не намерением

Симптом
Журнал полон, и роль, чьи действия он фиксирует, может его переписать.

Таблица становится свидетельством тогда, когда роль, чьи действия она записывает, не может её изменить. Комментарий в модели «журнал аудита, не править» — это намерение, а намерения не являются контролем.

Довод распространяется и на форму: журнал, схему которого я меняю в одиночку, не является журналом по той же причине, по которой им не является журнал, который я могу очистить.

Как ловится
Отдельная роль-владелец; у записываемой роли — вставка и чтение и больше ничего. И кто-то действительно пробовал от этой роли `UPDATE`, `DELETE`, `TRUNCATE` и `ALTER` и смотрел, как каждая отказывает, — потому что это свойство живёт в боевой базе и больше нигде. На тестовой роль другая и грантов нет вовсе, поэтому прогон показывает, что журнал пишется, и не может показать, что журнал сопротивляется. Половина, которую он покрывает, — не интересная.
13

Проба сказала обратное правдеНулевой код возврата — не доказательство

Симптом
Проверка прав прошла, права не выданы.

Выдача себе права, которого у тебя нет, проходит без ошибки и без эффекта.

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

Как ловится
Проверять последствием, а не кодом возврата. Попробовать запрещённую операцию и требовать, чтобы она отказала.
14

Запись аудита удалилась вместе с тем, о ком написанаКаскад — удобство, стирающее свидетельство

Симптом
Пользователя удалили, и вместе с ним исчезла запись о том, что с ним делали.

Каскадный внешний ключ — очевидное умолчание, и он тихо делает свидетельство слабее того, о чём свидетельствует: строка, которую никому нельзя удалить напрямую, исчезает в тот момент, когда исчезает её субъект.

Этого никто не решает. Оно приезжает вместе со схемой, переживает ревью, потому что везде остальное так и делается, и обнаруживается в день, когда спрашивают, что делали с аккаунтом, которого больше нет.

Как ловится
Ссылка обнуляется, а не каскадит — и проверяется поведением на боевой базе, а не чтением схемы: схема и есть то, что выглядело правильным с самого начала.
Что общего у всех четырнадцати

Ни одно из них не про криптографию

Каждое — мера, которая отчитывалась об успехе и не применялась: её никто не просил отказать, а отказ, который никто ни разу не вызвал, — это отказ, работу которого никто не видел.

Контроль стоит ровно столько, сколько стоит его ветка отказа, а именно её никто не гоняет.

Две ловушки в этом списке — сканы исходников, и обе подписаны как признак, а не доказательство. Всё остальное нашлось одинаково: предъявить выведенный ключ каждому пути проверки, выполнить запрещённый запрос от роли, которая не должна мочь, прислать запрос, который стоит слишком дорого. Список ведётся открыто, потому что пустая строка в таком документе читается как «покрыто».

Если что-то из этого показалось знакомым

Пришлите, что обещают ваши защитные меры.

Политику, чеклист или три контроля, которые вы назовёте, если спросить, что защищает деньги. Отвечу письменно: какие из них сейчас не могут отказать и что нужно, чтобы каждый умел показать свой отказ. Бесплатно, без созвона, обычно за два дня. Интересно, кто это написал? Системы за этим →