Меня часто спрашивают: "Алексей, у нас же есть code review, зачем нам ещё SAST?" И я всегда отвечаю одно — потому что человек пропустит. Гарантированно. На одном проекте в Яндексе мы нашли SQL-injection в коде, который прошёл через трёх ревьюеров. Три человека. Пропустили.
Вот именно для этого нужны SAST и SCA. Но сначала разберёмся, что это вообще такое и почему они работают по-разному.
SAST vs SCA: в чём разница
SAST (Static Application Security Testing) анализирует исходный код прямо в момент разработки. Смотрит на то, как ты пишешь, что ты используешь, куда передаёшь данные. Это как очень внимательный статический анализ.
SCA (Software Composition Analysis) смотрит на зависимости — все эти npm-пакеты, pip-библиотеки, maven-артефакты. Проверяет, нет ли в них известных уязвимостей.
На практике это работает так: SAST ловит баги в твоём коде, SCA ловит баги в чужом коде, который ты подтянул как зависимость. Оба нужны. Вместе они покрывают примерно 80% типичных проблем.
Давайте по очереди.
Как работает SAST: ловим уязвимости в своём коде
SAST анализирует абстрактное синтаксическое дерево (AST) кода и ищет опасные паттерны. Не запускает код, просто смотрит — "ну вот смотрите, здесь пользовательский ввод идёт прямо в SQL-запрос, это плохо".
Типичные уязвимости, которые ловит SAST:
SQL-injection. Самая частая. Выглядит так:
username = request.args.get('username')
query = f"SELECT * FROM users WHERE name = '{username}'"
db.execute(query)
Пользователь передаст ' OR '1'='1 и вуаля — видит всех пользователей. SAST инструмент сразу скажет: "Стой, параметр из request идёт в SQL без санитизации."
XSS (Cross-Site Scripting). Пользователь передаёт JavaScript, ты это выводишь на страницу без экранирования:
comment = request.args.get('comment')
html = f"<div>{comment}</div>"
return render_template('page.html', content=html)
Пользователь передаст <script>alert('hacked')</script> — и скрипт выполнится в браузере каждого, кто откроет комментарий.
Hardcoded credentials. Люди пишут пароли прямо в коде:
API_KEY = "sk_live_51234567890abcdefghijklmnop"
DATABASE_PASSWORD = "SuperSecret123"
SAST найдёт это за секунду.
Unsafe deserialization. В Python это особенно опасно:
import pickle
data = request.data
user = pickle.loads(data) # ОЧЕНЬ плохо
Если пользователь передаст специально сконструированный пикл, он может выполнить произвольный код на сервере.
Path traversal. Когда пользователь может обратиться к файлам вне нужной директории:
filename = request.args.get('file')
with open(f'/uploads/{filename}', 'r') as f:
return f.read()
Пользователь передаст ../../etc/passwd и прочитает системные файлы.
SAST инструменты работают по правилам. Каждому известному типу уязвимости соответствует набор сигнатур. Инструмент проходит по коду и ищет совпадения.
Самые популярные SAST для разработчиков:
Semgrep — открытый, быстрый, с огромной базой правил. Работает локально. Пишешь код, запускаешь semgrep --config=p/security-audit и видишь проблемы в секундах.
SonarQube — корпоративный стандарт. Есть бесплатная версия, но full-featured только в платной. Интегрируется с CI/CD, строит графики, отслеживает тренды.
Checkmarx — дорогой, но очень полный. Используют в крупных корпорациях.
Bandit (для Python) и ESLint с security-плагинами (для JS) — специализированные, лёгкие, хорошо встраиваются в локальные workflows.
По моему опыту, для стартапа хватает Semgrep или Bandit. Для корпорации — SonarQube.
SCA: ловим уязвимости в зависимостях
Тут всё проще с точки зрения технологии, но сложнее с точки зрения управления.
SCA берёт твой package.json, requirements.txt, go.mod или pom.xml и проверяет каждую зависимость против известных уязвимостей. Есть база типа CVE (Common Vulnerabilities and Exposures) — там собраны все найденные баги в открытых библиотеках.
Реальный пример: в 2021 году нашли уязвимость в log4j (Java-библиотека логирования). CVE-2021-44228. Это была критическая уязвимость — любой может выполнить код на сервере, просто отправив специальную строку в логи.
За пару часов миллионы приложений были скомпрометированы. Те, у кого работал SCA, узнали об этом мгновенно и обновили зависимость.
Типичные сценарии:
Устаревшая версия с известной уязвимостью. Ты ставишь django==2.0 в 2024 году — SAST знает, что там десятки найденных багов.
Транзитивные зависимости. Ты используешь пакет A, он зависит от пакета B, B зависит от C. Если в C найдена уязвимость, ты об этом можешь не знать, но SCA покажет.
Лицензионные риски. SCA также проверяет лицензии. Если ты используешь GPL-библиотеку в коммерческом проекте, это проблема.
SCA инструменты:
Snyk — очень популярный, хорошо интегрируется с GitHub и GitLab. Показывает не только уязвимость, но и рекомендует обновление или workaround.
Dependabot (встроен в GitHub) — бесплатный, простой, создаёт PR с обновлениями зависимостей.
OWASP Dependency-Check — открытый, работает локально, можно встроить в pipeline.
Safety (для Python) — простой, быстрый. Просто запустил — и видишь проблемы.
Я обычно рекомендую: для GitHub используй Dependabot (уже встроен), для приватных проектов или если нужна глубокая аналитика — Snyk, для Python — Safety в CI/CD плюс локально перед коммитом.
Интеграция в pipeline: как это работает на практике
Хорошо. Ты знаешь про SAST и SCA. Но как это всё включить в свой workflow, чтобы не замедлить разработку и не создать шум?
Вот типичный pipeline:
stages:
- build
- test
- security
- deploy
security_sast:
stage: security
image: returntocorp/semgrep
script:
- semgrep --config=p/security-audit --json > sast-report.json
artifacts:
reports:
sast: sast-report.json
security_sca:
stage: security
image: python:3.11
script:
- pip install safety
- safety check --json > sca-report.json
artifacts:
reports:
dependency_scanning: sca-report.json
security_bandit:
stage: security
image: python:3.11
script:
- pip install bandit
- bandit -r src/ -f json > bandit-report.json
artifacts:
reports:
sast: bandit-report.json
Это GitLab CI. Каждый раз, когда ты пушишь код, проходит проверка. Если SAST найдёт критическую уязвимость, пайплайн падает и MR не мержится.
На GitHub выглядит похоже, но через Actions:
name: Security Checks
on: [push, pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
Важный момент: не все уязвимости одинаковые. SAST может выдать false positives. Например, считает, что eval() всегда опасен, но иногда это не так. Нужно настраивать правила под свой проект.
На одном проекте мы сначала включили SAST слишком строго и получили 200+ алертов за раз. Половина были false positives. Команда расстроилась, отключила всё. Потом мы переконфигурировали — оставили только critical и high severity — и всё встало на место.
Практический пример: поймали уязвимость
Давайте на реальном примере. Вот код, который пишет младший разработчик:
from flask import Flask, request
import sqlite3
app = Flask(__name__)
@app.route('/user/<user_id>')
def get_user(user_id):
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
user = cursor.fetchone()
return user
@app.route('/search')
def search():
term = request.args.get('q')
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE name LIKE '%{term}%'"
cursor.execute(query)
results = cursor.fetchall()
return results
Запускаешь Semgrep:
semgrep --config=p/security-audit /app/code.py
Вывод:
rules/python.lang.security.injection.sql.tainted-sql-string.yaml
SQL injection (CWE-89)
/app/code.py:10:18-10:32
10 | query = f"SELECT * FROM users WHERE id = {user_id}"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Severity: HIGH
rules/python.lang.security.injection.sql.tainted-sql-string.yaml
SQL injection (CWE-89)
/app/code.py:18:18-18:50
18 | query = f"SELECT * FROM users WHERE name LIKE '%{term}%'"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Severity: HIGH
Инструмент сразу указал на проблему. Как исправить:
@app.route('/user/<user_id>')
def get_user(user_id):
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
query = "SELECT * FROM users WHERE id = ?"
cursor.execute(query, (user_id,))
user = cursor.fetchone()
return user
@app.route('/search')
def search():
term = request.args.get('q')
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
query = "SELECT * FROM users WHERE name LIKE ?"
cursor.execute(query, (f'%{term}%',))
results = cursor.fetchall()
return results
Теперь параметры передаются отдельно, и SAST не ругается.
Для SCA пример проще. Ты ставишь старую версию:
requests==2.20.0
Запускаешь Safety:
safety check
Вывод:
requests 2.20.0 in /env/lib/python3.8/site-packages
Known security vulnerability in requests found in 2.20.0
CVE-2018-18074: CRITICAL
Обновляешь requirements.txt:
requests>=2.31.0
И готово.
Как выбрать инструменты для своей команды
Если у тебя стартап, маленькая команда — начни с простого. Для Python это Bandit + Safety, для JavaScript это ESLint с плагинами + Dependabot (в GitHub). Всё бесплатно, всё работает локально и в CI/CD.
Если команда больше 10 человек и проект critical — переходи на Semgrep + Snyk. Semgrep дешевле, Snyk красивше и умнее.
Если это enterprise — SonarQube + Checkmarx, но это дорого (десятки тысяч в год).
Главное правило: лучше работающее простое решение, чем идеальное, которое никто не включит.
По моему опыту, больше половины команд включают SAST/SCA, но половина из них потом выключает, потому что "слишком много алертов". Поэтому настройка и калибровка — это 70% успеха. Начни с high severity, потом добавляй medium. Настрой false positives. Дай команде неделю привыкнуть.
Distiq как дополнение к SAST/SCA
Если честно, SAST и SCA — это foundation. Но они ловят только то, что написано в правилах. А что если разработчик использует опасный паттерн, который не в сигнатурах?
Вот тут помогает AI code review. Distiq анализирует каждый MR/PR и ловит не только уязвимости безопасности, но и логические ошибки, проблемы с производительностью, нарушения архитектуры. Работает как дополнение к SAST — находит то, что автоматические правила
