Строка «авторизация проверена» не говорит, что именно проверялось. Цена ошибки — закрыть задачу после happy path и не заметить чужой объект, неизвестную роль или изменение метода. Без связи между требованием, входом, ожидаемым результатом и фактическим выводом security-проверка становится списком галочек.
Соберём небольшую матрицу проверки для политики профиля: положительный случай, два отказа и неизвестная роль. Каждая строка содержит условие и ожидаемый ответ. Код запускается локально через node:assert/strict, поэтому результат можно повторить без доступа к данным или сети.
У требования должна быть проверяемая форма
Требование «пользователь видит только свой профиль» превращается в четыре части: субъект, объект, действие и ожидаемый эффект. Успешная строка проверяет владельца, отрицательная — другого владельца, а boundary-случай проверяет отсутствие объекта или роль. Положительный тест без отказов не показывает, что правило действительно ограничивает доступ.
| Поле | Пример | Почему нужно |
|---|---|---|
| requirementId | AUTH-PROFILE-01 | стабильная ссылка на правило |
| input | user u-1 → profile u-2 | что именно подали |
| expected | deny / 403 | критерий решения |
| actual | deny / 403 | полученный результат |
| evidence | assertion output | чем подтверждён вывод |
Тестируем отказ первым классом результата
В 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 для user | deny | избыточная роль |
| неизвестная роль | deny | fail-open по умолчанию |
| неизвестный ресурс | deny или 404 | утечка существования |
Порядок подготовки проверки
- Взять одно требование и записать его субъект, действие, объект и ожидаемый ответ.
- Составить положительный и минимум два отрицательных входа.
- Закрепить версию требования и имя теста, чтобы изменение стандарта было заметно.
- Запустить тест без UI и сохранить понятный вывод каждой строки.
- При падении сравнить фактический input, policy branch и expected result.
- Проверить, что тест не использует секреты, чужие данные и случайное внешнее состояние.
Ограничения и следующий шаг
Локальная функция не заменяет интеграционную проверку 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. Помогает различить сам контроль и уверенность в его работе. Граница применимости: Каталог не выбирает права вашего ресурса и не является отчётом о соответствии.