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

Важно знать

  • CRITICAL → сборка падает, hotfix < 24 часов.
  • HIGH → сборка падает, патч ≤ 2 недели (текущий спринт).
  • MEDIUM → отчёт, патч ≤ 30 дней.
  • LOW → игнорим, не отображаем в dashboard.
  • Suppression без срока — не считается suppressed, считается долгом.
  • SARIF — все инструменты (semgrep, osv-scanner, Trivy, @microsoft/eslint-formatter-sarif) отдают в GitHub Security tab единым потоком.
  • || true в CI — запрещено; прячет findings за кажущейся зелёной сборкой.
  • «Не уверен, эксплуатируется ли» — добавляй suppression с обоснованием. Молчание = долг.

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

Severity → SLA

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

SeverityИнструментыSLA на исправлениеБлокирует
CRITICALлюбой< 24 часаСборка падает, hotfix немедленно
HIGHeslint-plugin-security / semgrep / osv-scanner / Trivy≤ 2 недели (sprint)Сборка падает
MEDIUMeslint-plugin-security / semgrep / Trivy≤ 30 днейОтчёт, не блокирует
LOWлюбойИгнор, не отображается

CRITICAL — hotfix < 24 часа

Примеры:

  • Active exploit известного уровня (например, критический prototype pollution в transitive dep).
  • Hardcoded prod credential в истории репо.
  • CVE с public PoC в используемом в runtime пакете.

Action plan:

  1. Немедленный алерт security-team.
  2. Создание hotfix branch.
  3. Патч / временная митигация (overrides в package.json для transitive fix).
  4. Deploy в прод в течение 24 часов.
  5. Post-mortem.

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

Примеры:

  • CVE с CVSS ≥ 7.0 в прямой зависимости (npm audit / osv-scanner).
  • eslint-plugin-security детектит injection-паттерн в новом коде.
  • semgrep rule detect-non-literal-require на пользовательском вводе.

Action plan:

  • Ticket с label security/high.
  • Включение в текущий спринт.
  • PR review с фиксом до merge.
  • Deploy со следующим релизом.

MEDIUM — 30 дней

Примеры:

  • CVE с CVSS 4.0–6.9.
  • Trivy finding в OS-пакете базового образа без активного вектора.
  • eslint-plugin-security detect-possible-timing-attacks на некритичном пути.

Action plan:

  • Ticket с label security/medium.
  • Включение в один из следующих спринтов.
  • Не fixed за 30 дней → auto-escalate до HIGH.

LOW — игнор

Примеры:

  • Теоретическая CVE без известного вектора эксплуатации в dev-зависимости.
  • Trivy finding в test-контейнере.
  • eslint-plugin-security совет по стилю (detect-unsafe-regex на статичном паттерне без пользовательского ввода).

Не отображаем в dashboard. Не создаём ticket. Не тратим время.

Suppressions имеют срок

R-SEC-FIND-2: без срока — не suppressed, а долг.

Для osv-scannerosv-scanner.toml в корне репо:

# osv-scanner.toml
[[IgnoredVulns]]
id = "GHSA-xxxx-yyyy-zzzz"
reason = "false positive: транзитивный путь не достижим в runtime Order-сервиса; ждём патч в upstream@3.2.1 до 2026-09-01"

Для npm audit с audit-ciaudit-ci.json:

{
  "allowlist": [
    {
      "module-name": "some-package",
      "advisory": "GHSA-xxxx-yyyy-zzzz",
      "expiry": "2026-09-01",
      "reason": "CVE не достижима: модуль используется только в build tooling, не в runtime Customer API"
    }
  ]
}

Для eslint (suppression конкретной строки):

// eslint-disable-next-line security/detect-non-literal-fs-filename -- justify: путь строится из allowlist констант, не из пользовательского ввода; до: 2026-08-15
const report = fs.readFileSync(allowedPaths[type]);

Раз в квартал — автоматическая проверка просроченных:

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

node -e "
const cfg = require('./audit-ci.json');
const expired = (cfg.allowlist || []).filter(e => e.expiry && e.expiry < '$NOW');
if (expired.length) {
  console.error('EXPIRED suppressions:', JSON.stringify(expired, null, 2));
  process.exit(1);
}
"

CI прогоняет скрипт еженедельно и постит issue в репо. Без срока suppression превращается в «забытое исключение» — через год никто не помнит, почему добавили.

GitHub Code Scanning + SARIF

R-SEC-FIND-3: единый dashboard без отдельной инфры.

Все инструменты Node-стека отдают SARIF:

