DarkRiDDeR13 мин

SARIF без контекста: какие части правила и результата нужны для решения

БезопасностьАрхитектура

После нескольких спорных срабатываний команда получает SARIF-файл, видит ruleId, сообщение и позицию в исходнике, но всё равно не может решить, что делать. Одни считают result достаточным основанием для блокировки, другие предлагают убрать правило. Проблема в модели данных: файл результата и правило описывают форму наблюдения, а решение требует ещё контекста компонента и явной политики обработки.

Цена смешения слоёв быстро растёт. Если parser превращает location в доказательство исполнения, создаётся ложная срочность. Если ruleId воспринимается как «шум», правило пропадает для другого участка с иным входом. Если policy скрыта в комментарии к PR, нельзя восстановить владельца, scope и дату пересмотра. Надёжнее разложить сигнал по контракту: формат сохраняет поля, rule задаёт intent, result указывает на совпадение, а человек добавляет проектный контекст и решение.

SARIF — транспорт результата, а не модель приложения

Стандарт SARIF 2.1.0 определяет log с полем version и массивом runs. В run описываются tool, его driver и rules, а results ссылаются на ruleId и locations. Это хороший общий язык между анализатором, CI и просмотрщиком: result не нужно переводить в свободный текст при каждом шаге. Но общность формата имеет границу. SARIF не знает, какой asset важнее, какой feature flag включён, кто подтверждает boundary и какова допустимая политика исключений в этой команде.

Именно поэтому нужно не «обогащать SARIF догадками», а держать рядом отдельную context record. В нашем synthetic примере это пять строк: asset, entry point, trust boundary, owner и release scope. Их можно привязать к result fingerprint, но нельзя выдавать за поля, полученные из scanner. Такой разрыв делает audit честным: можно увидеть, что пришло из инструмента, что добавил reviewer и где решение всё ещё опирается на неизвестное.

Граница данных вокруг одного SARIF result
ОбъектПример поляКто его создаётЧто это означаетЧего не доказывает
SARIF logversion: 2.1.0producer форматаверсия транспортного контрактакачество анализа
Tool drivername, version, rulesанализаторкакой набор правил описанчто правило запускалось в вашем CI
Ruleid, level, revisionавтор policyintent и конфигурацию правилачто совпадение опасно именно здесь
ResultruleId, message, fingerprintанализатородно совпадение с ruleвыполнимость пути или exploitability
LocationURI, startLineанализаторуказатель на позициюактуальность ревизии или runtime execution
Context recordasset, boundary, ownerвладелец компонентаусловия проектного reviewполное покрытие всех сценариев

Правило должно называть свою гипотезу

Хорошее правило не обязано решать всю задачу безопасности. Ему достаточно точно назвать наблюдаемую гипотезу: «встречена конструкция команды с недоверенным значением». В synthetic contract rule имеет id, revision, category, default level и message. Revision важна: один и тот же id может жить дольше конкретного pattern, а reviewer должен видеть, по какой форме сделано решение. Level задаёт ожидаемую обработку, но не заменяет evidence для одного result.

Ниже показана учебная форма YAML. Она не лежит в файлах проекта и не выполняется fixture. В мае 2023 Semgrep v1.20.0 уже был опубликован, а исходник CLI с этим тегом версии явно объявлял --sarif как output format. Это позволяет сослаться на конкретную версию инструмента и формат, но не позволяет заявить, что правило ниже было запущено Semgrep, увидело исходник или воспроизводит чью-то production policy.

# Учебная форма правила: этот fragment не запускается fixture и не относится к проекту.
rules:
  - id: demo.untrusted-command-construction
    languages: [javascript]
    severity: WARNING
    message: "Проверить synthetic построение команды"
    patterns:
      - pattern: runShell($UNTRUSTED)

В реальном репозитории rule change надо review как код: зафиксировать revision, diff, языки, scope файлов и ожидаемую причину срабатывания. Нельзя оправдать изменение фразой «снизили шум», если не названо, какую форму теперь намеренно игнорируют. Если rule становится уже, это означает управляемый trade-off: часть будущих совпадений больше не придёт. Такой trade-off допустим только рядом с контекстом и владельцем, а не как побочный эффект неясного refactor YAML.

