DarkRiDDeR14 мин

Проверка безопасности без чекбоксов: связываем требование, тест и результат

БезопасностьКачество

Строка «авторизация проверена» не говорит, что именно проверялось. Цена ошибки — закрыть задачу после happy path и не заметить чужой объект, неизвестную роль или изменение метода. Без связи между требованием, входом, ожидаемым результатом и фактическим выводом security-проверка становится списком галочек.

Соберём небольшую матрицу проверки для политики профиля: положительный случай, два отказа и неизвестная роль. Каждая строка содержит условие и ожидаемый ответ. Код запускается локально через node:assert/strict, поэтому результат можно повторить без доступа к данным или сети.

У требования должна быть проверяемая форма

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

Структура записи проверки
ПолеПримерПочему нужно
requirementIdAUTH-PROFILE-01стабильная ссылка на правило
inputuser u-1 → profile u-2что именно подали
expecteddeny / 403критерий решения
actualdeny / 403полученный результат
evidenceassertion outputчем подтверждён вывод
Петля проверки безопасности: требование, вход, отрицательный тест, результат и разбор расхождения.
Петля возвращает строку в проверку, если фактический результат отличается от ожидаемого. PASS не скрывает отрицательные случаи.

Тестируем отказ первым классом результата

В security-проверке deny — не исключение теста, а ожидаемый результат для запрещённого входа. Поэтому код должен проверять как статус, так и безопасную причину. Если функция возвращает только boolean, диагностировать обход сложнее: неизвестная роль, чужой объект и недопустимое действие смешиваются.

Набор тестов должен быть малым, но разнонаправленным. Один тест показывает разрешение владельцу, второй — горизонтальную границу, третий — вертикальную роль, четвёртый — неизвестный input. Для каждого случая сохраняйте название, а не только число пройденных assertions.

Учебная матрица и assert

Вход функции — объект запроса и запись профиля. Ожидаемый вывод содержит статус allow или deny и безопасную причину. Пример запускается одной командой Node и печатает имя каждого случая. Он не читает файл, сеть или секреты; его задача — показать форму проверяемого security-контракта.

import assert from 'node:assert/strict';

function decide({ role, subject, resource }) {
  if (role === 'admin' && resource.kind === 'audit') return { status: 'allow', reason: 'admin-audit' };
  if (role === 'user' && resource.kind === 'profile' && resource.owner === subject) return { status: 'allow', reason: 'owner' };
  return { status: 'deny', reason: 'default-deny' };
}

const cases = [
  ['owner can read', { role: 'user', subject: 'u-1', resource: { kind: 'profile', owner: 'u-1' } }, { status: 'allow', reason: 'owner' }],
  ['foreign owner is denied', { role: 'user', subject: 'u-1', resource: { kind: 'profile', owner: 'u-2' } }, { status: 'deny', reason: 'default-deny' }],
  ['unknown role is denied', { role: 'guest', subject: 'u-1', resource: { kind: 'profile', owner: 'u-1' } }, { status: 'deny', reason: 'default-deny' }],
];

for (const [name, input, expected] of cases) {
  assert.deepEqual(decide(input), expected, name);
  console.log('PASS', name);
}

Запуск печатает три строки PASS. Важна не сама библиотека assert, а форма входа и ожидаемого результата. Добавьте четвёртую строку для администратора и пятую для неизвестного ресурса, если эти ветки входят в ваш контракт. Не называйте отсутствие теста доказательством отсутствия уязвимости.

Матрица связывает код и источник

Идентификатор требования должен вести к месту кода, а название теста — к конкретной комбинации входов. Для одной политики допустимы несколько тестов. Если проверка падает, сохраняются diff: какой вход подали, какой ответ получили и какое правило ожидали. Это позволяет исправить policy, test или само требование, не меняя их незаметно одновременно.

Стандарт не задаёт ваш список ролей и не решает, какой ответ показывать внешнему пользователю. ASVS полезен как версионируемый словарь требований, а NIST SSDF — как рамка процесса безопасной разработки. Фактический вывод появляется только из вашего кода, тестовых данных и запуска.

Отрицательные случаи важнее красивого PASS

Минимальный набор входов
СлучайОжидаемое решениеОшибка при обходе
свой профильallowсломанный доступ к легитимному действию
чужой профильdenyгоризонтальная эскалация
audit для userdenyизбыточная роль
неизвестная рольdenyfail-open по умолчанию
неизвестный ресурсdeny или 404утечка существования

Порядок подготовки проверки

  1. Взять одно требование и записать его субъект, действие, объект и ожидаемый ответ.
  2. Составить положительный и минимум два отрицательных входа.
  3. Закрепить версию требования и имя теста, чтобы изменение стандарта было заметно.
  4. Запустить тест без UI и сохранить понятный вывод каждой строки.
  5. При падении сравнить фактический input, policy branch и expected result.
  6. Проверить, что тест не использует секреты, чужие данные и случайное внешнее состояние.

Ограничения и следующий шаг

Локальная функция не заменяет интеграционную проверку middleware, токенов, базы и кэша. Она не показывает race condition или ошибку маршрутизации. Модель угроз может потребовать отдельные проверки CSRF, SSRF, rate limit и журналирования. Матрица фиксирует границу, но не расширяет её автоматически.

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

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

  • NIST SP 800-218 — Secure Software Development Framework (SSDF) Version 1.1 — February 2022. Даёт общий словарь практик безопасной разработки и связывает защиту с жизненным циклом продукта. Граница применимости: Рамка не подтверждает наличие контроля, уязвимости или результата в конкретном приложении.
  • OWASP Application Security Verification Standard — Version 5.0.0, released 30 May 2025. Даёт версионируемые требования для проверки технических контролей веб-приложения. Граница применимости: Стандарт не заменяет модель угроз, настройку инфраструктуры и тестирование конкретных данных.
  • NIST SP 800-53 Rev. 5 — Security and Privacy Controls — September 2020, updates as of 10 December 2020. Помогает различить сам контроль и уверенность в его работе. Граница применимости: Каталог не выбирает права вашего ресурса и не является отчётом о соответствии.