{ "index": 167, "slug": "editorial-2023-05-mechanism-static-analysis", "title": "SARIF без контекста: как читать результат статического анализа", "excerpt": "SARIF переносит результат проверки, но не принимает решение за команду. Разбираем границы между правилом, совпадением, строкой в коде и контекстом, который нужен для действия.", "contentHtml": "
В pull request появляется результат статического анализа. В нём есть ruleId, сообщение, URI файла и номер строки. Один инженер предлагает заблокировать слияние. Другой называет результат ложным срабатыванием и хочет отключить правило. Оба решения преждевременны: файл описывает наблюдение инструмента, но не объясняет, что происходит в приложении.
Цена ошибки зависит от выбранного обхода. Глобальное отключение убирает сигнал и для следующих участков кода. Безусловное блокирование превращает каждое совпадение формы в аварию. Команда тратит время на споры, а важный результат может затеряться среди шумных комментариев. Нужна простая граница: формат хранит данные, правило формулирует гипотезу, результат указывает на совпадение, а решение требует контекста проекта.
\nSARIF 2.1.0 — формат обмена результатами статического анализа. В нём можно передать версию формата, инструмент, набор правил, результат и позицию в артефакте. Это общий контейнер для CI, анализатора и просмотрщика. Он не знает, является ли участок достижимым в нужном релизе, кто владеет компонентом и разрешено ли исключение в конкретной команде.
\nПравило задаёт проверяемую гипотезу. Например: «значение из условно недоверенного источника передали в построение команды». Совпадение с шаблоном показывает только то, что форма кода похожа на гипотезу. Оно не доказывает источник значения, исполнение ветки или наличие уязвимости.
\nРезультат связывает гипотезу с наблюдением. ruleId показывает, какое правило сработало. message объясняет, что заметил инструмент. fingerprint помогает сопоставить результат между запусками. location указывает на файл и строку. Эти поля нужны для навигации и повторной проверки. Они не заменяют проверку исходника и границ данных.
| Слой | Пример данных | Что это означает | Чего не доказывает |
|---|---|---|---|
| Формат | version: 2.1.0 | Как читать log | Качество проверки |
| Правило | id, revision, level | Какая гипотеза задана | Риск именно в этом месте |
| Результат | ruleId, message, fingerprint | Какое совпадение найдено | Достижимость и влияние |
| Позиция | URI и номер строки | Где искать наблюдение | Что код исполняется |
| Контекст | asset, boundary, owner, scope | В каких условиях принимать решение | Полное покрытие сценариев |
Чтобы выбрать действие, добавьте к результату пять полей. asset называет компонент или поток данных. entryPoint показывает предполагаемую точку входа. trustBoundary фиксирует, почему значение считают недоверенным. owner указывает роль или человека, который может подтвердить устройство компонента. releaseScope связывает проверку с изменением, веткой или релизом.
Поле может быть неизвестно. Тогда запишите это прямо. Если не найден entry point, статус должен быть «контекст неполный», а не «безопасно». Если неизвестна граница доверия, нельзя объявлять значение проверенным. Такая запись сохраняет отрицательный путь: отсутствие доказательств не превращается ни в finding, ни в false positive.
\nНиже приведён искусственный объект в памяти. Он не читает файл, не запускает Semgrep, не вызывает shell и не описывает настоящий finding. Значения src/demo-command.js, строки и fingerprint нужны только для показа связей между полями.
const result = {\n ruleId: 'demo.untrusted-command-construction',\n message: { text: 'Проверить передачу значения в команду' },\n partialFingerprints: {\n primaryLocationLineHash: 'demo-fingerprint-001'\n },\n locations: [{\n physicalLocation: {\n artifactLocation: { uri: 'src/demo-command.js' },\n region: { startLine: 14 }\n }\n }]\n};\n\nconst context = {\n asset: 'demo-export-job',\n entryPoint: 'demo-http-handler',\n trustBoundary: 'demo-request-parameter',\n owner: 'demo-security-owner',\n releaseScope: 'demo-change-2023-05'\n};\nОбъект результата отвечает на вопрос «что и где совпало». Контекст отвечает на вопрос «какие условия нужно проверить перед действием». В примере нет исходного файла и нет доказательства, что строка исполняется. Поэтому допустимый вывод ограничен: нужно открыть соответствующую ревизию кода, проверить поток значения и подтвердить владельца.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Один ruleId повторяется в разных файлах | Синтаксическая форма шире проектного контекста | Сверить intent и revision правила, затем проверить источники значений | Оставить сигнал или уточнить правило с новой revision |
| В сообщении есть строка, но нет решения | Location приняли за доказательство исполнения | Проверить актуальную ревизию, entry point и достижимость ветки | Записать контекст; не повышать result до вердикта |
| Команда хочет убрать правило целиком | Шум одного результата смешали с политикой для всех файлов | Сравнить scope исключения с областью будущих результатов | Выбрать точечное исключение или изменить pattern |
| Результат исчез после обновления | Нет fingerprint и версии правила в записи review | Сопоставить base commit, tool version и revision | Повторить проверку и сохранить исходный result |
| Никто не подтверждает безопасность | У контекста нет owner или trust boundary | Назначить владельца и явно отметить неизвестные поля | Оставить result видимым до получения evidence |
ruleId, revision анализатора, fingerprint, URI, строку и commit. Не добавляйте вывод о риске, которого нет в данных.Строка в SARIF может устареть между анализом и review. Файл мог измениться, ветка могла не попасть в релиз, а код мог быть недостижимым при нужной конфигурации. Поэтому location — это адрес для проверки, а не доказательство runtime-пути.
\nSeverity тоже не является итоговой оценкой. Уровень правила задаёт ожидаемую реакцию инструмента. Он не учитывает бизнес-ценность asset, права вызывающего кода, компенсирующие проверки и область релиза. Переносить его напрямую в слово «критично» нельзя.
\nFingerprint полезен для повторного review, но это не score риска. Он помогает увидеть, что один результат сохранился, переместился или исчез. Причину изменения нужно искать в diff, версии правила и коде, а не в самом fingerprint.
\nРазделение слоёв не даёт гарантии, что анализатор найдёт все ошибки. SARIF может быть неполным или заполненным по-разному разными producer. Static analysis может не знать о динамической загрузке, feature flag, сгенерированном коде и runtime-конфигурации. Контекстная запись не заменяет тест, ручной data-flow review, проверку доступа или воспроизводимый запуск инструмента.
\nНе каждое правило стоит расширять. Более широкий pattern может поднять шум и увеличить стоимость review. Не каждое исключение стоит запрещать. Узкое, временное исключение с понятным объектом иногда лучше, чем изменение общего правила ради одного безопасного участка. Важны область действия, владелец, причина и дата повторной проверки.
\nПример в этой статье синтетический. Он проверяет только смысл полей и порядок рассуждения. Он не сообщает число срабатываний, coverage, false-positive rate, production effect или факт запуска в каком-либо репозитории.
\nРезультат можно передавать в review, когда выполнены четыре условия: правило и его revision известны; location проверена на актуальном commit; asset, trust boundary и owner записаны либо явно отмечены как неизвестные; выбранное действие имеет scope и способ отмены. Для tune должна существовать новая revision и описание изменённой гипотезы. Для scoped suppress нужны точный объект результата, причина и дата пересмотра.
\nЕсли хотя бы одно условие не выполнено, готовый статус — «контекст не собран». Это проверяемый результат: указан недостающий факт, назначен владелец и определён следующий шаг. Такой статус сохраняет сигнал и не обещает того, чего не подтверждают данные.
\n