{ "index": 167, "slug": "editorial-2023-05-mechanism-static-analysis", "title": "SARIF без контекста: как читать результат статического анализа", "excerpt": "SARIF переносит результат проверки, но не принимает решение за команду. Разбираем границы между правилом, совпадением, строкой в коде и контекстом, который нужен для действия.", "contentHtml": "

В pull request появляется результат статического анализа. В нём есть ruleId, сообщение, URI файла и номер строки. Один инженер предлагает заблокировать слияние. Другой называет результат ложным срабатыванием и хочет отключить правило. Оба решения преждевременны: файл описывает наблюдение инструмента, но не объясняет, что происходит в приложении.

\n

Цена ошибки зависит от выбранного обхода. Глобальное отключение убирает сигнал и для следующих участков кода. Безусловное блокирование превращает каждое совпадение формы в аварию. Команда тратит время на споры, а важный результат может затеряться среди шумных комментариев. Нужна простая граница: формат хранит данные, правило формулирует гипотезу, результат указывает на совпадение, а решение требует контекста проекта.

\n

Что именно сообщает анализатор

\n

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

\n

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

\n

Результат связывает гипотезу с наблюдением. ruleId показывает, какое правило сработало. message объясняет, что заметил инструмент. fingerprint помогает сопоставить результат между запусками. location указывает на файл и строку. Эти поля нужны для навигации и повторной проверки. Они не заменяют проверку исходника и границ данных.

\n
Граница между наблюдением и решением
СлойПример данныхЧто это означаетЧего не доказывает
Форматversion: 2.1.0Как читать logКачество проверки
Правилоid, revision, levelКакая гипотеза заданаРиск именно в этом месте
РезультатruleId, message, fingerprintКакое совпадение найденоДостижимость и влияние
ПозицияURI и номер строкиГде искать наблюдениеЧто код исполняется
Контекстasset, boundary, owner, scopeВ каких условиях принимать решениеПолное покрытие сценариев
\n

Минимальный контекст для review

\n

Чтобы выбрать действие, добавьте к результату пять полей. asset называет компонент или поток данных. entryPoint показывает предполагаемую точку входа. trustBoundary фиксирует, почему значение считают недоверенным. owner указывает роль или человека, который может подтвердить устройство компонента. releaseScope связывает проверку с изменением, веткой или релизом.

\n

Поле может быть неизвестно. Тогда запишите это прямо. Если не найден entry point, статус должен быть «контекст неполный», а не «безопасно». Если неизвестна граница доверия, нельзя объявлять значение проверенным. Такая запись сохраняет отрицательный путь: отсутствие доказательств не превращается ни в finding, ни в false positive.

\n

Пример: результат не равен вердикту

\n

Ниже приведён искусственный объект в памяти. Он не читает файл, не запускает Semgrep, не вызывает shell и не описывает настоящий finding. Значения src/demo-command.js, строки и fingerprint нужны только для показа связей между полями.

\n
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
\"Схема
Правило задаёт гипотезу, result указывает на совпадение, а project context связывает его с конкретным решением. Иллюстрация не показывает реальный запуск анализатора.
\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
\n

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

\n
  1. Зафиксируйте симптом. Сохраните ruleId, revision анализатора, fingerprint, URI, строку и commit. Не добавляйте вывод о риске, которого нет в данных.
  2. Прочитайте intent правила. Определите, какую форму оно ищет, какие языки и файлы входят в scope, какие условия считаются исключением.
  3. Проверьте исходник. Откройте ту же ревизию файла. Найдите entry point, источник значения, преобразования и вызов, на котором сработало правило.
  4. Заполните контекст. Назовите asset, trust boundary, owner и release scope. Неизвестные значения пометьте как неизвестные.
  5. Выберите узкое действие. Keep оставляет правило без изменений. Tune меняет гипотезу и получает новую revision. Scoped suppress ограничивает конкретный идентифицируемый результат и хранит причину, owner и дату пересмотра.
  6. Проверьте отрицательный путь. Если контекст не собран, не отключайте правило и не называйте совпадение подтверждённой уязвимостью. Назначьте следующий проверяемый шаг.
  7. Зафиксируйте rollback. Для изменения policy сохраните прежнюю revision и область действия. Возврат должен быть отдельным изменением конфигурации, а не устной договорённостью.
\n

Почему location и severity недостаточны

\n

Строка в SARIF может устареть между анализом и review. Файл мог измениться, ветка могла не попасть в релиз, а код мог быть недостижимым при нужной конфигурации. Поэтому location — это адрес для проверки, а не доказательство runtime-пути.

\n

Severity тоже не является итоговой оценкой. Уровень правила задаёт ожидаемую реакцию инструмента. Он не учитывает бизнес-ценность asset, права вызывающего кода, компенсирующие проверки и область релиза. Переносить его напрямую в слово «критично» нельзя.

\n

Fingerprint полезен для повторного review, но это не score риска. Он помогает увидеть, что один результат сохранился, переместился или исчез. Причину изменения нужно искать в diff, версии правила и коде, а не в самом fingerprint.

\n

Ограничения метода

\n

Разделение слоёв не даёт гарантии, что анализатор найдёт все ошибки. SARIF может быть неполным или заполненным по-разному разными producer. Static analysis может не знать о динамической загрузке, feature flag, сгенерированном коде и runtime-конфигурации. Контекстная запись не заменяет тест, ручной data-flow review, проверку доступа или воспроизводимый запуск инструмента.

\n

Не каждое правило стоит расширять. Более широкий pattern может поднять шум и увеличить стоимость review. Не каждое исключение стоит запрещать. Узкое, временное исключение с понятным объектом иногда лучше, чем изменение общего правила ради одного безопасного участка. Важны область действия, владелец, причина и дата повторной проверки.

\n

Пример в этой статье синтетический. Он проверяет только смысл полей и порядок рассуждения. Он не сообщает число срабатываний, coverage, false-positive rate, production effect или факт запуска в каком-либо репозитории.

\n

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

\n

Результат можно передавать в review, когда выполнены четыре условия: правило и его revision известны; location проверена на актуальном commit; asset, trust boundary и owner записаны либо явно отмечены как неизвестные; выбранное действие имеет scope и способ отмены. Для tune должна существовать новая revision и описание изменённой гипотезы. Для scoped suppress нужны точный объект результата, причина и дата пересмотра.

\n

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

\n

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

\n" }