# adr-0012: Security-гейт — secret-scanning + dependency audit перед мержем - **Статус:** proposed - **Дата:** 2026-06-07 - **Задача:** ORCH-022 - **Детальный ADR:** `docs/work-items/ORCH-022/06-adr/ADR-001-security-gate.md` ## Контекст Оркестратор автономен: `developer` пишет код без человека-фильтра. Перед слиянием ветки в `main` нет проверки на утёкший секрет (ключ/токен/пароль/приватный ключ) и уязвимую зависимость (CVE). Для self-hosting один общий прод-инстанс обслуживает все проекты с общей БД — секрет/CVE через одну задачу попадает в прод всех (CLAUDE.md §self-hosting, §8). Фактический мерж PR в `main` делает `deployer` в начале стадии `deploy`. ## Решение Детерминированный (без LLM) **security-гейт как под-гейт ребра `deploy-staging → deploy`**, рядом с merge-gate (ORCH-043) и image-freshness (ORCH-058), исполняемый **ПЕРВЫМ** среди edge-под-гейтов (ДО merge-gate). `STAGE_TRANSITIONS` не меняется; в `QG_CHECKS` добавлен `check_security_gate`. Паттерн — как у соседей: leaf-модуль `src/security_gate.py` (never-raise) + тонкая обёртка в `QG_CHECKS` + врезка `_handle_security_gate` в `advance_stage`. - **Secret-scanning (`gitleaks`, offline):** скан `origin/main..HEAD`; любой секрет вне аллоулиста (`.gitleaks.toml`) → вклад в FAIL. Offline → гарантия «секрет всегда блокирует» не зависит от сети. - **Dependency audit (`pip-audit`, OSV/PyPI):** severity ≥ `security_dep_block_severity` (дефолт `HIGH`) → FAIL; ниже / UNKNOWN → warning. Недоступность фида → **fail-open + громкий warning** (анти-петля; флаг `security_dep_audit_fail_closed` для строгого режима). - **ПЕРВЫМ на ребре, ДО merge-gate:** дёшево фейлить до дорогих rebase/rebuild; скан ветки ДО rebase не «обвиняет» задачу в CVE, притащенной обновившимся `main` (анти-петля ORCH-061); до захвата merge-lease → при FAIL lease освобождать не нужно. - **Артефакт `17-security-report.md`** с YAML-frontmatter (`security_status`, `secrets_found`, `deps_blocking`, `deps_warning`, `deps_audit_degraded`); вердикт читается ТОЛЬКО из frontmatter (канон), negative-токен авторитетен; битый/нет → fail-closed. - **FAIL → откат на `development`** + developer-retry (общий `_developer_retry_count`, cap 3, затем `set_issue_blocked` + Telegram); `task_desc` несёт дословные находки (ORCH-046). - **Условность (как ORCH-35/43/58):** `security_gate_enabled` + `security_gate_repos`; пусто → реально только self-hosting (`orchestrator`), прочие репо — no-op pass. - **never-raise**, таймаут `security_scan_timeout_s`, гейт не деплоит/не рестартит прод. ## Альтернативы - **Вариант R (review-стадия):** diff может разойтись с мержем в `main`; merge-edge — последняя страховка. Отклонено. - **Вариант C (CI-job через `check_ci_green`):** пороги/severity/аллоулист/артефакт плохо выражаются статусом коммита; коуплинг с раннером. Отклонено для v1 (точка расширения). - **Новая стадия `security`:** «пустая» стадия без агента не имеет триггера (как в ORCH-043). Отклонено. - **fail-closed dep-audit / аудит после rebase:** ложные откаты → петля. Отклонено. - **Новая колонка retry в БД:** не нужна (переиспользуем `_developer_retry_count`). ## Последствия - Класс «тихо влитый секрет/CVE» закрыт: секреты — безусловно (offline), CVE — best-effort при доступности фида. Самоприменение CLAUDE.md §8 без человека. - Плата: ещё один «скрытый» под-гейт ребра (нет в `STAGE_TRANSITIONS`); внешние инструменты (gitleaks в образе, pip-audit в зависимостях); время скана на каждом прогоне (ограничено таймаутом); v1 — Python-only (SAST/мульти-стек — follow-up WI). - Сквозное изменение (новый QG + edge-под-гейт) → `arch:major-change`; прод-деплой ORCH-022 — строго через staging-гейт (8501), без рестарта прод-контейнера. ## Связи adr-0006 (merge-gate — паттерн edge-под-гейта/отката), adr-0008 (image-freshness — условность/never-raise/fail-closed), adr-0003 (условный гейт / `is_self_hosting_repo`), adr-0009 (анти-петля ложных FAIL, ORCH-061), ORCH-046 (дословный reason в `task_desc`), ORCH-9/15 (мульти-стек — будущая зависимость), ORCH-2 (worktree-изоляция).