176 lines
15 KiB
Markdown
176 lines
15 KiB
Markdown
# 02 — ТЗ: Security-гейт (secret-scanning + dependency audit)
|
||
|
||
Work Item: **ORCH-022** · Стадия: analysis · См. `01-brd.md`, `03-acceptance-criteria.md`.
|
||
|
||
> **Граница ответственности аналитика.** Ниже — *функциональные требования и точки
|
||
> касания* кода. Выбор размещения гейта в пайплайне, конкретных инструментов и схемы
|
||
> модулей — **решение архитектора** (см. §4 и `01-brd.md` §8). ТЗ фиксирует требования к
|
||
> любому из допустимых вариантов и инварианты, которые нельзя нарушать.
|
||
|
||
---
|
||
|
||
## 1. Контекст кода (как есть)
|
||
|
||
- **Стадии:** `src/stages.py::STAGE_TRANSITIONS` — линейный конвейер
|
||
`… review → testing → deploy-staging → deploy → done`. Фактический merge ветки в
|
||
`main` делает агент `deployer` **в начале стадии `deploy`** (CLAUDE/README).
|
||
- **Quality Gates:** `src/qg/checks.py` — реестр `QG_CHECKS` (имя → функция), сигнатуры
|
||
диспетчеризуются в `src/stage_engine.py::_run_qg`.
|
||
- **Существующий паттерн «красный гейт → возврат developer»:**
|
||
`check_ci_green` (PR #18) и rollback-ветки в
|
||
`stage_engine._handle_qg_failure_rollbacks` (откат на `development`, developer-retry,
|
||
cap `MAX_DEVELOPER_RETRIES = 3`, затем `set_issue_blocked` + Telegram).
|
||
- **Эталонный паттерн детерминированного под-гейта на ребре** (без LLM, never-raise,
|
||
условный раскат, откат на `development`):
|
||
- merge-gate **ORCH-043** — `src/merge_gate.py` + `check_branch_mergeable` +
|
||
`stage_engine._handle_merge_gate` (ребро `deploy-staging → deploy`);
|
||
- image-freshness **ORCH-058** — `src/image_freshness.py` + `_check_staging_image_fresh`
|
||
+ `stage_engine._handle_image_freshness` (то же ребро).
|
||
Оба: leaf-модуль с чистой логикой (never-raise) + тонкая обёртка в `QG_CHECKS` +
|
||
врезка-обработчик в `advance_stage`, kill-switch `*_enabled` + scope `*_repos`,
|
||
реально только для self-hosting при пустом scope.
|
||
- **CI:** `.gitea/workflows/ci.yml` — один job `test` (pytest) на `self-hosted` раннере,
|
||
push в `feature/**` и PR в `main`. `check_ci_green` читает комбинированный статус
|
||
коммита из Gitea API.
|
||
- **Артефакты задачи** нумерованы до `16-post-deploy-log.md`.
|
||
- **Зависимости Python:** `requirements.txt` (корень репо).
|
||
|
||
---
|
||
|
||
## 2. Функциональные требования к реализации
|
||
|
||
### FR-1. Secret-scanning ветки перед мержем
|
||
- Сканировать ветку задачи / её diff относительно `origin/main` на секреты
|
||
(ключи, токены, пароли, приватные ключи).
|
||
- **Любой** подтверждённый секрет (не из аллоулиста) → вердикт **FAIL** (порог A4: секреты
|
||
всегда блокируют).
|
||
- Инструмент (gitleaks / trufflehog) — выбор архитектора. Должен запускаться offline-/
|
||
детерминированно (без LLM) и иметь конфиг правил/аллоулиста в репозитории.
|
||
|
||
### FR-2. Dependency audit
|
||
- Аудит зависимостей целевого стека на известные CVE. Для Python — манифест
|
||
`requirements.txt` (инструмент pip-audit / trivy — выбор архитектора).
|
||
- Классификация по severity. **Порог блокировки (A4, конфигурируемо BR-10):**
|
||
- `CRITICAL`, `HIGH` → вклад в **FAIL**;
|
||
- `MEDIUM`, `LOW` → **warning** (фиксируется в артефакте, не блокирует).
|
||
- Недоступность CVE-фида: degrade-поведение по решению ADR (дефолт-предложение —
|
||
fail-open + громкий warning, чтобы не плодить ложные завороты). Поведение должно быть
|
||
детерминированным и протестированным.
|
||
|
||
### FR-3. Машиночитаемый артефакт-вердикт
|
||
- Гейт порождает артефакт security-отчёта с **YAML-frontmatter**, напр.:
|
||
```
|
||
---
|
||
security_status: PASS # PASS | FAIL
|
||
secrets_found: 0
|
||
deps_blocking: 0 # число уязвимостей уровня блокировки
|
||
deps_warning: 2
|
||
---
|
||
```
|
||
Имя артефакта — предложение: **`17-security-report.md`** (следующий свободный номер;
|
||
финализирует архитектор). Тело — человекочитаемый список находок.
|
||
- Вердикт читается гейтом **ТОЛЬКО из frontmatter** (канон проекта: «машинные вердикты —
|
||
строго YAML-frontmatter, никогда проза»), по образцу `_parse_deploy_status` /
|
||
`_parse_staging_status` / `check_reviewer_verdict`. Negative-токен (FAIL) авторитетен.
|
||
- Отсутствие/битый frontmatter → `(False, reason)` (fail-closed на чтении вердикта,
|
||
как у существующих парсеров).
|
||
|
||
### FR-4. Поведение красного гейта (откат)
|
||
- `security_status: FAIL` → откат на `development` + enqueue `developer`, по образцу
|
||
`_handle_qg_failure_rollbacks` (merge-gate-ветка — точный шаблон):
|
||
- cap `MAX_DEVELOPER_RETRIES` (3); при исчерпании — `set_issue_blocked` + Telegram-алерт;
|
||
- `task_desc` для developer несёт **дословную причину** (какие секреты/CVE), по образцу
|
||
ORCH-046 (встраивание must-fix в `task_desc`), а не только ссылку на артефакт;
|
||
- Plane-коммент + `notify_qg_failure` (наблюдаемость BR-11).
|
||
|
||
### FR-5. Условный раскат (как ORCH-35/43/58)
|
||
- Глобальный kill-switch `security_gate_enabled` (env `ORCH_SECURITY_GATE_ENABLED`,
|
||
дефолт по согласованию; рекомендуется `true` с safety-net, как у соседних фич).
|
||
- Scope `security_gate_repos` (CSV); пусто → реально только `is_self_hosting_repo(repo)`
|
||
(`orchestrator`). Прочие репо → `(True, "security-gate N/A for <repo>")` (мгновенный pass).
|
||
- Отдельные пороги-флаги (A4/BR-10): напр. `security_dep_block_severity`
|
||
(`HIGH` по умолчанию), при желании `security_secrets_block` (`true`).
|
||
|
||
### FR-6. never-raise
|
||
- Любая внутренняя ошибка гейта (сбой сканера, отсутствие бинаря, таймаут) →
|
||
`(False, "<reason>")` **без** проброса исключения в `advance_stage`. Контракт —
|
||
как у `check_branch_mergeable` (внешний + внутренний guard).
|
||
- Таймаут сканирования ограничен (по образцу `merge_retest_timeout_s`).
|
||
|
||
### FR-7. Наблюдаемость
|
||
- Блокировка → Telegram + Plane-коммент (BR-11). Проход → лог-строка, без шумных
|
||
нотификаций (по образцу merge-gate pass).
|
||
- Желательно: краткий снимок в `GET /queue` (опционально, по образцу блоков `reconcile`/
|
||
`reaper`/`post_deploy`) — на усмотрение архитектора.
|
||
|
||
---
|
||
|
||
## 3. Задействованные модули `src/` (точки касания)
|
||
|
||
| Модуль | Изменение |
|
||
|--------|-----------|
|
||
| `src/security_gate.py` (**новый leaf-модуль**) | Чистая логика гейта: запуск сканеров, классификация по severity, применение порогов/аллоулиста, формирование вердикта + парсер frontmatter. **never-raise.** По образцу `src/merge_gate.py` / `src/image_freshness.py` / `src/post_deploy.py`. |
|
||
| `src/qg/checks.py` | Новый чек `check_security_gate` (тонкая обёртка над `security_gate`, ленивый импорт во избежание циклов) + регистрация в `QG_CHECKS`. Условность (kill-switch/scope/self-hosting) — как `check_branch_mergeable` / `_check_staging_image_fresh`. |
|
||
| `src/stage_engine.py` | Врезка-обработчик `_handle_security_gate(...)` по образцу `_handle_merge_gate` / `_handle_image_freshness`: вызов в `advance_stage` на выбранном архитектором ребре; FAIL → откат на `development` (FR-4); never-raise. **`STAGE_TRANSITIONS` НЕ меняется**, если выбран вариант «под-гейт ребра». |
|
||
| `src/config.py` | Новые настройки: `security_gate_enabled`, `security_gate_repos`, `security_dep_block_severity`, `security_scan_timeout_s` (+ при необходимости пути к бинарям/конфигам сканеров). С docstring-комментариями по образцу ORCH-043/058. |
|
||
| `.gitea/workflows/ci.yml` | **Если** архитектор выберет CI-путь: новый job `security` (secret-scan + dep-audit), влияющий на комбинированный статус коммита (тогда срабатывает `check_ci_green`-паттерн PR #18). Иначе — не трогается. |
|
||
| `requirements.txt` / Dockerfile | Установка выбранных сканеров (если они Python-пакеты — в `requirements.txt`; если бинари — в Dockerfile/раннер). |
|
||
| Конфиг сканера + аллоулист | Версионируемые файлы в репозитории (напр. `.gitleaks.toml` / аллоулист) — BR-13. |
|
||
| `.openclaw/agents/developer.md` | (Если нужно) краткая инструкция developer'у про устранение security-находок при заворотах. |
|
||
|
||
> Если выбран вариант «гейт на стадии `review`» — врезка делается в соответствующую
|
||
> ветку `advance_stage`/обработчик ревью вместо ребра `deploy-staging → deploy`.
|
||
|
||
---
|
||
|
||
## 4. Размещение в пайплайне — варианты для архитектора
|
||
|
||
Требование BRD: **«перед слиянием ветки в `main`»**. Допустимы (выбор + обоснование — в ADR):
|
||
|
||
- **Вариант R (review):** security-проверка на стадии `review` (раньше отлов, дешевле
|
||
откат — задача ещё близко к development). Минус: дальше по конвейеру `main` может уйти
|
||
вперёд (но это закрывает merge-gate).
|
||
- **Вариант M (merge-edge, рекомендуемый к рассмотрению):** под-гейт на ребре
|
||
`deploy-staging → deploy`, рядом с merge-gate (ORCH-043) и image-freshness (ORCH-058) —
|
||
непосредственно перед фактическим мержем `deployer`'ом. Плюс: единое место «последней
|
||
страховки перед main», переиспользование готового паттерна врезки/отката/lease.
|
||
- **Вариант C (CI-job):** добавить job в `ci.yml`; вердикт течёт через `check_ci_green`.
|
||
Плюс: меньше нового кода в движке. Минус: пороги/severity-логика и артефакт-вердикт
|
||
сложнее выразить только статусом коммита.
|
||
|
||
ТЗ не предписывает вариант; реализация обязана сохранить инварианты §6.
|
||
|
||
---
|
||
|
||
## 5. Изменения API
|
||
- Новых HTTP-endpoint'ов **не требуется**.
|
||
- Допустимо (опционально, FR-7): расширить ответ `GET /queue` блоком `security`
|
||
(counts/last_run) — по образцу блоков `reconcile`/`reaper`/`post_deploy`. Не обязательно.
|
||
|
||
## 6. Изменения схемы БД
|
||
- **Не требуется.** Состояние гейта — артефакт-файл + (при необходимости) sentinel-файлы,
|
||
по образцу merge-lease / deploy-state / post-deploy-state. Миграций БД нет.
|
||
- Если архитектор сочтёт нужным считать security-retry отдельно от developer-retry —
|
||
предпочесть подсчёт по `jobs`/`agent_runs` (как `_developer_retry_count` /
|
||
`_merge_defer_count`), без новых колонок.
|
||
|
||
## 7. Инварианты (НЕ нарушать)
|
||
1. `STAGE_TRANSITIONS` и реестр `QG_CHECKS` остаются консистентными; при варианте
|
||
«под-гейт ребра» — `STAGE_TRANSITIONS` не меняется (триггер — то же событие стадии).
|
||
2. Машинный вердикт — только из YAML-frontmatter, не из прозы.
|
||
3. never-raise: гейт никогда не пробрасывает исключение в `advance_stage`.
|
||
4. Условность как ORCH-35/43/58: не-self репо при пустом scope не затрагиваются (no-op).
|
||
5. Гейт **не деплоит и не рестартит** прод-контейнер (self-hosting safety).
|
||
6. Откат и retry-счётчик developer не ломаются (cap=3, затем эскалация).
|
||
7. Документация (CLAUDE.md, README, CHANGELOG, ADR) обновлена в том же PR (BR-12).
|
||
|
||
## 8. Артефакты pipeline, создаваемые/обновляемые
|
||
- **Новый:** `docs/work-items/ORCH-022/17-security-report.md` (имя финализирует архитектор)
|
||
с `security_status:`-frontmatter (FR-3) — порождается гейтом per-task.
|
||
- **ADR:** `docs/work-items/ORCH-022/06-adr/ADR-001-<slug>.md` (решение: размещение,
|
||
инструменты, degrade-поведение фида, пороги). При сквозном влиянии — global ADR в
|
||
`docs/architecture/adr/`.
|
||
- **Обновить:** `CLAUDE.md` (раздел «Артефакты задачи» — добавить 17-…),
|
||
`docs/architecture/README.md` (таблица гейтов + реестр `QG_CHECKS` + новый раздел),
|
||
`CHANGELOG.md`, `.env.example` (новые `ORCH_SECURITY_*`).
|