Files
progcode/editorial/agent-rewrites/166.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
19 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 166,
"slug": "editorial-2023-05-field-static-analysis",
"title": "Шумное правило статического анализа: как принять обратимое решение",
"excerpt": "Один результат статического анализа не объясняет, нужно ли менять правило или подавлять сигнал. Разбираем контекст, scope, срок, rollback и проверяемый критерий готовности.",
"contentHtml": "<p>В CI появляется результат правила, которое ищет передачу недоверенного значения в построение команды. Команда открывает строку, видит безопасный для своего сценария путь и предлагает выключить правило. На следующем запуске исчезают все результаты этой категории. Цена ошибки — потеря сигнала в коде, который ещё никто не проверил, и отсутствие ответа на простой вопрос: почему правило стало тише и кто разрешил это изменение.</p>\n<p>Обратная ошибка тоже стоит дорого. Если каждое совпадение называть уязвимостью, review получает ложную срочность. Инженеры начинают закрывать предупреждения по тексту сообщения. После нескольких таких итераций доверие к анализатору падает. Поэтому результат анализатора — это повод проверить контекст, а не готовый вердикт.</p>\n<h2>Сначала отделите результат от вывода</h2>\n<p>SARIF хранит сведения об инструменте, правиле, результате и позиции в файле. Эти поля отвечают на вопрос «где и по какой гипотезе сработал анализатор». Они не доказывают, что ветка исполняется, значение пришло из сети или команда действительно запускается. Для этого нужен контекст проекта: источник значения, путь до опасного вызова, граница доверия, владелец кода и область действия решения.</p>\n<p>Возьмём узкую учебную гипотезу: значение из параметра запроса передают в функцию, которая строит команду. Фрагмент показывает форму, которую правило может искать. Он не является результатом реального сканирования и не доказывает уязвимость.</p>\n<pre><code>function runReport(request) {\n const reportName = request.query.name;\n return runShell(`report --name ${reportName}`);\n}\n\n// Учебный контекст: нужно отдельно проверить источник,\n// экранирование, достижимость ветки и фактический sink.</code></pre>\n<p>У этого совпадения есть несколько независимых вопросов. Может ли внешний пользователь менять <code>request.query.name</code>? Проверяет ли код значение до вызова? Принимает ли <code>runShell</code> строку как команду или передаёт аргументы безопасным массивом? Попадает ли функция в собираемый артефакт? Пока ответов нет, допустимы только формулировки «результат требует проверки» и «контекст неполный».</p>\n<h2>Три действия вместо глобального выключателя</h2>\n<p><code>keep</code> оставляет результат видимым. Выбирайте его, когда сигнал понятен, но контекст ещё не собран. Это не признание уязвимости и не отказ от исправления. Это сохранение наблюдаемости до следующей проверки.</p>\n<p><code>tune</code> меняет гипотезу правила. Такое действие нужно, если правило захватывает форму, которая не соответствует его назначению: например, оно не отличает безопасный массив аргументов от конкатенации строки. Tune требует новой версии правила, короткого описания diff и проверки того, какие совпадения перестанут появляться.</p>\n<p><code>suppress</code> временно ограничивает один идентифицируемый результат. У него должны быть точный fingerprint, узкий scope, владелец, причина и дата окончания. Suppress не делает код безопасным. Он только задаёт политику отображения конкретного сигнала.</p>\n<p>Глобальное <code>disable</code> не заменяет ни одно из этих действий. Оно меняет поведение правила для текущих и будущих результатов. Если проекту действительно нужна такая смена policy, её надо рассматривать отдельно: назвать категорию, оценить потерю сигнала, назначить владельца и определить способ вернуть правило. Нельзя прятать решение уровня policy в комментарии к одному результату.</p>\n<h2>Минимальный контракт решения</h2>\n<div class=\"table-scroll\"><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>Нет источника значения и trust boundary</td><td>Сопоставить ruleId, revision, URI, строку, fingerprint и путь данных</td><td>Оставить <code>keep</code> до сбора контекста</td></tr><tr><td>Правило срабатывает на безопасном API</td><td>Гипотеза не различает строку и массив аргументов</td><td>Прочитать intent правила и проверить diff на минимальной паре примеров</td><td>Выбрать <code>tune</code> с новой revision</td></tr><tr><td>Один результат блокирует выпуск</td><td>Исключение не имеет точного scope или срока</td><td>Проверить fingerprint, owner, reviewBy и expiresOn</td><td>Разрешить только scoped <code>suppress</code></td></tr><tr><td>Предлагают выключить всё правило</td><td>Результат одного участка смешан с policy категории</td><td>Оценить будущие результаты и отдельный rollback policy</td><td>Вынести global disable в отдельное решение</td></tr><tr><td>После изменения непонятно, что вернулось</td><td>Rollback удаляет запись, но не повторяет анализ</td><td>Сверить config diff и повторный отчёт на том же commit</td><td>Вернуть точное исключение или прежнюю revision и повторить проверку</td></tr></tbody></table></div>\n<h2>Как записать evidence</h2>\n<p>Запись должна быть короткой, но достаточной для повторной проверки. Для любого действия укажите <code>owner</code>, <code>reason</code>, <code>action</code> и <code>reviewBy</code>. Для <code>tune</code> добавьте новую <code>ruleRevision</code> и описание изменения. Для <code>suppress</code> добавьте точный <code>fingerprint</code>, scope и <code>expiresOn</code>. Дата следующего review не должна быть позже даты окончания исключения.</p>\n<p>Причина «шум» ничего не объясняет. Хорошая причина связывает решение с проверяемым фактом: «вызов получает массив аргументов после нормализации; правило ожидает конкатенацию строки; diff проверен на двух учебных формах». В настоящем проекте сюда добавляют ссылку на задачу, commit или сохранённый контекст без секретов. Не добавляйте в публичную запись токены, пользовательские данные и полный фрагмент чувствительного кода.</p>\n<h2>Учебная проверка отрицательных путей</h2>\n<p>Небольшой synthetic-пример полезен, когда нужно проверить сам контракт решения. Он должен отклонять неполные варианты: глобальное отключение, suppress без fingerprint, suppress без срока, tune без новой revision и context без trust boundary. Следующий фрагмент запускает только детерминированную проверку объектов в памяти. Он не читает репозиторий, не загружает rule pack, не запускает Semgrep, не меняет CI и не сообщает о найденной уязвимости.</p>\n<pre><code>const decision = {\n action: 'suppress',\n owner: 'security-review',\n reason: 'Проверен один synthetic result',\n fingerprint: 'synthetic-fingerprint-command-001',\n scope: 'exact-result',\n reviewBy: '2023-05-20',\n expiresOn: '2023-05-27'\n};\n\nconst valid =\n decision.action === 'suppress' &amp;&amp;\n decision.fingerprint &amp;&amp;\n decision.scope === 'exact-result' &amp;&amp;\n decision.reviewBy &lt;= decision.expiresOn;\n\nconsole.log(valid ? 'plan-valid' : 'plan-invalid');\n// Synthetic plan only: configuration is not applied.</code></pre>\n<p>Проверка должна быть полезна прежде всего отрицательным исходом. Если убрать fingerprint, изменить scope на общий или поставить <code>reviewBy</code> после <code>expiresOn</code>, план обязан стать недействительным. Если тест проходит при <code>action: 'disable-globally'</code>, контракт слишком слабый. Это проверка формы решения, а не доказательство качества правила и не оценка безопасности приложения.</p>\n<figure><img src=\"/assets/editorial/2023/static-analysis-2023-decision-gate.svg\" alt=\"Гейт решения для результата статического анализа: полный контекст ведёт к keep, tune или scoped suppress; глобальное отключение требует отдельной policy; rollback возвращает прежнюю видимость.\" loading=\"lazy\" /><figcaption>Сначала собирается контекст результата, затем выбирается узкое действие. Схема показывает policy-контракт, а не запуск анализатора, реальные findings или эффект в production.</figcaption></figure>\n<h2>Порядок действий</h2>\n<ol><li><strong>Зафиксируйте симптом.</strong> Сохраните ruleId, revision, URI, строку, уровень, сообщение и fingerprint. Не начинайте с изменения конфигурации.</li><li><strong>Восстановите контекст.</strong> Найдите источник значения, путь до sink, trust boundary, владельца кода и артефакт, в который попадает модуль.</li><li><strong>Проверьте intent правила.</strong> Прочитайте описание, версию и diff. Отдельно отметьте, что результат показывает, а чего не показывает.</li><li><strong>Выберите действие.</strong> Используйте keep для неполного контекста, tune для неверной гипотезы, scoped suppress для одного проверенного результата. Global disable вынесите в отдельную policy.</li><li><strong>Заполните evidence.</strong> Добавьте owner, reason, scope, fingerprint и даты. Для tune укажите новую revision и ожидаемую границу.</li><li><strong>Проверьте отрицательный путь.</strong> Убедитесь, что неполный контекст, общий suppress и просроченные даты отклоняются.</li><li><strong>Подготовьте rollback.</strong> Для suppress удалите точное исключение и повторите анализ. Для tune верните прежнюю revision и сравните diff. Не считайте rollback выполненным по одному изменению файла.</li></ol>\n<h2>Ограничения</h2>\n<p>Статический анализ не видит весь runtime-контекст. Правило может не знать о конфигурации, feature flag, генерации кода, маршруте данных, правах пользователя и фактическом deploy-артефакте. SARIF не превращает позицию в файле в доказательство исполнения. Одинаковая строка может быть опасной в одном сервисе и безопасной в другом.</p>\n<p>Формат исключений и fingerprint зависит от конкретного анализатора и версии CLI. Не переносите поля из учебного объекта в конфигурацию без проверки официальной документации. Не называйте synthetic result находкой, не заявляйте снижение числа ложных срабатываний без измерения и не утверждайте, что опасные случаи не потеряны без проверки на выбранном наборе кода.</p>\n<p>Rollback тоже имеет границу. Он возвращает видимость правила или убирает scoped exception. Он не отменяет уже выпущенный код и не доказывает безопасность старого состояния. Если исключение успело скрыть другие результаты, их нужно искать отдельным повторным анализом.</p>\n<h2>Критерий готовности</h2>\n<p>Решение готово, когда другой инженер может по записи ответить на пять вопросов: какой result разбирали, какую гипотезу проверяли, почему выбрали keep, tune или suppress, кто и когда пересматривает решение, как вернуть прежнюю видимость. Для tune должна существовать новая revision и проверенный diff. Для suppress должны совпадать fingerprint и scope, а expiry должна быть будущей. Для rollback должен быть выполнен повторный анализ на том же commit или явно зафиксировано, почему это невозможно.</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> — нормативное описание формата результатов; оно не подтверждает достоверность отдельного результата.</li><li><a href=\"https://github.com/semgrep/semgrep/releases\" target=\"_blank\" rel=\"noopener noreferrer\">Semgrep: официальные релизы</a> — источник для проверки версии CLI и её поведения; версия анализатора должна совпадать с версией проекта.</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>"
}