Опирается на правила: R-SEC-FIND-1, R-SEC-FIND-2, R-SEC-FIND-3, R-SEC-FIND-X1, R-SEC-1, R-SEC-4 из Security Style Guide → раздел 6. Реакция на findings.

Важно знать

  • CRITICAL — сборка падает, hotfix ≤ 24 часа; источник: любой инструмент.
  • HIGH — сборка падает, патч ≤ 2 недели; gosec -severity HIGH и govulncheck возвращают exit code 1 автоматически.
  • MEDIUM — отчёт, патч ≤ 30 дней; не блокирует merge.
  • LOW — игнор, не отображается в dashboard.
  • //nolint:gosec без кода нарушения и обоснования ≥ 30 символов — не принимается (R-SEC-SAST-X1).
  • govuln-suppressions.json без "until" — suppression не засчитывается, считается долгом (R-SEC-FIND-2).
  • Молчание = долг: finding без suppression накапливается, повторно анализируется на каждом review.
  • SARIF из gosec, semgrep, trivy image загружается в GitHub Security tab — единый дашборд без отдельной инфры (R-SEC-FIND-3).

Findings — это входящий поток работы для security backlog. Контракт R-SEC-FIND-* задаёт SLA per severity: команда не тонет в noise (LOW) и реагирует на критичное быстро. Без явного SLA findings накапливаются, дашборд превращается в «море красного», команда перестаёт обращать внимание.

Severity → SLA

R-SEC-FIND-1: четыре уровня, четыре разных действия.

SeverityИнструментыSLAБлокирует сборку
CRITICALgosec, govulncheck, Trivy, semgrep≤ 24 часа, hotfixда
HIGHgosec, govulncheck, Trivy≤ 2 недели (текущий спринт)да
MEDIUMgosec, Trivy≤ 30 днейнет
LOWлюбойнет, игнор

В CI это достигается без дополнительного кода — инструменты сами возвращают ненулевой exit code:

# PR gate — SAST
gosec -fmt sarif -out gosec.sarif -severity HIGH ./...

# main/nightly/release gate — CVE
govulncheck ./...

# после docker build — образ
trivy image \
  --exit-code 1 \
  --severity HIGH,CRITICAL \
  --format sarif \
  --output trivy.sarif \
  orders-service:${{ github.sha }}

CRITICAL — hotfix ≤ 24 часа

Примеры для сервиса orders:

  • govulncheck находит активно эксплуатируемый CVE в github.com/go-chi/chi в runtime.
  • Gitleaks фиксирует SBER_API_KEY=prod-... в коммите.
  • gosec G401 на crypto/md5 в пути хеширования пароля Customer.

Алгоритм:

  1. Уведомить команду немедленно.
  2. Создать hotfix branch от main.
  3. Применить patch или временный workaround.
  4. Задеплоить в прод в течение 24 часов.
  5. Провести post-mortem.

HIGH — текущий спринт

Примеры:

  • CVSS 7.5 в pgx/v5 транзитивной зависимости (SQL-инъекция).
  • gosec G304 на os.Open(r.URL.Query().Get("path")) в обработчике Product.
  • Trivy HIGH в base image golang:1.23-alpine.
gh issue create \
  --title "HIGH: gosec G304 в ProductHandler.ServeHTTP" \
  --label "security/high" \
  --body "gosec G304: path построен из query param без валидации. Заменить на allowlist."

MEDIUM — 30 дней

Примеры:

  • CVSS 5.3 в encoding/json без active exploit.
  • Trivy MEDIUM в OS-пакете alpine без known PoC.

Ticket создаётся, включается в ближайшие спринты. Если через 30 дней не закрыт — автоматически поднимается до HIGH (CI-скрипт или GitHub Action по метке + дате).

LOW — игнор

Примеры:

  • gosec G304 с confidence LOW на внутреннем инструментальном пути.
  • Trivy LOW в dev-пакете alpine без производственного влияния.

Не создаём ticket, не показываем в дашборде. Конфиг .golangci.yml фильтрует на уровне инструмента:

linters-settings:
  gosec:
    severity: high
    confidence: high
issues:
  max-issues-per-linter: 0
  max-same-issues: 0

Suppressions имеют срок

R-SEC-FIND-2: два механизма — //nolint: для SAST, govuln-suppressions.json для CVE.

//nolint для gosec

// adapters/in/http/product_handler.go

//nolint:gosec // G304: путь берётся из cfg.TemplatePath (trusted config, не из запроса);
// заменить на embed.FS до: 2026-12-01
f, err := os.Open(cfg.TemplatePath)
if err != nil {
    return fmt.Errorf("open template: %w", err)
}
defer f.Close()

Шаблон: //nolint:gosec // <код>: <обоснование ≥ 30 символов>; <план>; до: <YYYY-MM-DD>.

Blanket //nolint без кода нарушения и без даты — не принимается на review.

govuln-suppressions.json для CVE

{
  "suppressions": [
    {
      "vuln": "GO-2024-3321",
      "reason": "уязвимая функция parseOldFormat не вызывается в нашем коде-пути; используем только NewFormat",
      "until": "2026-09-01"
    }
  ]
}