# .github/workflows/security.yml (фрагмент)
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: ESLint security (SARIF)
        run: |
          npx eslint --format @microsoft/eslint-formatter-sarif \
            --output-file eslint-results.sarif src/
        continue-on-error: true
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: eslint-results.sarif

      - name: Semgrep (SARIF)
        run: semgrep scan --sarif --output semgrep.sarif --config p/typescript --config p/nodejs src/
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif

      - name: OSV-Scanner (SARIF)
        run: osv-scanner --format sarif --output osv-results.sarif --lockfile package-lock.json
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: osv-results.sarif

      - name: Trivy image (SARIF)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/org/product-service:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

После — все findings в GitHub Security tab:

  • Фильтр по severity, инструменту, файлу.
  • Трекинг: open / dismissed / fixed.
  • Уведомления в Slack/PagerDuty через GitHub.

Для блокировки сборки на HIGH/CRITICAL eslint запускается отдельно с --max-warnings 0 до SARIF-экспорта:

- name: ESLint fail on HIGH/CRITICAL
  run: npx eslint --plugin security src/

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

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

OSV-Scanner: "GHSA-xxxx-yyyy-zzzz: ReDoS в package@1.2.3"
Developer:   "Ну вроде там regex на статичном паттерне, наверное не эксплуатируется"
[ничего не делает]
[finding остаётся в dashboard, security team тратит время снова и снова]

Что должно произойти — suppression с явным обоснованием в osv-scanner.toml:

[[IgnoredVulns]]
id = "GHSA-xxxx-yyyy-zzzz"
reason = """
ReDoS в package@1.2.3: regex вызывается только на enum-значениях из OrderStatus,
пользовательского ввода нет; reachable path в Product-каталоге отсутствует.
Ждём патч в upstream@1.3.0. Пересмотреть 2026-09-01 после upgrade.
"""

Это:

  • Запись в репо — security team может ревью.
  • Срок — повторная проверка через 3 месяца.
  • Обоснование — explicit аргумент, не «мне кажется».

Молчание = finding висит в dashboard, следующий ревьюер тратит те же 20 минут на тот же анализ.

Baseline-механика

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

{
  "osvScanner": {
    "knownVulnerabilities": [
      {
        "id": "GHSA-xxxx-yyyy-zzzz",
        "package": "some-package@1.2.3",
        "firstSeen": "2026-04-10"
      }
    ]
  }
}

При release tag — CI сравнивает findings с baseline:

#!/bin/bash
# tools/check-new-findings.sh
NEW=$(osv-scanner --format json --lockfile package-lock.json | \
  node -e "
    const known = require('./config/security-baseline.json').osvScanner.knownVulnerabilities.map(v => v.id);
    let data = '';
    process.stdin.on('data', d => data += d);
    process.stdin.on('end', () => {
      const parsed = JSON.parse(data);
      const ids = parsed.results.flatMap(r => r.packages.flatMap(p => p.vulnerabilities.map(v => v.id)));
      const newFindings = ids.filter(id => !known.includes(id));
      if (newFindings.length) { console.error('NEW findings:', newFindings); process.exit(1); }
    });
  ")

Без baseline первый CVE в transitive dep останавливает pipeline — команда добавляет || true и security-скан превращается в дашборд, который никто не смотрит.

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

АнтипаттернПравилоЧто взамен
Игнорирование finding без suppressionR-SEC-FIND-X1Suppression с обоснованием в osv-scanner.toml / audit-ci.json / eslint-комментарий
Suppression без срокаR-SEC-FIND-2expiry / дата в комментарии обязательны
|| true после npm audit / eslint в CIR-SEC-1--audit-level=high; --max-warnings 0 без || true
CRITICAL не hotfix < 24чR-SEC-FIND-1Hotfix branch + deploy в прод немедленно
HIGH в backlog без срокаR-SEC-FIND-1Включить в текущий спринт ≤ 2 нед
MEDIUM игнорируется бессрочноR-SEC-FIND-1≤ 30 дней, auto-escalate
Findings только в проприетарном инструменте без SARIFR-SEC-FIND-3Экспорт SARIF в GitHub Security tab
Baseline не обновляется на merge в mainR-SEC-4Auto-update в CI step
Просроченные suppressions не отслеживаютсяR-SEC-FIND-2Еженедельный скрипт + issue в репо

Куда дальше

  • SAST по коду — suppressions в eslint-комментарии и nosemgrep со сроком.
  • CVE в зависимостях — osv-scanner.toml, audit-ci.json, overrides для транзитивных фиксов.
  • Секреты в коде и истории — утечка = CRITICAL, rotate в течение часа.
  • Container/image-уязвимости — Trivy SARIF в Security tab, non-root, digest-pinned base.
  • Криптография в коде — weak crypto = HIGH, argon2/AES-GCM/crypto.randomBytes.