Схема границ данных: правило с версией и intent порождает synthetic SARIF result с location; отдельная context record добавляет asset, entry point, trust boundary, owner и scope; только затем появляется решение.
Схема объясняет контракт учебного процесса. Она не показывает настоящий запуск анализатора, фактический исходный код, покрытие, достижимость или вывод о безопасности.

Result и location надо читать как указатель

Result полезен, потому что соединяет message, ruleId и конкретный fingerprint. Fingerprint помогает отличить один результат от соседнего при повторном review. Но fingerprint — это идентификатор сопоставления, а не персональный риск-score. URI и строка тоже не становятся доказательством сами по себе: за время между анализом и review файл мог поменяться, а обработчик мог быть недостижим в интересующем релизе. Поэтому в context record стоит хранить ссылку на ревизию и scope, который проверяется, а рядом писать метод, которым планируется проверить entry point.

В fixture location намеренно синтетическая: src/demo-command.js:14. Assertions проверяют, что строка сохранилась в объекте и что sourceRead равен not-performed-by-fixture. Это защита от удачной, но ложной демонстрации. Если тест мог бы пройти, одновременно делая вывод «строка прочитана анализатором», он учил бы ровно той подмене, против которой направлена статья.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. У result есть message и location, но reviewer не может объяснить, почему действие должно быть keep, tune или suppress. Не назначайте severity по впечатлению от текста сообщения.
  2. Причина. Формат обмена, intent правила, совпадение и проектная политика слиты в один JSON или комментарий. Владелец каждого поля исчезает.
  3. Проверка. Выпишите отдельно version SARIF, tool version, rule id/revision, result fingerprint и location. Затем заведите context record с asset, entry point, boundary, owner и release scope.
  4. Действие. Свяжите context record с fingerprint, но храните его как проектное evidence. При неполных полях верните статус context-incomplete, а не подменяйте отсутствующие данные предположением.
  5. Проверка контракта. Запустите fixture. Он должен принять только synthetic-static-analysis-input-v1, отвергнуть SARIF другой версии и не добавить claims о runtime.
  6. Rollback. Если обсуждение меняет policy, в плане остаётся before state. Реальный rollback требует отдельного config diff и повторного анализа; synthetic output этого не исполняет.

Контекст не заменяет техническую проверку

Context record делает вопрос обозримым, но не превращается в доказательство. Поле entryPoint: demo-http-handler — гипотеза о месте входа. Поле trustBoundary: demo-request-parameter — договорённость, что именно считают недоверенным в этом сценарии. Для реального решения нужно выбрать метод проверки: review data flow, targeted test, trace в разрешённом окружении или другой подход, который команда может повторить. Метод и его границы следует записывать рядом, иначе на следующем review неизвестно, что именно было проверено.

NIST SSDF полезен здесь не как набор волшебных правил, а как напоминание о воспроизводимости безопасной разработки: изменение должно иметь владельца, evidence и путь повторной проверки. Если результат анализатора не совпадает с контекстом компонента, не надо заставлять один из слоёв притворяться другим. Лучше оставить запись «формат и rule известны; контекст или техническая проверка ещё не завершены». Это менее эффектно, чем слово finding, но точнее определяет следующий шаг.

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

Этот материал не сравнивает анализаторы, не измеряет false positive rate, не утверждает поддержку всех языков и не обещает, что SARIF одинаково заполнен каждым producer. Спецификация SARIF 2.1.0 имеет статус OASIS Standard от 27 марта 2020 года; Semgrep v1.20.0 — выбранная историческая точка от 28 апреля 2023 года. Их наличие не заменяет проверку реальной версии CLI, rule packs, schema validation, base commit и настроек CI конкретного проекта.

Следующий шаг — выбрать один существующий result и собрать два маленьких артефакта: неизменяемую выдержку из SARIF без секретов и context record с пятью полями. Затем на review сформулировать одну гипотезу rule и метод её проверки. Если context не собирается, не переписывайте rule наугад. Сначала назначьте owner и срок. Так формат остаётся транспортом фактов, а решение — проверяемой частью инженерной работы.

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