Без поля "until" — suppression не засчитывается, CI считает finding открытым.

Квартальный аудит просроченных suppressions

#!/bin/bash
# tools/check-expired-suppressions.sh
NOW=$(date +%Y-%m-%d)

echo "=== просроченные //nolint ==="
grep -rn 'до:' . --include="*.go" | while IFS= read -r line; do
    until=$(echo "$line" | grep -oP 'до: \K[\d-]+')
    if [[ -n "$until" && "$until" < "$NOW" ]]; then
        echo "EXPIRED: $line"
    fi
done

echo "=== просроченные govuln-suppressions ==="
if [[ -f govuln-suppressions.json ]]; then
    jq -r '.suppressions[] | select(.until < "'"$NOW"'") | "EXPIRED: \(.vuln) (\(.until))"' \
        govuln-suppressions.json
fi

Запускать в CI weekly (schedule: cron: '0 9 * * 1'), постить issue в репо при наличии просроченных.

GitHub Code Scanning — SARIF

R-SEC-FIND-3: все инструменты экспортируют SARIF, загружают в GitHub Security tab.

# .github/workflows/pr.yml (фрагмент)
- name: Run gosec
  run: gosec -fmt sarif -out gosec.sarif -severity HIGH ./...

- name: Upload gosec SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: gosec.sarif

# .github/workflows/main.yml (фрагмент)
- name: Scan image
  run: |
    trivy image \
      --exit-code 1 \
      --severity HIGH,CRITICAL \
      --format sarif \
      --output trivy.sarif \
      orders-service:${{ github.sha }}

- name: Upload Trivy SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: trivy.sarif

В GitHub Security tab — фильтрация по severity, инструменту, файлу; tracking статуса (open / dismissed / fixed); уведомления. Без отдельной инфры типа SonarQube.

Baseline-механика на release

R-SEC-4: релиз блокируется только на новые findings.

{
  "govulncheck": {
    "knownVulns": [
      {
        "id": "GO-2024-3100",
        "pkg": "golang.org/x/net",
        "firstSeen": "2026-04-10"
      }
    ]
  }
}

При release tag CI сравнивает govulncheck -json ./... с baseline:

  • Новый vuln ID — блокируем release, создаём ticket.
  • Vuln уже в baseline — не блокируем, это управляемый долг.

Без baseline первый CVE в транзитивной зависимости останавливает pipeline на неделю — команда начинает игнорировать security в целом.

«Не уверен, эксплуатируется ли»

R-SEC-FIND-X1: главный антипаттерн.

Плохо — finding остаётся открытым, security team анализирует повторно на каждом PR:

// gosec G304, developer думает: "sqlc генерирует запросы, тут нет инъекции"
// ничего не делает
rows, err := db.QueryContext(ctx, fmt.Sprintf("SELECT * FROM orders WHERE id = %s", id))

Правильно — фикс (предпочтительно для sqlc/pgx):

// core/order/store.go — sqlc генерирует типизированные запросы, G304 не применим
order, err := q.GetOrder(ctx, orderID)
if err != nil {
    return nil, fmt.Errorf("get order %d: %w", orderID, err)
}

Правильно — suppression, если фикс невозможен прямо сейчас:

// adapters/in/http/report_handler.go

//nolint:gosec // G304: путь из cfg.ReportDir (trusted config, не из request);
// refactor на embed.FS запланирован в Q4 2026; до: 2026-12-31
f, err := os.Open(filepath.Join(cfg.ReportDir, reportID+".pdf"))
if err != nil {
    return fmt.Errorf("open report: %w", err)
}
defer f.Close()

Suppression — не плохо. Плохо — молчание: finding без разрешения накапливает долг без SLA.

Что запрещено

АнтипаттернПравилоЧто взамен
//nolint:gosec без кода нарушенияR-SEC-SAST-X1//nolint:gosec // G304: ... с обоснованием
//nolint:gosec без даты до:R-SEC-FIND-2добавить ; до: YYYY-MM-DD
govuln-suppressions.json без "until"R-SEC-FIND-2"until": "YYYY-MM-DD" обязательно
Игнорирование finding без suppressionR-SEC-FIND-X1suppression с обоснованием или фикс
CRITICAL не hotfix в 24 часаR-SEC-FIND-1немедленный hotfix
HIGH в backlog «когда-нибудь»R-SEC-FIND-1включить в текущий спринт (≤ 2 нед)
gosec/Trivy без SARIF-выгрузкиR-SEC-FIND-3upload-sarif в каждом CI-шаге
Baseline не обновляется на merge в mainR-SEC-4auto-update через CI

Куда дальше

  • SAST по коду — конфиг .golangci.yml, gosec-правила, синтаксис //nolint со сроком.
  • CVE в зависимостях — govulncheck, govuln-suppressions.json, Renovate для go.mod.
  • Секреты в коде и истории — Gitleaks, .pre-commit-config.yaml, rotate-first при утечке.
  • Container/image-уязвимости — Trivy, distroless base, digest-pinned образы.
  • Криптография в коде — crypto/rand, AES-GCM, bcrypt cost ≥ 12, JWT-верификация.