Опирается на правила:
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 немедленно |
| HIGH | eslint-plugin-security / semgrep / osv-scanner / Trivy | ≤ 2 недели (sprint) | Сборка падает |
| MEDIUM | eslint-plugin-security / semgrep / Trivy | ≤ 30 дней | Отчёт, не блокирует |
| LOW | любой | — | Игнор, не отображается |
CRITICAL — hotfix < 24 часа
Примеры:
- Active exploit известного уровня (например, критический prototype pollution в transitive dep).
- Hardcoded prod credential в истории репо.
- CVE с public PoC в используемом в runtime пакете.
Action plan:
- Немедленный алерт security-team.
- Создание hotfix branch.
- Патч / временная митигация (overrides в
package.jsonдля transitive fix). - Deploy в прод в течение 24 часов.
- Post-mortem.
HIGH — текущий спринт
Примеры:
- CVE с CVSS ≥ 7.0 в прямой зависимости (
npm audit/osv-scanner). eslint-plugin-securityдетектит injection-паттерн в новом коде.semgrepruledetect-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-securitydetect-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-scanner — osv-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-ci — audit-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 без suppression | R-SEC-FIND-X1 | Suppression с обоснованием в osv-scanner.toml / audit-ci.json / eslint-комментарий |
| Suppression без срока | R-SEC-FIND-2 | expiry / дата в комментарии обязательны |
|| true после npm audit / eslint в CI | R-SEC-1 | --audit-level=high; --max-warnings 0 без || true |
| CRITICAL не hotfix < 24ч | R-SEC-FIND-1 | Hotfix branch + deploy в прод немедленно |
| HIGH в backlog без срока | R-SEC-FIND-1 | Включить в текущий спринт ≤ 2 нед |
| MEDIUM игнорируется бессрочно | R-SEC-FIND-1 | ≤ 30 дней, auto-escalate |
| Findings только в проприетарном инструменте без SARIF | R-SEC-FIND-3 | Экспорт SARIF в GitHub Security tab |
| Baseline не обновляется на merge в main | R-SEC-4 | Auto-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.