Пишем свои правила для semgrep

Ранее я уже писал пост про кастомные правила в Semgrep, но вчера благополучно случайно его удалил🥲

А это значит только одно: пора написать новую и уже более сильную статью по этой теме)

Для меня Semgrep по-настоящему раскрывается только в тот момент, когда ты начинаешь писать свои кастомные правила.

Потому что именно здесь инструмент перестаёт быть просто “SAST из коробки” и начинает адаптироваться под логику твоего проекта, твою модель угроз и твои требования к secure coding.

━━━━━━━━━━ Почему кастомные правила важны ━━━━━━━━━━

Готовые правила хороши как базовый уровень.

Они неплохо ловят общие проблемы, типовые паттерны уяз и стандартные anti-patterns. Но почти всегда у них есть предел: они не знают контекст именно вашего приложения.

А в реальных проектах решают как раз детали: • какие вызовы у вас действительно считаются опасными • в каком контексте поведение уже становится рискованным • какие конструкции нужно исключать, чтобы не собирать шум • какое remediation message увидит разработчик • поймёт ли он вообще, что от него хотят исправить

По сути кастомное правило - это уже не просто поиск. Это описание того, что именно ты считаешь важным для безопасности своего кода.

━━━━━━━━━━ С чего обычно начинается своё правило ━━━━━━━━━━

Почти всегда основа начинается с pattern.

Это главный кирпич правила: ты показываешь Semgrep, какую именно конструкцию кода нужно считать совпадением.

Самый простой пример: • pattern: eval(…)

Что здесь важно понять сразу: • pattern ищет не просто строку, а форму конструкции • … означает “здесь может быть что угодно” в допустимом контексте • в случае eval(…) ты говоришь: найди вызов eval, а аргументы уже могут быть разными

Следующий очень полезный уровень - metavariables.

Это шаблонные переменные вида: • $X • $ARG • $FUNC • $OBJ

Они нужны, когда ты хочешь не просто что-то найти, а связать между собой части совпадения: одно и то же имя, конкретный аргумент, объект или вызов.

━━━━━━━━━━ Как строится логика правила ━━━━━━━━━━

Когда правило становится чуть умнее, дальше обычно используются такие операторы:

➤ patterns Это логическое “И”. Нужно, когда должны совпасть сразу несколько условий.

➤ pattern-either Это логическое “ИЛИ”. Удобно, когда опасная конструкция встречается в нескольких формах.

➤ pattern-not Это отсечение шума. Сначала находишь общий случай, потом вырезаешь безопасный вариант.

➤ pattern-inside Полезно, когда конструкция опасна только внутри конкретного контекста: функции, блока, обработчика, middleware и т.д.

➤ pattern-not-inside Одна из самых полезных вещей против false positive. Помогает исключать сценарии, где нужный паттерн уже обёрнут или обработан безопасно.

Если правило становится сложнее, дальше уже подключается metavariable-regex.

Но здесь мой совет простой: сначала мыслить через pattern, а regex использовать только как дофильтрацию, а не как фундамент всего правила.

━━━━━━━━━━ Что ещё важно знать на старте ━━━━━━━━━━

Условно я бы делил правила на 2 уровня: • search rules - когда ты ищешь конкретные конструкции • taint rules - когда нужно отслеживать поток данных от source до sink

И вот taint rules - это уже следующий сильный уровень, потому что там начинается не просто поиск конструкции, а работа с dataflow.

Ещё отдельный полезный момент - generic mode.

Он нужен, когда ты работаешь не с классическим поддерживаемым языком, а с чем-то более текстовым или смешанным: • XML • конфиги • нестандартные файлы • шаблоны со смешанным содержимым

И ещё одна вещь, которую многие зря пропускают, - тестирование правил.

Если правило не тестируется, то со временем оно почти гарантированно начнёт шуметь или что-то пропускать.

И для меня кастомные правила Semgrep - это тот самый момент, когда SAST перестаёт быть просто “чем-то из коробки” и начинает реально подстраиваться под безопасность твоего проекта.

#AppSec #cybersecurity #semgrep

Пишем свои правила для semgrep | Сетка — социальная сеть от hh.ru