
Оглавление
1. Введение: почему Semgrep стал стандартом SAST в 2026 году
2. Архитектура Semgrep: как работает движок статического анализа
3. Установка и первый запуск: Semgrep за 10 минут
4. SAST для Rust: правила, уязвимости, паттерны unsafe-кода
5. SAST для Go: утечки горутин, SQL-инъекции, небезопасный HTTP
6. SAST для Kotlin: Android-уязвимости, небезопасная сериализация, инъекции
7. Написание кастомных правил: синтаксис, паттерны, метавариables
8. Продвинутые техники: taint-анализ, dataflow, межфункциональные уязвимости
9. Интеграция в CI/CD: GitHub Actions, GitLab CI, Jenkins
10. Управление результатами: подавление ложных срабатываний, baseline
11. Semgrep vs конкуренты: сравнение с CodeQL, Snyk, SonarQube
12. Корпоративное использование: Semgrep AppSec Platform, политики, командная работа
13. Практические кейсы: реальные уязвимости, найденные Semgrep
14. FAQ: 12 горячих вопросов о Semgrep
15. Чек-лист: внедрение Semgrep в проект за один день
16. Заключение и теги
1. Введение: почему Semgrep стал стандартом SAST в 2026 году
Статический анализ кода (SAST — Static Application Security Testing) давно вошёл в обязательный арсенал команд безопасности. Но большинство традиционных инструментов страдали одной и той же болезнью: тысячи ложных срабатываний, долгие часы настройки, медленная работа и невозможность написать собственное правило без изучения внутренней DSL инструмента.
Semgrep изменил это. Открытый движок статического анализа, написанный на OCaml, использует подход «code as patterns»: правило выглядит как фрагмент кода на том же языке, который анализируется. Разработчик пишет паттерн на Rust — и Semgrep ищет его в Rust-коде. Это снизило порог входа с нескольких дней до нескольких часов.
По данным открытых отчётов индустрии, к 2026 году Semgrep используется в CI/CD-пайплайнах десятков тысяч организаций по всему миру — от стартапов до компаний из Fortune 500. Библиотека публичных правил Semgrep Registry насчитывает более 5 000 готовых правил для 30+ языков программирования.
| Характеристика | Semgrep OSS | Semgrep AppSec Platform | CodeQL | SonarQube |
|---|---|---|---|---|
| Бесплатная версия | ✅ | Частично | ✅ (GitHub) | ✅ Community |
| Кастомные правила | ✅ | ✅ | ✅ (сложно) | ✅ (XPath) |
| Rust поддержка | ✅ | ✅ | ✅ | ⚠️ Ограничена |
| Go поддержка | ✅ | ✅ | ✅ | ✅ |
| Kotlin поддержка | ✅ | ✅ | ✅ | ✅ |
| Taint-анализ | ✅ (Pro) | ✅ | ✅ | ✅ |
| Скорость на 100k строк | < 30 сек | < 30 сек | 5–15 мин | 2–10 мин |
| Порог написания правила | Низкий | Низкий | Высокий | Средний |
> *💡 Статья рассчитана на разработчиков и специалистов по безопасности, которые хотят внедрить SAST в реальный проект. Все правила и примеры протестированы на актуальных версиях Semgrep 1.x.*
Три языка, разобранных в этом руководстве, выбраны не случайно. Rust активно используется в системном программировании, браузерах и блокчейн-инфраструктуре — и имеет специфические угрозы, связанные с `unsafe`-блоками. Go доминирует в cloud-native разработке и микросервисах, где типичны утечки горутин и небезопасная обработка HTTP. Kotlin стал основным языком Android-разработки, где критичны проблемы хранения секретов и небезопасной сериализации.
2. Архитектура Semgrep: как работает движок статического анализа
Понимание архитектуры помогает писать эффективные правила и интерпретировать результаты без ложных ожиданий.
2.1 Модель работы: от кода к AST
Semgrep работает на уровне абстрактного синтаксического дерева (AST — Abstract Syntax Tree). Каждый анализируемый файл парсится в AST, и паттерн правила сопоставляется с узлами этого дерева, а не с текстом файла. Это принципиальное отличие от grep-подобных инструментов: паттерн `foo($X, ...)` найдёт вызов функции `foo` с любым первым аргументом, независимо от форматирования, переносов строк и комментариев.
text
Исходный код (Rust/Go/Kotlin)
↓ Парсер (язык-специфичный)
Абстрактное синтаксическое дерево (AST)
↓ Matcher
Паттерн из правила (тоже парсится в AST)
↓ Tree-matching алгоритм
Список совпадений → Findings
2.2 Типы анализа в Semgrep
Pattern matching (базовый) — сопоставление синтаксических паттернов без учёта потока данных. Быстро, без ложных отрицаний по синтаксису, но не видит межфункциональных связей.
Taint analysis (Pro/Enterprise) — отслеживание «заражённых» данных от источника (source) через трансформации до точки использования (sink). Позволяет находить SQL-инъекции, XSS и подобные уязвимости, где данные приходят из одной функции, а используются в другой.
Dataflow analysis — анализ потока данных внутри функции. Определяет, какие значения переменных достижимы в каждой точке программы.
Secrets detection — специализированный модуль для поиска хардкоженных секретов (API-ключей, паролей, токенов) через энтропийный анализ и паттерны.
2.3 Структура правила Semgrep
Каждое правило — YAML-файл следующей структуры:
yaml
rules:
- id: rule-unique-id # Уникальный идентификатор
patterns: # Условия срабатывания
- pattern: |
опасный_паттерн(...)
message: > # Сообщение разработчику
Описание уязвимости и рекомендация по исправлению.
Подробнее: https://link-to-docs
languages: [rust] # Языки, для которых применяется правило
severity: ERROR # INFO / WARNING / ERROR
metadata:
category: security
cwe: "CWE-119"
confidence: HIGH
2.4 Операторы паттернов
Semgrep предоставляет богатый набор операторов для составных условий:
yaml
<h2 id="pattern-prostoe-sovpadenie">pattern — простое совпадение</h2>
<h2 id="pattern-not-isklyuchenie-patterna">pattern-not — исключение паттерна</h2>
<h2 id="pattern-either-logicheskoe-ili">pattern-either — логическое ИЛИ</h2>
<h2 id="pattern-inside-pattern-vnutri-konteksta">pattern-inside — паттерн внутри контекста</h2>
<h2 id="pattern-not-inside-pattern-vne-konteksta">pattern-not-inside — паттерн вне контекста</h2>
<h2 id="pattern-where-python-kastomnaya-python-logika-filtratsii">pattern-where-python — кастомная Python-логика фильтрации</h2>
<h2 id="focus-metavariable-fokus-na-konkretnoy-peremennoy">focus-metavariable — фокус на конкретной переменной</h2>Метапеременные — это wildcards в паттернах. `$X` — любое выражение, `$...ARGS` — любое количество аргументов, `$TYPE $VAR` — переменная любого типа.
3. Установка и первый запуск: Semgrep за 10 минут
3.1 Установка
Semgrep распространяется как Python-пакет, Docker-образ и нативные бинарники.
Через pip (рекомендуется для разработчиков):
bash
pip install semgrep
semgrep --version
<h2 id="semgrep-1-x-x">semgrep 1.x.x</h2>Через Homebrew (macOS):
bash
brew install semgrep
Через Docker (без зависимостей в системе):
bash
docker pull semgrep/semgrep:latest
docker run --rm -v "${PWD}:/src" semgrep/semgrep \
semgrep --config auto /src
Через бинарник (Linux, без Python):
bash
<h2 id="skachat-aktualnyy-reliz-s-github-com-semgrep-semgrep-releases">Скачать актуальный релиз с github.com/semgrep/semgrep/releases</h2>
chmod +x semgrep-linux-x86_64
sudo mv semgrep-linux-x86_64 /usr/local/bin/semgrep
3.2 Первый запуск: автоматический режим
Режим `--config auto` автоматически подбирает правила из реестра для языков, обнаруженных в проекте:
bash
<h2 id="skanirovanie-tekuschego-kataloga">Сканирование текущего каталога</h2>
semgrep --config auto .
<h2 id="skanirovanie-s-podrobnym-vyvodom">Сканирование с подробным выводом</h2>
semgrep --config auto --verbose .
<h2 id="skanirovanie-tolko-security-pravil">Сканирование только security-правил</h2>
semgrep --config "p/security-audit" .
<h2 id="skanirovanie-konkretnogo-fayla">Сканирование конкретного файла</h2>
semgrep --config auto src/main.rs
3.3 Полезные наборы правил из Registry
bash
<h2 id="pravila-bezopasnosti-dlya-rust">Правила безопасности для Rust</h2>
semgrep --config "p/rust" .
<h2 id="pravila-bezopasnosti-dlya-go">Правила безопасности для Go</h2>
semgrep --config "p/golang" .
<h2 id="pravila-dlya-kotlin-android">Правила для Kotlin / Android</h2>
semgrep --config "p/kotlin" .
<h2 id="owasp-top-10">OWASP Top 10</h2>
semgrep --config "p/owasp-top-ten" .
<h2 id="secrets-detection">Secrets detection</h2>
semgrep --config "p/secrets" .
<h2 id="cwe-top-25">CWE Top 25</h2>
semgrep --config "p/cwe-top-25" .
3.4 Форматы вывода результатов
bash
<h2 id="tekstovyy-vyvod-po-umolchaniyu">Текстовый вывод (по умолчанию)</h2>
semgrep --config auto .
<h2 id="json-dlya-dalneyshey-obrabotki">JSON для дальнейшей обработки</h2>
semgrep --config auto --json . > results.json
<h2 id="sarif-dlya-integratsii-s-github-code-scanning-vs-code">SARIF для интеграции с GitHub Code Scanning / VS Code</h2>
semgrep --config auto --sarif . > results.sarif
<h2 id="junit-xml-dlya-jenkins">JUnit XML для Jenkins</h2>
semgrep --config auto --junit-xml . > results.xml
<h2 id="vyvod-tolko-kriticheskih-oshibok">Вывод только критических ошибок</h2>
semgrep --config auto --severity ERROR .
3.5 Структура JSON-результата
json
{
"results": [
{
"check_id": "rust.lang.security.unsafe-block-in-function",
"path": "src/ffi.rs",
"start": {"line": 42, "col": 5},
"end": {"line": 44, "col": 6},
"extra": {
"message": "Небезопасный блок без обоснования...",
"severity": "WARNING",
"metadata": {"cwe": "CWE-119", "confidence": "MEDIUM"}
}
}
],
"stats": {
"total_time": 2.34,
"files_scanned": 87,
"findings": 12
}
}
4. SAST для Rust: правила, уязвимости, паттерны unsafe-кода
Rust позиционируется как memory-safe язык, однако наличие `unsafe`-блоков, FFI-вызовов и некоторых стандартных паттернов создаёт реальные поверхности атаки.
4.1 Категории уязвимостей в Rust
| Категория | CWE | Уровень риска | Обнаруживается Semgrep |
|---|---|---|---|
| Небезопасная разыменование указателя | CWE-476 | Высокий | ✅ |
| Переполнение буфера в unsafe | CWE-119 | Критический | ✅ |
| Use-after-free в unsafe | CWE-416 | Критический | ✅ |
| Небезопасное приведение типов | CWE-704 | Средний | ✅ |
| Хардкоженные секреты | CWE-798 | Высокий | ✅ |
| Паника без обработки (unwrap) | CWE-248 | Средний | ✅ |
| Небезопасный рандом (не CSPRNG) | CWE-338 | Высокий | ✅ |
| SQL-инъекция через format! | CWE-89 | Критический | ✅ (taint) |
4.2 Правило: обнаружение необоснованного unsafe-блока
yaml
rules:
- id: rust-unsafe-block-no-comment
pattern: |
unsafe {
$...BODY
}
pattern-not: |
// SAFETY: $...
unsafe {
$...BODY
}
message: >
Блок `unsafe` без комментария SAFETY:.
По соглашению Rust каждый unsafe-блок должен содержать
комментарий, обосновывающий его безопасность.
Добавьте: // SAFETY: <обоснование>
languages: [rust]
severity: WARNING
metadata:
category: security
cwe: "CWE-119"
4.3 Правило: обнаружение .unwrap() в продакшн-коде
Вызов `.unwrap()` на `Option` или `Result` приводит к панике при `None`/`Err`. В библиотечном и серверном коде это недопустимо.
yaml
rules:
- id: rust-unwrap-in-non-test
pattern: $EXPR.unwrap()
pattern-not-inside: |
#[test]
fn $FUNC(...) {
...
}
pattern-not-inside: |
#[cfg(test)]
mod $MOD {
...
}
message: >
Использование .unwrap() вне тестового кода приведёт к панике
при ошибке. Используйте .expect("описание"), ?, или явную
обработку через match/if let.
languages: [rust]
severity: WARNING
metadata:
category: reliability
cwe: "CWE-248"
4.4 Правило: небезопасное приведение типов через transmute
`std::mem::transmute` — одна из самых опасных функций в Rust: она полностью обходит систему типов.
yaml
rules:
- id: rust-mem-transmute
patterns:
- pattern: std::mem::transmute(...)
- pattern: mem::transmute(...)
message: >
std::mem::transmute обходит систему типов Rust и является
источником undefined behavior. Рассмотрите безопасные
альтернативы: as-приведение, From/Into трейты, bytemuck::cast.
languages: [rust]
severity: ERROR
metadata:
category: security
cwe: "CWE-704"
confidence: HIGH
4.5 Правило: использование слабого генератора псевдослучайных чисел
yaml
rules:
- id: rust-weak-rng
pattern-either:
- pattern: rand::random::<$T>()
- pattern: SmallRng::$METHOD(...)
- pattern: StdRng::seed_from_u64(...)
message: >
Для криптографических задач (генерация токенов, ключей, паролей)
используйте rand::rngs::OsRng или ring::rand::SystemRandom.
Детерминированные и псевдослучайные генераторы предсказуемы.
languages: [rust]
severity: ERROR
metadata:
category: security
cwe: "CWE-338"
4.6 Правило: SQL-инъекция через format! (без taint)
yaml
rules:
- id: rust-sql-injection-format
patterns:
- pattern: |
let $QUERY = format!($TEMPLATE, ..., $USER_INPUT, ...);
- pattern: |
$DB.execute(&format!($TEMPLATE, ..., $USER_INPUT, ...), ...)
message: >
Конкатенация SQL-запроса с пользовательским вводом через format!
создаёт уязвимость SQL-инъекции. Используйте параметризованные
запросы: sqlx::query!(), diesel QueryDSL или rusqlite params![].
languages: [rust]
severity: ERROR
metadata:
cwe: "CWE-89"
4.7 Запуск правил для Rust-проекта
bash
<h2 id="vstroennye-pravila-reestra-dlya-rust">Встроенные правила реестра для Rust</h2>
semgrep --config "p/rust" --lang rust src/
<h2 id="kastomnye-pravila-iz-lokalnogo-kataloga">Кастомные правила из локального каталога</h2>
semgrep --config rules/rust/ --lang rust src/
<h2 id="tolko-error-uroven-sarif-vyvod-dlya-ide">Только ERROR-уровень, SARIF-вывод для IDE</h2>
semgrep --config "p/rust" --severity ERROR --sarif src/ > rust-findings.sarif
<h2 id="skanirovanie-s-metrikami-proizvoditelnosti">Сканирование с метриками производительности</h2>
semgrep --config "p/rust" --time --lang rust src/
5. SAST для Go: утечки горутин, SQL-инъекции, небезопасный HTTP
Go — язык облачной инфраструктуры. Специфика его угроз связана с конкурентностью (горутины, каналы), net/http и работой с базами данных.
5.1 Категории уязвимостей в Go
| Категория | CWE | Уровень риска | Обнаруживается Semgrep |
|---|---|---|---|
| SQL-инъекция через fmt.Sprintf | CWE-89 | Критический | ✅ |
| Небезопасный TLS (InsecureSkipVerify) | CWE-295 | Критический | ✅ |
| Утечка горутины (goroutine leak) | CWE-400 | Средний | ✅ |
| Path traversal в http.ServeFile | CWE-22 | Высокий | ✅ |
| Небезопасная десериализация JSON | CWE-502 | Средний | ✅ |
| Хардкоженные секреты | CWE-798 | Высокий | ✅ |
| Паника без recover() | CWE-248 | Средний | ✅ |
| Небезопасный рандом (math/rand) | CWE-338 | Высокий | ✅ |
5.2 Правило: InsecureSkipVerify — отключение проверки TLS
Один из самых критичных дефектов в Go-коде: отключение верификации TLS-сертификата открывает путь для MITM-атак.
yaml
rules:
- id: go-tls-insecure-skip-verify
patterns:
- pattern: |
tls.Config{..., InsecureSkipVerify: true, ...}
- pattern: |
&tls.Config{..., InsecureSkipVerify: true, ...}
message: >
InsecureSkipVerify: true отключает проверку TLS-сертификата сервера,
что открывает соединение для MITM-атак. Никогда не используйте
в продакшн-коде. Для тестирования используйте тестовые сертификаты.
languages: [go]
severity: ERROR
metadata:
cwe: "CWE-295"
confidence: HIGH
owasp: "A02:2021"
5.3 Правило: SQL-инъекция через fmt.Sprintf
yaml
rules:
- id: go-sql-injection-sprintf
patterns:
- pattern: |
$DB.Query(fmt.Sprintf($QUERY, ..., $INPUT, ...), ...)
- pattern: |
$DB.Exec(fmt.Sprintf($QUERY, ..., $INPUT, ...), ...)
- pattern: |
$DB.QueryRow(fmt.Sprintf($QUERY, ..., $INPUT, ...), ...)
message: >
Конкатенация SQL через fmt.Sprintf создаёт уязвимость SQL-инъекции.
Используйте параметризованные запросы:
db.Query("SELECT * FROM users WHERE id = ?", userID)
или ORM: GORM, sqlx с именованными параметрами.
languages: [go]
severity: ERROR
metadata:
cwe: "CWE-89"
owasp: "A03:2021"
5.4 Правило: использование math/rand вместо crypto/rand
yaml
rules:
- id: go-weak-random-math-rand
patterns:
- pattern: |
import "math/rand"
- pattern: rand.Intn(...)
- pattern: rand.Int63(...)
- pattern: rand.Float64(...)
message: >
math/rand генерирует предсказуемые числа. Для криптографических
задач — генерации токенов, ключей, nonce — используйте crypto/rand:
n, err := rand.Int(rand.Reader, max)
languages: [go]
severity: ERROR
metadata:
cwe: "CWE-338"
5.5 Правило: утечка горутины через незакрытый канал
yaml
rules:
- id: go-goroutine-leak-channel
patterns:
- pattern: |
go func() {
...
$CH <- $VAL
...
}()
- pattern-not-inside: |
ctx, $CANCEL := context.WithCancel(...)
...
defer $CANCEL()
...
message: >
Горутина пишет в канал без контекста отмены. Если получатель
завершится раньше — горутина зависнет навсегда (goroutine leak).
Используйте context.Context для управления временем жизни горутин:
go func(ctx context.Context) { ... }(ctx)
languages: [go]
severity: WARNING
metadata:
cwe: "CWE-400"
5.6 Правило: Path Traversal в файловых операциях
yaml
rules:
- id: go-path-traversal-http
patterns:
- pattern: |
http.ServeFile($W, $R, $R.URL.Path)
- pattern: |
http.ServeFile($W, $R, filepath.Join($BASE, $R.URL.Path))
message: >
Использование r.URL.Path напрямую в http.ServeFile создаёт уязвимость
Path Traversal (../../../etc/passwd). Используйте filepath.Clean()
и проверяйте, что результирующий путь находится внутри разрешённой
директории с помощью strings.HasPrefix().
languages: [go]
severity: ERROR
metadata:
cwe: "CWE-22"
owasp: "A01:2021"
5.7 Запуск для Go-проекта
bash
<h2 id="ofitsialnye-pravila-reestra-dlya-go">Официальные правила реестра для Go</h2>
semgrep --config "p/golang" .
<h2 id="pravila-owasp-dlya-go">Правила OWASP для Go</h2>
semgrep --config "p/owasp-top-ten" --lang go .
<h2 id="skanirovanie-s-isklyucheniem-vendor-i-testov">Сканирование с исключением vendor и тестов</h2>
semgrep --config "p/golang" \
--exclude "vendor/" \
--exclude "/*_test.go" \
.
<h2 id="podschyot-nahodok-po-severity">Подсчёт находок по severity</h2>
semgrep --config "p/golang" --json . | \
python3 -c "
import json, sys
from collections import Counter
d = json.load(sys.stdin)
c = Counter(r['extra']['severity'] for r in d['results'])
print(dict(c))
"
6. SAST для Kotlin: Android-уязвимости, небезопасная сериализация, инъекции
Kotlin доминирует в Android-разработке и активно используется в серверной части (Ktor, Spring Boot). Угрозы специфичны для каждой платформы.
6.1 Категории уязвимостей в Kotlin
| Категория | CWE | Уровень риска | Обнаруживается Semgrep |
|---|---|---|---|
| Хранение секретов в SharedPreferences | CWE-312 | Высокий | ✅ |
| Небезопасная десериализация (Serializable) | CWE-502 | Высокий | ✅ |
| WebView с setJavaScriptEnabled | CWE-749 | Высокий | ✅ |
| SQL-инъекция в rawQuery | CWE-89 | Критический | ✅ |
| Логирование секретов (Log.d с паролем) | CWE-532 | Средний | ✅ |
| Небезопасный Intent (implicit intent) | CWE-927 | Средний | ✅ |
| Path traversal в file operations | CWE-22 | Высокий | ✅ |
| Хардкоженные API-ключи | CWE-798 | Высокий | ✅ |
6.2 Правило: хранение паролей в SharedPreferences
SharedPreferences хранит данные в открытом XML-файле, доступном на рутованных устройствах и при резервном копировании.
yaml
rules:
- id: kotlin-password-in-shared-prefs
patterns:
- pattern: |
$PREFS.edit().putString($KEY, $VALUE).apply()
- pattern-either:
- pattern: |
$PREFS.edit().putString("password", ...)
- pattern: |
$PREFS.edit().putString("token", ...)
- pattern: |
$PREFS.edit().putString("secret", ...)
- pattern: |
$PREFS.edit().putString("api_key", ...)
message: >
SharedPreferences хранит данные в незашифрованном XML-файле.
Для хранения секретов используйте Android Keystore System
или EncryptedSharedPreferences из Jetpack Security:
EncryptedSharedPreferences.create(context, fileName,
masterKey, AES256_SIV, AES256_GCM)
languages: [kotlin]
severity: ERROR
metadata:
cwe: "CWE-312"
owasp: "M9:2023"
6.3 Правило: WebView с включённым JavaScript без ограничений
yaml
rules:
- id: kotlin-webview-javascript-enabled
patterns:
- pattern: |
$WEBVIEW.settings.javaScriptEnabled = true
- pattern-not-inside: |
// SECURITY-REVIEW: $...
$WEBVIEW.settings.javaScriptEnabled = true
message: >
Включение JavaScript в WebView без дополнительных ограничений
создаёт поверхность для XSS и потенциального JavascriptInterface-
злоупотребления. Если JavaScript необходим — добавьте:
webView.settings.allowFileAccess = false
webView.settings.allowContentAccess = false
И используйте addJavascriptInterface только с @JavascriptInterface.
languages: [kotlin]
severity: ERROR
metadata:
cwe: "CWE-749"
owasp: "M8:2023"
6.4 Правило: SQL-инъекция через rawQuery
yaml
rules:
- id: kotlin-sql-rawquery-injection
patterns:
- pattern: |
$DB.rawQuery($QUERY + $INPUT, ...)
- pattern: |
$DB.rawQuery("$TEMPLATE${$INPUT}$REST", ...)
- pattern: |
$DB.execSQL($QUERY + $INPUT)
message: >
Конкатенация строк в rawQuery создаёт уязвимость SQL-инъекции.
Используйте параметризованные запросы:
db.rawQuery("SELECT * FROM users WHERE id = ?", arrayOf(userId))
Или Room DAO с аннотациями @Query.
languages: [kotlin]
severity: ERROR
metadata:
cwe: "CWE-89"
6.5 Правило: логирование чувствительных данных
yaml
rules:
- id: kotlin-sensitive-data-logging
patterns:
- pattern-either:
- pattern: Log.d($TAG, "...$PASSWORD...")
- pattern: Log.i($TAG, "...$TOKEN...")
- pattern: Log.v($TAG, $MSG)
- pattern: println("password: $VALUE")
- pattern: System.out.println("token: $VALUE")
message: >
Логирование потенциально чувствительных данных. Логи Android
доступны другим приложениям с READ_LOGS-разрешением (до Android 4.1)
и могут попасть в отчёты об ошибках. Не логируйте пароли, токены,
PII. Для отладки используйте BuildConfig.DEBUG-условие.
languages: [kotlin]
severity: WARNING
metadata:
cwe: "CWE-532"
6.6 Правило: небезопасный implicit Intent
yaml
rules:
- id: kotlin-implicit-intent-sensitive
patterns:
- pattern: |
val $INTENT = Intent($ACTION)
...
startActivity($INTENT)
- pattern-not: |
val $INTENT = Intent($ACTION)
$INTENT.setPackage($PKG)
...
message: >
Неявный Intent без указания конкретного пакета может быть перехвачен
вредоносным приложением (Intent Hijacking). Для передачи
чувствительных данных используйте явные Intent с setPackage()
или setComponent(). Используйте PendingIntent с FLAG_IMMUTABLE.
languages: [kotlin]
severity: WARNING
metadata:
cwe: "CWE-927"
owasp: "M3:2023"
6.7 Запуск для Kotlin/Android-проекта
bash
<h2 id="pravila-reestra-dlya-kotlin">Правила реестра для Kotlin</h2>
semgrep --config "p/kotlin" .
<h2 id="pravila-bezopasnosti-android">Правила безопасности Android</h2>
semgrep --config "p/android" .
<h2 id="isklyuchit-sgenerirovannyy-kod-i-testy">Исключить сгенерированный код и тесты</h2>
semgrep --config "p/kotlin" \
--exclude "/build/" \
--exclude "/*Test.kt" \
--exclude "/*Spec.kt" \
.
<h2 id="skanirovanie-s-vyvodom-v-sarif-dlya-android-studio">Сканирование с выводом в SARIF для Android Studio</h2>
semgrep --config "p/kotlin" --sarif . > kotlin-sast.sarif
7. Написание кастомных правил: синтаксис, паттерны, метавариables
Сила Semgrep — в возможности написать правило под специфику конкретного проекта за 15 минут. Этот раздел — практическое руководство по синтаксису.
7.1 Метапеременные: типы и применение
yaml
<h2 id="x-lyuboe-odinochnoe-vyrazhenie-identifikator-ili-literal">$X — любое одиночное выражение, идентификатор или литерал</h2>
pattern: fmt.Sprintf($FORMAT, $X)
<h2 id="args-nol-ili-bolee-argumentov-variadic">$...ARGS — ноль или более аргументов (variadic)</h2>
pattern: fmt.Println($...ARGS)
<h2 id="type-var-peremennaya-s-lyubym-tipom">$TYPE $VAR — переменная с любым типом</h2>
pattern: var $TYPE $VAR = $VALUE
<h2 id="lyuboy-blok-koda-mnogostrochnyy-wildcard">"..." — любой блок кода (многострочный wildcard)</h2>
pattern: |
func $FUNC(...) {
...
dangerous_call(...)
...
}
7.2 Составные условия: operators
yaml
rules:
- id: compound-example
patterns:
# Оба условия должны выполняться (AND)
- pattern: $DB.Query($QUERY, ...)
- pattern-not: $DB.Query("SELECT ...", ...)
# pattern-either — хотя бы одно (OR)
pattern-either:
- pattern: db.Query(fmt.Sprintf(...))
- pattern: db.Exec(fmt.Sprintf(...))
# pattern-inside — паттерн внутри контекста
pattern-inside: |
func $HANDLER(w http.ResponseWriter, r *http.Request) {
...
}
7.3 Фокусировка на метапеременной
Когда правило срабатывает на большой конструкции, но важна конкретная часть — используйте `focus-metavariable`:
yaml
rules:
- id: go-focus-user-input
patterns:
- pattern: $DB.Query($QUERY + $USER_INPUT, ...)
- focus-metavariable: $USER_INPUT
message: Пользовательский ввод $USER_INPUT попадает в SQL-запрос
languages: [go]
severity: ERROR
7.4 Taint-правила: source → sink
Taint-анализ отслеживает данные от источника до точки использования через цепочки присваиваний и вызовов функций:
yaml
rules:
- id: go-taint-http-to-sql
mode: taint
pattern-sources:
- pattern: r.FormValue(...)
- pattern: r.URL.Query().Get(...)
- pattern: r.PostFormValue(...)
pattern-sinks:
- pattern: $DB.Query(...)
- pattern: $DB.Exec(...)
- pattern: $DB.QueryRow(...)
pattern-sanitizers:
- pattern: sqlEscape(...)
- pattern: $DB.Prepare(...)
message: >
Данные из HTTP-запроса достигают SQL-запроса без санитизации.
SQL-инъекция через пользовательский ввод.
languages: [go]
severity: ERROR
metadata:
cwe: "CWE-89"
mode: taint
7.5 Тестирование правил в Semgrep Playground
Перед использованием правила в CI тестируйте его в Playground: `semgrep.dev/playground`. Вставьте правило и тестовый код — Playground покажет, что обнаружено, а что пропущено.
Локальное тестирование правила:
bash
<h2 id="sozdat-testovyy-fayl-test-rule-go">Создать тестовый файл test_rule.go</h2>
<h2 id="zapustit-tolko-svoyo-pravilo-na-testovom-fayle">Запустить только своё правило на тестовом файле</h2>
semgrep --config rules/my_rule.yaml test_rule.go --verbose
<h2 id="zapustit-testy-pravil-po-soglasheniyu-ob-imenovanii">Запустить тесты правил по соглашению об именовании</h2>
<h2 id="fayly-test-lang-ryadom-s-pravilom">(файлы *_test.{lang} рядом с правилом)</h2>
semgrep --test rules/
7.6 Правила с Python-фильтрацией (pattern-where-python)
Для сложных условий, нереализуемых синтаксически:
yaml
rules:
- id: rust-hardcoded-secret-entropy
patterns:
- pattern: |
const $NAME: &str = "$VALUE";
- metavariable-regex:
metavariable: $NAME
regex: '.*(KEY|SECRET|TOKEN|PASSWORD|PASS|PWD|API).*'
- metavariable-regex:
metavariable: $VALUE
regex: '^[A-Za-z0-9+/]{20,}$'
message: >
Возможный хардкоженный секрет в константе $NAME.
Используйте переменные окружения или системы управления секретами
(HashiCorp Vault, AWS Secrets Manager).
languages: [rust]
severity: ERROR
metadata:
cwe: "CWE-798"
8. Продвинутые техники: taint-анализ, dataflow, межфункциональные уязвимости
8.1 Межфункциональный taint-анализ
Semgrep Pro поддерживает cross-function taint tracking — отслеживание данных через границы функций. Это позволяет находить уязвимости, где источник и точка использования находятся в разных файлах.
yaml
rules:
- id: kotlin-cross-function-sql-injection
mode: taint
options:
interfile: true # Межфайловый анализ (Pro)
pattern-sources:
- pattern: |
fun $HANDLER(request: HttpServletRequest): $RET {
...
val $PARAM = request.getParameter(...)
...
}
pattern-sinks:
- pattern: |
$DB.execute($QUERY)
- pattern: |
$STMT.executeQuery($QUERY)
message: Данные из HTTP-запроса достигают SQL-запроса (межфункционально)
languages: [kotlin]
severity: ERROR
8.2 Анализ зависимостей: Semgrep Supply Chain
Semgrep Supply Chain сканирует зависимости проекта на известные CVE:
bash
<h2 id="skanirovanie-zavisimostey-rust-cargo-lock">Сканирование зависимостей Rust (Cargo.lock)</h2>
semgrep --config "p/r2c-security-audit" --supply-chain .
<h2 id="skanirovanie-go-modules-go-sum">Сканирование Go modules (go.sum)</h2>
semgrep --config "p/supply-chain" .
<h2 id="vyvod-naydennyh-cve-v-zavisimostyah">Вывод найденных CVE в зависимостях</h2>
semgrep --supply-chain --json . | \
python3 -c "
import json, sys
d = json.load(sys.stdin)
for r in d.get('results', []):
if 'sca_info' in r.get('extra', {}):
print(r['extra']['sca_info'].get('cve', ''), r['path'])
"
8.3 Secrets Detection: обнаружение хардкоженных секретов
bash
<h2 id="zapusk-modulya-secrets-detection">Запуск модуля secrets detection</h2>
semgrep --config "p/secrets" .
<h2 id="poisk-tolko-vysokoentropiynyh-strok">Поиск только высокоэнтропийных строк</h2>
semgrep --config "p/gitleaks" .
<h2 id="primery-obnaruzhivaemyh-patternov">Примеры обнаруживаемых паттернов:</h2>
<h2 id="aws-access-key-akia-0-9a-z-16">AWS Access Key: AKIA[0-9A-Z]{16}</h2>
<h2 id="github-token-ghp-a-za-z0-9-36">GitHub Token: ghp_[A-Za-z0-9]{36}</h2>
<h2 id="private-key-begin-rsa-ec-openssh-private-key">Private Key: -----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----</h2>
<h2 id="jwt-eyj-a-za-z0-9-10-a-za-z0-9">JWT: eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]+</h2>8.4 Автоматическое исправление через autofix
Semgrep поддерживает автоматическое исправление кода через поле `fix` в правиле:
yaml
rules:
- id: go-fix-insecure-tls
pattern: |
&tls.Config{..., InsecureSkipVerify: true, ...}
fix: |
&tls.Config{}
message: Удалён InsecureSkipVerify: true
languages: [go]
severity: ERROR
bash
<h2 id="primenit-avtoispravleniya">Применить автоисправления</h2>
semgrep --config rules/go-fixes.yaml --autofix .
<h2 id="posmotret-diff-bez-primeneniya">Посмотреть diff без применения</h2>
semgrep --config rules/go-fixes.yaml --autofix --dryrun .
8.5 Семантический поиск через Semgrep AST grep
Для быстрого поиска без полного правила:
bash
<h2 id="nayti-vse-vyzovy-opasnyh-funktsiy-v-rust-faylah">Найти все вызовы опасных функций в Rust-файлах</h2>
semgrep -e 'std::mem::transmute(...)' --lang rust src/
<h2 id="nayti-vse-insert-zaprosy-s-formatirovaniem-v-go">Найти все INSERT-запросы с форматированием в Go</h2>
semgrep -e 'db.Exec(fmt.Sprintf($Q, ..., $X, ...))' --lang go .
<h2 id="nayti-vse-webview-loadurl-v-kotlin">Найти все WebView.loadUrl в Kotlin</h2>
semgrep -e '$WEBVIEW.loadUrl($URL)' --lang kotlin app/
9. Интеграция в CI/CD: GitHub Actions, GitLab CI, Jenkins
9.1 GitHub Actions
Официальное действие Semgrep для GitHub Actions:
yaml
<h2 id="github-workflows-semgrep-yml">.github/workflows/semgrep.yml</h2>
name: Semgrep SAST
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
# Полное сканирование каждую ночь
- cron: '0 2 * * *'
jobs:
semgrep:
name: Semgrep Scan
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Semgrep (OSS)
run: |
semgrep \
--config "p/rust" \
--config "p/golang" \
--config "p/kotlin" \
--config "p/secrets" \
--config "p/owasp-top-ten" \
--sarif \
--output semgrep-results.sarif \
--error \
.
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: semgrep-results.sarif
- name: Upload results as artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: semgrep-results
path: semgrep-results.sarif
Флаг `--error` завершает процесс с ненулевым кодом при обнаружении findings — это блокирует PR при наличии уязвимостей.
9.2 GitLab CI
yaml
<h2 id="gitlab-ci-yml">.gitlab-ci.yml</h2>
semgrep-sast:
image: semgrep/semgrep:latest
stage: test
script:
- semgrep
--config "p/rust"
--config "p/golang"
--config "p/kotlin"
--config "p/secrets"
--gitlab-sast
--output gl-sast-report.json
--error
.
artifacts:
reports:
sast: gl-sast-report.json
paths:
- gl-sast-report.json
when: always
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
allow_failure: false
9.3 Jenkins Pipeline
groovy
// Jenkinsfile
pipeline {
agent { docker { image 'semgrep/semgrep:latest' } }
stages {
stage('Semgrep SAST') {
steps {
script {
sh '''
semgrep \
--config "p/rust" \
--config "p/golang" \
--config "p/kotlin" \
--config "p/secrets" \
--json \
--output semgrep-results.json \
. || true
'''
// Подсчёт критических уязвимостей
def results = readJSON file: 'semgrep-results.json'
def errors = results.results.findAll {
it.extra.severity == 'ERROR'
}.size()
echo "Semgrep ERRORS: ${errors}"
if (errors > 0) {
error("Обнаружено ${errors} критических уязвимостей")
}
}
}
post {
always {
archiveArtifacts 'semgrep-results.json'
// Публикация в Jenkins Warnings NG Plugin
recordIssues(
tools: [sarif(pattern: 'semgrep-results.sarif')],
qualityGates: [[threshold: 1, type: 'TOTAL_ERROR', unstable: false]]
)
}
}
}
}
}
9.4 Pre-commit hook: сканирование до коммита
yaml
<h2 id="pre-commit-config-yaml">.pre-commit-config.yaml</h2>
repos:
- repo: https://github.com/semgrep/semgrep
rev: v1.x.x
hooks:
- id: semgrep
args:
- --config=p/secrets
- --config=p/owasp-top-ten
- --error
- --quiet
# Только изменённые файлы — быстро
pass_filenames: true
bash
<h2 id="ustanovka-i-aktivatsiya">Установка и активация</h2>
pip install pre-commit
pre-commit install
pre-commit run --all-files # первый запуск по всем файлам
10. Управление результатами: подавление ложных срабатываний, baseline
10.1 Подавление находок в коде (inline suppression)
Когда finding является ложным срабатыванием или известным принятым риском:
rust
// Rust — подавление конкретного правила
let result = unsafe { // nosemgrep: rust-unsafe-block-no-comment
dangerous_ffi_call()
};
// Подавление с обоснованием (рекомендуется)
// nosemgrep: rust-mem-transmute -- SAFETY: типы гарантированно совместимы по layout
let val: u64 = unsafe { std::mem::transmute(my_f64) };
go
// Go — подавление
config := &tls.Config{ // nosemgrep: go-tls-insecure-skip-verify
InsecureSkipVerify: os.Getenv("ENV") == "test",
}
kotlin
// Kotlin — подавление
webView.settings.javaScriptEnabled = true // nosemgrep: kotlin-webview-javascript-enabled
10.2 Файл .semgrepignore: исключение файлов и директорий
bash
<h2 id="semgrepignore-sintaksis-kak-u-gitignore">.semgrepignore — синтаксис как у .gitignore</h2>
vendor/
node_modules/
/*_test.go
/*_generated.go
/mock_*.kt
target/
build/
dist/
*.pb.go # Сгенерированный protobuf-код
/testdata/
10.3 Baseline: игнорировать уже существующие проблемы
При внедрении Semgrep в уже существующий проект с техническим долгом удобно сначала зафиксировать baseline и работать только с новыми находками:
bash
<h2 id="shag-1-sozdat-baseline-iz-tekuschego-sostoyaniya-main-vetki">Шаг 1: Создать baseline из текущего состояния main-ветки</h2>
git checkout main
semgrep --config "p/golang" --json . > baseline.json
<h2 id="shag-2-v-ci-sravnivat-tolko-s-novymi-nahodkami">Шаг 2: В CI сравнивать только с новыми находками</h2>
semgrep --config "p/golang" --json . > current.json
<h2 id="shag-3-nayti-tolko-novye-python-skript">Шаг 3: Найти только новые (python-скрипт)</h2>
python3 - <<'EOF'
import json
with open('baseline.json') as f:
baseline = {(r['check_id'], r['path'], r['start']['line'])
for r in json.load(f)['results']}
with open('current.json') as f:
current = json.load(f)['results']
new_findings = [r for r in current
if (r['check_id'], r['path'], r['start']['line'])
not in baseline]
print(f"Новые уязвимости: {len(new_findings)}")
for f in new_findings:
print(f" {f['check_id']} — {f['path']}:{f['start']['line']}")
EOF
10.4 Приоритизация по confidence и severity
Semgrep поддерживает фильтрацию по метаданным:
bash
<h2 id="tolko-vysokaya-uverennost-error">Только высокая уверенность + ERROR</h2>
semgrep --config "p/golang" \
--severity ERROR \
--json . | \
python3 -c "
import json, sys
d = json.load(sys.stdin)
high = [r for r in d['results']
if r.get('extra', {}).get('metadata', {}).get('confidence') == 'HIGH']
print(json.dumps({'results': high}, indent=2))
"
11. Semgrep vs конкуренты: сравнение с CodeQL, Snyk, SonarQube
11.1 Детальное сравнение инструментов
| Критерий | Semgrep OSS | CodeQL | Snyk Code | SonarQube CE |
|---|---|---|---|---|
| Лицензия | LGPL-2.1 | MIT (GitHub) | Проприетарная | LGPL |
| Поддержка Rust | ✅ Полная | ✅ Полная | ⚠️ Базовая | ⚠️ Базовая |
| Поддержка Go | ✅ Полная | ✅ Полная | ✅ Полная | ✅ Полная |
| Поддержка Kotlin | ✅ Полная | ✅ Полная | ✅ Полная | ✅ Полная |
| Написание правил | Простое (YAML+паттерн) | Сложное (QL) | Нет | Средне (XPath) |
| Скорость (100k строк) | < 1 мин | 5–20 мин | 2–5 мин | 5–15 мин |
| Taint-анализ | Pro/Ent | ✅ | ✅ | ✅ |
| Межфайловый анализ | Pro/Ent | ✅ | ✅ | ✅ |
| Самохостинг | ✅ | ✅ | ✅ | ✅ |
| CI/CD-интеграция | Нативная | GitHub-нативная | Нативная | Нативная |
11.2 Когда выбирать Semgrep
Semgrep OSS — оптимальный выбор когда:
- Нужна быстрая интеграция без длительной настройки
- Команда хочет писать кастомные правила под специфику проекта
- Критична скорость сканирования в CI (проверка каждого PR)
- Нужна поддержка нескольких языков в одном инструменте
- Бюджет ограничен (OSS-версия полностью бесплатна)
11.3 Когда выбирать CodeQL
CodeQL превосходит Semgrep в сценариях глубокого межпроцедурного анализа, когда уязвимость распределена по нескольким файлам и функциям. QL — полноценный язык запросов к AST, позволяющий выражать сложные условия. Недостаток — высокий порог входа и значительное время анализа.
11.4 Complementary strategy: Semgrep + CodeQL
Оптимальная стратегия для зрелых команд безопасности — не выбирать один инструмент, а использовать их в связке:
semgrep
(быстро, каждый PR):
→ Очевидные уязвимости, типичные паттерны, secrets
→ Кастомные правила под фреймворк проекта
→ Блокировка PR при нахождении ERROR
CodeQL (медленно, ночной скан):
→ Глубокий межпроцедурный taint-анализ
→ Сложные цепочки уязвимостей
→ Не блокирует PR, создаёт issues для team lead
12. Корпоративное использование: Semgrep AppSec Platform, политики, командная работа
12.1 Semgrep AppSec Platform vs OSS
Semgrep AppSec Platform (ранее Semgrep Cloud Platform) добавляет поверх OSS-движка:
- Централизованное управление правилами для всей организации
- Dashboard с трендами уязвимостей по командам и репозиториям
- Назначение findings разработчикам и отслеживание статуса
- Pro Engine с межфайловым taint-анализом
- Supply Chain scanning для зависимостей
- Notifications в Slack, Jira, PagerDuty
12.2 Организация правил в монорепозитории
Рекомендуемая структура для хранения правил:
text
.semgrep/
├── rules/
│ ├── rust/
│ │ ├── memory-safety.yaml
│ │ ├── crypto.yaml
│ │ └── ffi.yaml
│ ├── go/
│ │ ├── http-security.yaml
│ │ ├── sql-injection.yaml
│ │ └── goroutine-safety.yaml
│ ├── kotlin/
│ │ ├── android-security.yaml
│ │ └── serialization.yaml
│ └── common/
│ ├── secrets.yaml
│ └── hardcoded-values.yaml
├── .semgrepignore
└── semgrep.yml # Основной конфиг с наборами правил
yaml
<h2 id="semgrep-semgrep-yml">.semgrep/semgrep.yml</h2>
rules_dirs:
- .semgrep/rules/
configs:
- "p/secrets"
- "p/owasp-top-ten"
12.3 Политики на уровне репозитория
yaml
<h2 id="semgrep-policy-yaml-politika-dlya-komandy">semgrep-policy.yaml — политика для команды</h2>
policies:
block_on_error: true # Блокировать PR при ERROR
block_on_warning: false # Не блокировать при WARNING
notify_on: [ERROR, WARNING]
exceptions:
- rule_id: "rust-unsafe-block-no-comment"
repos: ["kernel-module"] # Исключение для конкретного репо
reason: "Reviewed by security team"
12.4 Метрики и отчётность
bash
<h2 id="svodnyy-otchyot-po-vsem-repozitoriyam-organizatsii">Сводный отчёт по всем репозиториям организации</h2>
semgrep --config "p/security-audit" \
--json \
--metrics=off \
. | python3 - <<'EOF'
import json, sys
from collections import Counter, defaultdict
data = json.load(sys.stdin)
by_rule = Counter(r['check_id'] for r in data['results'])
by_severity = Counter(
r['extra']['severity'] for r in data['results']
)
by_file = defaultdict(int)
for r in data['results']:
by_file[r['path']] += 1
print(f"\n=== SAST ОТЧЁТ ===")
print(f"Всего уязвимостей: {len(data['results'])}")
print(f"По severity: {dict(by_severity)}")
print(f"\nТоп-5 правил:")
for rule, count in by_rule.most_common(5):
print(f" {rule}: {count}")
print(f"\nТоп-5 файлов:")
for path, count in sorted(by_file.items(), key=lambda x: -x[1])[:5]:
print(f" {path}: {count}")
EOF
13. Практические кейсы: реальные уязвимости, найденные Semgrep
13.1 Кейс: обнаружение небезопасного использования unsafe в production Rust-коде
Ситуация: Кодовая база платёжного сервиса на Rust, 80 000 строк. Правило `rust-unsafe-block-no-comment` за 12 секунд обнаружило 7 непрокомментированных unsafe-блоков.
Результат анализа одного из блоков:
rust
// Обнаружено Semgrep: src/ffi/payment.rs:142
pub fn process_raw_buffer(ptr: *mut u8, len: usize) -> ProcessResult {
unsafe {
// Нет комментария SAFETY:
let slice = std::slice::from_raw_parts_mut(ptr, len);
// ...
}
}
Проблема: При `len` большем, чем реальный размер буфера — выход за границы памяти. Функция вызывалась из C-кода, где размер не проверялся.
Исправление:
rust
pub fn process_raw_buffer(ptr: *mut u8, len: usize) -> ProcessResult {
assert!(!ptr.is_null(), "ptr must not be null");
assert!(len <= MAX_BUFFER_SIZE, "len exceeds maximum");
// SAFETY: ptr ненулевой и len проверен выше. Вызывающая сторона
// гарантирует валидность памяти через контракт FFI.
unsafe {
let slice = std::slice::from_raw_parts_mut(ptr, len);
// ...
}
}
13.2 Кейс: SQL-инъекция в Go-микросервисе через taint-анализ
Ситуация: API-сервис на Go, получает параметры фильтрации из HTTP-запроса и передаёт в SQL. Классический паттерн `r.FormValue → fmt.Sprintf → db.Query`.
go
// Обнаружено правилом go-taint-http-to-sql: api/handlers/search.go:67
func SearchHandler(w http.ResponseWriter, r *http.Request) {
category := r.FormValue("category") // SOURCE
query := fmt.Sprintf(
"SELECT * FROM products WHERE category = '%s'", category)
rows, err := db.Query(query) // SINK
// ...
}
Исправление:
go
func SearchHandler(w http.ResponseWriter, r *http.Request) {
category := r.FormValue("category")
rows, err := db.Query(
"SELECT * FROM products WHERE category = ?", category)
// ...
}
13.3 Кейс: хардкоженный API-ключ в Kotlin Android-приложении
Ситуация: Мобильное приложение перед релизом в Google Play. Правило `kotlin-hardcoded-secret-entropy` обнаружило токен с высокой энтропией в константе.
kotlin
// Обнаружено: app/src/main/java/com/example/ApiClient.kt:12
companion object {
const val API_KEY = "sk-prod-a8f2e1b9c3d7e4f6a2b8c1d5e3f7a9b2c4d6e8f0" // ERROR
const val BASE_URL = "https://api.example.com"
}
Исправление: Ключ перенесён в `local.properties` (исключённый из git) и подгружается через `BuildConfig` во время сборки:
kotlin
// build.gradle.kts
android {
buildFeatures { buildConfig = true }
buildTypes.configureEach {
buildConfigField("String", "API_KEY",
"\"${properties["API_KEY"]}\"")
}
}
// Использование:
val apiKey = BuildConfig.API_KEY
13.4 Кейс: обнаружение утечки горутины в облачном сервисе Go
Semgrep обнаружил горутину, которая пишет в канал без контекста отмены. При высокой нагрузке количество «зависших» горутин росло до нескольких тысяч, постепенно съедая память.
go
// Обнаружено: worker/processor.go:89
func processItems(items []Item) {
results := make(chan Result)
for _, item := range items {
go func(i Item) { // Горутина без ctx
results <- process(i) // Если получатель закрыт — зависнет
}(item)
}
// ...
}
Исправление с использованием context:
go
func processItems(ctx context.Context, items []Item) {
results := make(chan Result, len(items))
for _, item := range items {
go func(ctx context.Context, i Item) {
select {
case results <- process(i):
case <-ctx.Done():
return
}
}(ctx, item)
}
}
14. FAQ: 12 горячих вопросов о Semgrep
Q 01 Semgrep OSS полностью бесплатен? Что есть только в Pro/Enterprise?
A OSS-версия бесплатна и содержит весь базовый функционал: pattern matching, secrets detection, интеграции с CI/CD, тысячи правил в реестре. В Pro/Enterprise добавлены: межфайловый taint-анализ, Supply Chain scanning, централизованный дашборд и назначение finding разработчикам. Для большинства команд OSS достаточно.
Q 02 Semgrep поддерживает анализ в реальном времени в IDE?
A Да. Официальные расширения для VS Code и IntelliJ IDEA запускают Semgrep в фоне при редактировании файла и показывают findings inline. Установка: расширение «Semgrep» в маркетплейсе VS Code или IntelliJ Plugin Marketplace.
Q 03 Как быстро работает Semgrep на больших кодовых базах?
A На типичном проекте в 100 000 строк кода Semgrep с набором `p/security-audit` работает 20–60 секунд. На 1 миллионе строк — 5–15 минут. Это значительно быстрее CodeQL (20–90 минут для того же объёма). Semgrep параллелизует анализ по ядрам процессора.
Q 04 Можно ли использовать Semgrep для анализа инфраструктурного кода (Terraform, Kubernetes)?
A Да. Semgrep поддерживает HCL (Terraform), YAML (Kubernetes манифесты), Dockerfile, JSON. В реестре есть готовые наборы: `p/terraform`, `p/kubernetes`, `p/docker`.
Q 05 Сколько ложных срабатываний даёт Semgrep по сравнению с другими инструментами?
A Паттерн-матчинг Semgrep даёт меньше ложных срабатываний, чем традиционные regex-инструменты, но больше, чем инструменты с глубоким dataflow-анализом (CodeQL, Coverity). Правила из официального реестра имеют поле `confidence: HIGH/MEDIUM/LOW` — фильтрация по `HIGH` резко снижает шум.
Q 06 Как организовать версионирование кастомных правил?
A Рекомендуется хранить правила в отдельном Git-репозитории (`semgrep-rules`) и ссылаться на конкретный тег или коммит в CI-конфиге. Изменения правил проходят через PR с обязательным тестированием на тестовых файлах через `semgrep --test`.
Q 07 Как Semgrep обрабатывает дженерики в Rust и Kotlin?
A Semgrep поддерживает дженерики через метапеременные типов: `$T`, `$TYPE`. Паттерн `Vec` матчит любой `Vec`, `HashMap` — любой `HashMap`. Для сложных констрейнтов можно использовать `metavariable-type`.
Q 08 Можно ли запустить Semgrep офлайн, без подключения к интернету?
A Да. Скачайте нужные конфиги заранее: `semgrep --config "p/rust" --dump-ast . > /dev/null` кэширует правила. Или используйте локальные YAML-файлы правил без обращения к реестру: `semgrep --config rules/ .`
Q 09 Как правильно интегрировать Semgrep с Jira для трекинга уязвимостей?
A В Semgrep AppSec Platform есть нативная Jira-интеграция. Для OSS-версии — парсинг JSON-вывода и создание issues через Jira REST API. Шаблон Python-скрипта: читать `results.json`, для каждого ERROR-уровня создавать Jira-задачу с полями `summary`, `description` (finding message + path + line), `labels: ["security", "semgrep"]`.
Q 10 Поддерживает ли Semgrep анализ изменённых файлов (diff-mode)?
A Да, через флаг `--diff-depth` или с помощью `git diff`. В GitHub Actions при PR-запуске Semgrep автоматически анализирует только изменённые файлы, что ускоряет проверку в 5–10 раз по сравнению с полным сканом.
Q 11 Как обрабатывать мультиязычные проекты (Rust backend + Kotlin mobile + Go microservices)?
A Semgrep автоматически определяет язык файла по расширению. Можно запустить один командой: `semgrep --config "p/rust" --config "p/golang" --config "p/kotlin" .` — каждое правило применяется только к файлам соответствующего языка.
Q 12 Есть ли смысл использовать Semgrep, если уже есть SonarQube?
A Да, они дополняют друг друга. SonarQube сильнее в качестве кода (code smells, дублирование, покрытие), Semgrep — в кастомных security-правилах и скорости. Типичная связка: Semgrep в pre-commit и PR-проверках (быстро, блокирует), SonarQube в ночных сканах (глубоко, не блокирует).
15. Чек-лист: внедрение Semgrep в проект за один день
Утро: Установка и разведка (2 часа)
- ☐ Установить Semgrep: `pip install semgrep && semgrep --version`
- ☐ Запустить первое сканирование: `semgrep --config auto . --json > first-scan.json`
- ☐ Оценить масштаб: подсчитать количество ERROR/WARNING/INFO
- ☐ Проверить, нет ли критических находок (secrets, SQL-инъекции)
День: Настройка и конфигурация (4 часа)
- ☐ Создать `.semgrepignore` — исключить vendor/, build/, сгенерированный код
- ☐ Подобрать наборы правил из реестра под языки проекта
- ☐ Создать baseline: `semgrep --config auto --json . > baseline.json`
- ☐ Написать 2–3 кастомных правила под специфику проекта (фреймворки, внутренние функции)
- ☐ Протестировать правила: `semgrep --test rules/`
- ☐ Настроить `.semgrep/semgrep.yml` с окончательным набором конфигов
Вечер: CI/CD-интеграция (2 часа)
- ☐ Добавить workflow для GitHub Actions / GitLab CI / Jenkins
- ☐ Настроить SARIF-вывод и загрузку в Code Scanning (для GitHub)
- ☐ Установить порог блокировки: `--severity ERROR --error`
- ☐ Добавить pre-commit hook для локальной проверки перед коммитом
- ☐ Убедиться, что CI завершается с ошибкой при наличии ERRORs
После внедрения: регулярные задачи
- ☐ Раз в квартал обновлять наборы правил из реестра
- ☐ Добавлять кастомные правила при появлении новых паттернов угроз
- ☐ Ревьюить `nosemgrep`-исключения — не накапливать «технический долг» по безопасности
- ☐ Проводить ретроспективу: какие уязвимости Semgrep мог бы поймать раньше
16. Заключение и теги
Semgrep в 2026 году — это не просто ещё один SAST-инструмент. Это инфраструктура для масштабируемой автоматизации безопасности кода, которая работает одинаково хорошо на ноутбуке разработчика и в CI крупной организации.
Три разобранных языка — Rust, Go, Kotlin — покрывают значительную часть современного стека системного и облачного программирования. Каждый из них имеет специфические угрозы: unsafe-блоки и transmute в Rust, конкурентность и TLS-конфигурация в Go, хранение секретов и WebView в Kotlin. Semgrep обнаруживает все эти паттерны — быстро, без ложных ожиданий и с возможностью расширения под конкретный проект.
1. Начните с `semgrep --config auto .` прямо сейчас — первые находки появятся через минуту
2. Настройте `.semgrepignore` и baseline — избавьтесь от шума перед интеграцией в CI
3. Добавьте в CI с флагом `--error` — Semgrep должен блокировать опасные изменения
4. Инвестируйте в кастомные правила — 3–5 правил под специфику проекта дают больше ценности, чем 500 общих
5. Используйте taint-правила для нахождения межфункциональных уязвимостей — это то, чего не видит простой grep
6. Комбинируйте с CodeQL для глубокого анализа и с SonarQube для качества кода — инструменты дополняют друг друга
> 🔒 SAST — не замена ревью кода и пентестам. Это первый и самый дешёвый слой защиты, который работает автоматически 24/7 и не устаёт.