Опирается на правила:
R-SEC-SAST-1…R-SEC-SAST-4иR-SEC-SAST-X1из Security Style Guide → раздел 1. SAST по коду.
Важно знать
- Error Prone обязателен — ловит баги на этапе компиляции, часть javac-passes (zero overhead).
- SpotBugs + FindSecBugs обязательны для production-кода — общие баги + security-specific (SQLi, XSS, weak crypto).
- HIGH/CRITICAL ломает сборку (
failOnError: true). MEDIUM — обязательный комментарий ревьюера. LOW — игнорим.- Suppressions в
config/spotbugs-exclude.xmlс обязательнымjustifyи сроком.@SuppressFBWarningsбез justification ≥ 30 символов — запрещён.- SAST — единственная защита от классов багов, которые review глазами пропускает.
SAST (Static Application Security Testing) — анализ исходного кода без выполнения. UCP формулирует минимальный mandatory-набор: один компилятор-плагин (Error Prone) + один анализатор (SpotBugs+FindSecBugs). Без них SQL-инъекция или утечка пароля проходит ревью «глазами» — security audit находит через год.
Error Prone
R-SEC-SAST-1: подключение через gradle plugin.
plugins {
id 'net.ltgt.errorprone' version '4.1.0'
}
dependencies {
errorprone 'com.google.errorprone:error_prone_core:2.36.0'
errorprone 'com.uber.nullaway:nullaway:0.12.1'
}
tasks.withType(JavaCompile).configureEach {
options.errorprone.error('NullAway')
options.errorprone.option('NullAway:AnnotatedPackages', 'ru.vikulinva')
options.errorprone.disableWarningsInGeneratedCode = true
}
Что ловит:
EqualsHashCode—equals()безhashCode()(и наоборот).MissingOverride—@Overrideзабыт.MutableConstantField—public static finalс mutable type.FutureReturnValueIgnored—CompletableFuture.runAsync(...)без.get()или.join().MustBeClosedChecker—Stream.of(...)без try-with-resources.NullAway— потенциальный NPE в коде без@Nullableаннотаций.
Преимущество: zero overhead — Error Prone живёт внутри javac, та же compilation phase. CI не теряет времени.
При finding — compilation error, не warning. Это force-feedback: код не компилируется → не commit-ится.
SpotBugs + FindSecBugs
R-SEC-SAST-2: один gradle plugin покрывает оба.
plugins {
id 'com.github.spotbugs' version '6.0.27'
}
dependencies {
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.13.0'
spotbugsPlugins 'com.mebigfatguy.sb-contrib:sb-contrib:7.6.4'
}
spotbugs {
effort = 'max'
reportLevel = 'low'
excludeFilter = file('config/spotbugs-exclude.xml')
}
tasks.named('spotbugsMain') {
reports {
xml.required = true
sarif.required = true
html.enabled = false
}
}
SpotBugs ловит общие баги Java:
NP_NULL_*— null pointer на null reference.RC_REF_COMPARISON—==для objects вместо.equals().EI_EXPOSE_REP— getter возвращает mutable internal (нарушение encapsulation).SE_*— Serializable issues.DM_*— Deprecated API usage.
FindSecBugs (плагин) — security-specific:
SQL_INJECTION_JDBC/SQL_INJECTION_JPA— SQL injection через конкатенацию строк.XSS_REQUEST_PARAMETER_TO_*— XSS в HTTP responses.PATH_TRAVERSAL_IN—File.open(userInput)без sanitization.WEAK_MESSAGE_DIGEST_*— MD5/SHA1 для security.XXE_*— XML External Entity injection.HARD_CODE_PASSWORD—String password = "secret123"в коде.
effort = 'max' — самый глубокий анализ. reportLevel = 'low' — показывать все findings, фильтруем через severity.
Output в SARIF подключается к GitHub Code Scanning, findings видны в GitHub Security tab.
Severity → действие
R-SEC-SAST-3: разная реакция per-severity.
| Severity | SpotBugs/FindSecBugs | Error Prone | Действие |
|---|---|---|---|
| CRITICAL | high-rank security | error | Сборка падает |
| HIGH | rank 1-9 security | error | Сборка падает |
| MEDIUM | rank 10-14 | warn | Обязательный комментарий ревьюера |
| LOW | rank 15-20 | — | Игнорим |
CI-команда:
./gradlew spotbugsMain # exit 1 если HIGH+ finding
Без failOnError: true finding появляется в отчёте, но сборка зелёная. Это превращает SAST в дашборд, который никто не смотрит. Главное правило R-SEC-1: сборка падает на security-finding.
Suppressions со сроком
R-SEC-SAST-4: config/spotbugs-exclude.xml.
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<!-- justify: legacy ConcurrentHashMap value mutation, refactor запланирован
до: 2026-08-31 -->
<Match>
<Class name="ru.vikulinva.legacy.OldCache"/>
<Bug pattern="MS_MUTABLE_COLLECTION"/>
</Match>
<!-- justify: generated jOOQ Records — false positive
до: never (generated code) -->
<Match>
<Class name="~ru\.vikulinva\.generated\..*"/>
</Match>
</FindBugsFilter>
Правила:
justify:— причина исключения (не «забыли», а «почему это false-positive или acceptable»).до: YYYY-MM-DD— срок пересмотра. Без даты не принимается.- Generated code — единственный случай
never(jOOQ, openapi-generator, mapstruct output).
Раз в квартал — автоматический скрипт ищет просроченные suppressions, постит ticket. Без этого через год набор исключений превращается в «когда-то отключили, не помню почему» свалку.
@SuppressFBWarnings в коде
R-SEC-SAST-X1: только с явным justification.
// ОК — явный justification
@SuppressFBWarnings(
value = "EI_EXPOSE_REP",
justification = "Immutable Order aggregate exposes its items list to allow lazy iteration without copy"
)
public List<OrderItem> getItems() {
return items;
}
// КАТАСТРОФА — нет justification
@SuppressFBWarnings("EI_EXPOSE_REP")
public List<OrderItem> getItems() {
return items;
}
Review-скилл проверяет наличие justification >= 30 символов. Без — критическое нарушение.
Где SAST не помогает
SAST имеет fundamental ограничения:
- Не понимает runtime-значения (
if (userInput.equals("admin"))— теоретически injection, но statically anyone may pass). - False positives на framework-specific patterns (Spring Security, jOOQ generated).
- Не покрывает бизнес-логику (race conditions, ABAC bypass).
SAST — первый слой, не единственный. Дополняется:
- CVE scanning (R-SEC-DEP-*).
- Secrets scanning (R-SEC-SECRET-*).
- Container scanning (R-SEC-IMG-*).
- Manual code review — для business logic.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
@SuppressFBWarnings без justification ≥ 30 символов | R-SEC-SAST-X1 | явный комментарий |
Suppression без срока до: | R-SEC-SAST-4 | дата пересмотра обязательна |
SAST без failOnError: true | R-SEC-SAST-3 | сборка падает на HIGH+ |
| MEDIUM игнорируются без комментария ревьюера | R-SEC-SAST-3 | обязательный комментарий в PR |
Error Prone без NullAway | R-SEC-SAST-1 | оба плагина |
SpotBugs effort: default | R-SEC-SAST-2 | effort: max |
| Без SARIF output | R-SEC-SAST-2 | sarif.required = true для GitHub Code Scanning |
| Suppression на весь класс без обоснования | R-SEC-SAST-4 | точечная suppression с justification |
Куда дальше
- Security → раздел 1. SAST по коду — нормативные формулировки.
- CVE в зависимостях — следующий слой.
- Секреты в коде и истории — Gitleaks.
- Container/image-уязвимости — Trivy.
- Реакция на findings — Severity → SLA.
- Auth → JWT validation —
oauth2ResourceServerenforce-ит правильное использование.