8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 167,
|
||
"slug": "editorial-2023-05-mechanism-static-analysis",
|
||
"title": "SARIF без контекста: как читать результат статического анализа",
|
||
"excerpt": "SARIF переносит результат проверки, но не принимает решение за команду. Разбираем границы между правилом, совпадением, строкой в коде и контекстом, который нужен для действия.",
|
||
"contentHtml": "<p>В pull request появляется результат статического анализа. В нём есть <code>ruleId</code>, сообщение, URI файла и номер строки. Один инженер предлагает заблокировать слияние. Другой называет результат ложным срабатыванием и хочет отключить правило. Оба решения преждевременны: файл описывает наблюдение инструмента, но не объясняет, что происходит в приложении.</p>\n<p>Цена ошибки зависит от выбранного обхода. Глобальное отключение убирает сигнал и для следующих участков кода. Безусловное блокирование превращает каждое совпадение формы в аварию. Команда тратит время на споры, а важный результат может затеряться среди шумных комментариев. Нужна простая граница: формат хранит данные, правило формулирует гипотезу, результат указывает на совпадение, а решение требует контекста проекта.</p>\n<h2>Что именно сообщает анализатор</h2>\n<p>SARIF 2.1.0 — формат обмена результатами статического анализа. В нём можно передать версию формата, инструмент, набор правил, результат и позицию в артефакте. Это общий контейнер для CI, анализатора и просмотрщика. Он не знает, является ли участок достижимым в нужном релизе, кто владеет компонентом и разрешено ли исключение в конкретной команде.</p>\n<p>Правило задаёт проверяемую гипотезу. Например: «значение из условно недоверенного источника передали в построение команды». Совпадение с шаблоном показывает только то, что форма кода похожа на гипотезу. Оно не доказывает источник значения, исполнение ветки или наличие уязвимости.</p>\n<p>Результат связывает гипотезу с наблюдением. <code>ruleId</code> показывает, какое правило сработало. <code>message</code> объясняет, что заметил инструмент. <code>fingerprint</code> помогает сопоставить результат между запусками. <code>location</code> указывает на файл и строку. Эти поля нужны для навигации и повторной проверки. Они не заменяют проверку исходника и границ данных.</p>\n<table><caption>Граница между наблюдением и решением</caption><thead><tr><th scope=\"col\">Слой</th><th scope=\"col\">Пример данных</th><th scope=\"col\">Что это означает</th><th scope=\"col\">Чего не доказывает</th></tr></thead><tbody><tr><td>Формат</td><td><code>version: 2.1.0</code></td><td>Как читать log</td><td>Качество проверки</td></tr><tr><td>Правило</td><td><code>id</code>, revision, level</td><td>Какая гипотеза задана</td><td>Риск именно в этом месте</td></tr><tr><td>Результат</td><td><code>ruleId</code>, message, fingerprint</td><td>Какое совпадение найдено</td><td>Достижимость и влияние</td></tr><tr><td>Позиция</td><td>URI и номер строки</td><td>Где искать наблюдение</td><td>Что код исполняется</td></tr><tr><td>Контекст</td><td>asset, boundary, owner, scope</td><td>В каких условиях принимать решение</td><td>Полное покрытие сценариев</td></tr></tbody></table>\n<h2>Минимальный контекст для review</h2>\n<p>Чтобы выбрать действие, добавьте к результату пять полей. <code>asset</code> называет компонент или поток данных. <code>entryPoint</code> показывает предполагаемую точку входа. <code>trustBoundary</code> фиксирует, почему значение считают недоверенным. <code>owner</code> указывает роль или человека, который может подтвердить устройство компонента. <code>releaseScope</code> связывает проверку с изменением, веткой или релизом.</p>\n<p>Поле может быть неизвестно. Тогда запишите это прямо. Если не найден entry point, статус должен быть «контекст неполный», а не «безопасно». Если неизвестна граница доверия, нельзя объявлять значение проверенным. Такая запись сохраняет отрицательный путь: отсутствие доказательств не превращается ни в finding, ни в false positive.</p>\n<h2>Пример: результат не равен вердикту</h2>\n<p>Ниже приведён искусственный объект в памяти. Он не читает файл, не запускает Semgrep, не вызывает shell и не описывает настоящий finding. Значения <code>src/demo-command.js</code>, строки и fingerprint нужны только для показа связей между полями.</p>\n<pre><code>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};</code></pre>\n<p>Объект результата отвечает на вопрос «что и где совпало». Контекст отвечает на вопрос «какие условия нужно проверить перед действием». В примере нет исходного файла и нет доказательства, что строка исполняется. Поэтому допустимый вывод ограничен: нужно открыть соответствующую ревизию кода, проверить поток значения и подтвердить владельца.</p>\n<figure><img src=\"/assets/editorial/2023/static-analysis-2023-rule-context.svg\" alt=\"Схема границ данных: правило и результат передают наблюдение, а контекст добавляет границу доверия, владельца и область изменения перед решением\" loading=\"lazy\" /><figcaption>Правило задаёт гипотезу, result указывает на совпадение, а project context связывает его с конкретным решением. Иллюстрация не показывает реальный запуск анализатора.</figcaption></figure>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Диагностика спорного результата</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Один ruleId повторяется в разных файлах</td><td>Синтаксическая форма шире проектного контекста</td><td>Сверить intent и revision правила, затем проверить источники значений</td><td>Оставить сигнал или уточнить правило с новой revision</td></tr><tr><td>В сообщении есть строка, но нет решения</td><td>Location приняли за доказательство исполнения</td><td>Проверить актуальную ревизию, entry point и достижимость ветки</td><td>Записать контекст; не повышать result до вердикта</td></tr><tr><td>Команда хочет убрать правило целиком</td><td>Шум одного результата смешали с политикой для всех файлов</td><td>Сравнить scope исключения с областью будущих результатов</td><td>Выбрать точечное исключение или изменить pattern</td></tr><tr><td>Результат исчез после обновления</td><td>Нет fingerprint и версии правила в записи review</td><td>Сопоставить base commit, tool version и revision</td><td>Повторить проверку и сохранить исходный result</td></tr><tr><td>Никто не подтверждает безопасность</td><td>У контекста нет owner или trust boundary</td><td>Назначить владельца и явно отметить неизвестные поля</td><td>Оставить result видимым до получения evidence</td></tr></tbody></table>\n<h2>Порядок проверки</h2>\n<ol><li><strong>Зафиксируйте симптом.</strong> Сохраните <code>ruleId</code>, revision анализатора, fingerprint, URI, строку и commit. Не добавляйте вывод о риске, которого нет в данных.</li><li><strong>Прочитайте intent правила.</strong> Определите, какую форму оно ищет, какие языки и файлы входят в scope, какие условия считаются исключением.</li><li><strong>Проверьте исходник.</strong> Откройте ту же ревизию файла. Найдите entry point, источник значения, преобразования и вызов, на котором сработало правило.</li><li><strong>Заполните контекст.</strong> Назовите asset, trust boundary, owner и release scope. Неизвестные значения пометьте как неизвестные.</li><li><strong>Выберите узкое действие.</strong> Keep оставляет правило без изменений. Tune меняет гипотезу и получает новую revision. Scoped suppress ограничивает конкретный идентифицируемый результат и хранит причину, owner и дату пересмотра.</li><li><strong>Проверьте отрицательный путь.</strong> Если контекст не собран, не отключайте правило и не называйте совпадение подтверждённой уязвимостью. Назначьте следующий проверяемый шаг.</li><li><strong>Зафиксируйте rollback.</strong> Для изменения policy сохраните прежнюю revision и область действия. Возврат должен быть отдельным изменением конфигурации, а не устной договорённостью.</li></ol>\n<h2>Почему location и severity недостаточны</h2>\n<p>Строка в SARIF может устареть между анализом и review. Файл мог измениться, ветка могла не попасть в релиз, а код мог быть недостижимым при нужной конфигурации. Поэтому location — это адрес для проверки, а не доказательство runtime-пути.</p>\n<p>Severity тоже не является итоговой оценкой. Уровень правила задаёт ожидаемую реакцию инструмента. Он не учитывает бизнес-ценность asset, права вызывающего кода, компенсирующие проверки и область релиза. Переносить его напрямую в слово «критично» нельзя.</p>\n<p>Fingerprint полезен для повторного review, но это не score риска. Он помогает увидеть, что один результат сохранился, переместился или исчез. Причину изменения нужно искать в diff, версии правила и коде, а не в самом fingerprint.</p>\n<h2>Ограничения метода</h2>\n<p>Разделение слоёв не даёт гарантии, что анализатор найдёт все ошибки. SARIF может быть неполным или заполненным по-разному разными producer. Static analysis может не знать о динамической загрузке, feature flag, сгенерированном коде и runtime-конфигурации. Контекстная запись не заменяет тест, ручной data-flow review, проверку доступа или воспроизводимый запуск инструмента.</p>\n<p>Не каждое правило стоит расширять. Более широкий pattern может поднять шум и увеличить стоимость review. Не каждое исключение стоит запрещать. Узкое, временное исключение с понятным объектом иногда лучше, чем изменение общего правила ради одного безопасного участка. Важны область действия, владелец, причина и дата повторной проверки.</p>\n<p>Пример в этой статье синтетический. Он проверяет только смысл полей и порядок рассуждения. Он не сообщает число срабатываний, coverage, false-positive rate, production effect или факт запуска в каком-либо репозитории.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Результат можно передавать в review, когда выполнены четыре условия: правило и его revision известны; location проверена на актуальном commit; asset, trust boundary и owner записаны либо явно отмечены как неизвестные; выбранное действие имеет scope и способ отмены. Для tune должна существовать новая revision и описание изменённой гипотезы. Для scoped suppress нужны точный объект результата, причина и дата пересмотра.</p>\n<p>Если хотя бы одно условие не выполнено, готовый статус — «контекст не собран». Это проверяемый результат: указан недостающий факт, назначен владелец и определён следующий шаг. Такой статус сохраняет сигнал и не обещает того, чего не подтверждают данные.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html\" target=\"_blank\" rel=\"noopener noreferrer\">OASIS: Static Analysis Results Interchange Format (SARIF) Version 2.1.0</a> — спецификация формата log, runs, rules, results и locations.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218, Secure Software Development Framework</a> — официальная рамка практик безопасной разработки, а не доказательство эффективности отдельного правила.</li></ul>"
|
||
}
|