Опирается на правила: R-SEC-SAST-1R-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
}

Что ловит:

  • EqualsHashCodeequals() без hashCode() (и наоборот).
  • MissingOverride@Override забыт.
  • MutableConstantFieldpublic static final с mutable type.
  • FutureReturnValueIgnoredCompletableFuture.runAsync(...) без .get() или .join().
  • MustBeClosedCheckerStream.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_INFile.open(userInput) без sanitization.
  • WEAK_MESSAGE_DIGEST_* — MD5/SHA1 для security.
  • XXE_* — XML External Entity injection.
  • HARD_CODE_PASSWORDString password = "secret123" в коде.

effort = 'max' — самый глубокий анализ. reportLevel = 'low' — показывать все findings, фильтруем через severity.

Output в SARIF подключается к GitHub Code Scanning, findings видны в GitHub Security tab.

Severity → действие

R-SEC-SAST-3: разная реакция per-severity.

SeveritySpotBugs/FindSecBugsError ProneДействие
CRITICALhigh-rank securityerrorСборка падает
HIGHrank 1-9 securityerrorСборка падает
MEDIUMrank 10-14warnОбязательный комментарий ревьюера
LOWrank 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: trueR-SEC-SAST-3сборка падает на HIGH+
MEDIUM игнорируются без комментария ревьюераR-SEC-SAST-3обязательный комментарий в PR
Error Prone без NullAwayR-SEC-SAST-1оба плагина
SpotBugs effort: defaultR-SEC-SAST-2effort: max
Без SARIF outputR-SEC-SAST-2sarif.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 — oauth2ResourceServer enforce-ит правильное использование.