integ: merge ORCH-022 security/secret-scanning
This commit is contained in:
7
docs/work-items/ORCH-022/00-business-request.md
Normal file
7
docs/work-items/ORCH-022/00-business-request.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Business Request: [★ высокий] Security-гейт: secret-scanning + аудит зависимостей перед мержем
|
||||
|
||||
Work Item ID: ORCH-022
|
||||
|
||||
## Description
|
||||
|
||||
TBD
|
||||
150
docs/work-items/ORCH-022/01-brd.md
Normal file
150
docs/work-items/ORCH-022/01-brd.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# 01 — BRD: Security-гейт (secret-scanning + аудит зависимостей перед мержем)
|
||||
|
||||
Work Item: **ORCH-022**
|
||||
Приоритет: **★ высокий**
|
||||
Источник: предложение Стрим, одобрено Славой (2026-06-04).
|
||||
Стадия: analysis.
|
||||
|
||||
---
|
||||
|
||||
## 1. Бизнес-проблема
|
||||
|
||||
Оркестратор — автономная мульти-агентная система: агенты (`developer`) пишут код
|
||||
**без человека-фильтра по умолчанию**. Перед мержем в `main` сейчас нет проверки на:
|
||||
|
||||
- **утёкший секрет** — закоммиченный API-ключ / токен / пароль / приватный ключ;
|
||||
- **дырявую зависимость** — пакет с известной CVE;
|
||||
- (опционально) **базовую уязвимость кода** — типовой SAST-паттерн.
|
||||
|
||||
Для автономной системы это критично: ошибку, которую в обычной команде «выловили бы
|
||||
глазами на ревью», здесь поймать некому. Утёкший в `git`-историю ключ или уязвимая
|
||||
зависимость может уехать в прод и обслуживать **все** проекты (общий инстанс,
|
||||
self-hosting).
|
||||
|
||||
### Прецеденты / связки
|
||||
- **PR #18** (`check_ci_green`: красный CI → возврат на `development`) — задаёт целевой
|
||||
паттерн поведения красного гейта. Security-гейт должен вести себя так же.
|
||||
- **Управление секретами** (CLAUDE.md §8): секреты живут только в `.env`/`.env.staging`
|
||||
на хосте, канон — `.env.example`. Гейт — это автоматический страж этого правила.
|
||||
|
||||
---
|
||||
|
||||
## 2. Цель
|
||||
|
||||
Ввести **security-гейт перед слиянием ветки задачи в `main`**, который детерминированно
|
||||
(без LLM) проверяет diff/ветку на секреты и уязвимые зависимости и **блокирует
|
||||
продвижение** при нарушении порогов: красный security-гейт → **возврат на `development`**
|
||||
(developer-retry, как красный CI / merge-gate), задача **не уезжает в прод**.
|
||||
|
||||
### Бизнес-ценность
|
||||
- Структурно невозможно «тихо» влить секрет или известную CVE в прод автономной системы.
|
||||
- Самоприменение правила CLAUDE.md §8 (секреты не в гит) без участия человека.
|
||||
- Расширяет уже выстроенную линию автономных страховок (CI-гейт, merge-gate ORCH-043,
|
||||
staging-провенанс ORCH-058, post-deploy ORCH-021).
|
||||
|
||||
---
|
||||
|
||||
## 3. Объём (Scope)
|
||||
|
||||
### 3.1 В объёме (v1) — **предположение по умолчанию (A1)**
|
||||
1. **Secret-scanning** — обязательный минимум гейта. Поиск закоммиченных секретов
|
||||
в ветке задачи / её diff относительно `main`.
|
||||
2. **Dependency audit** — аудит зависимостей проекта на известные CVE.
|
||||
3. **Машиночитаемый артефакт-вердикт** security-гейта (YAML-frontmatter — канон гейтов).
|
||||
4. **Поведение красного гейта** = откат на `development` + developer-retry (cap
|
||||
`MAX_DEVELOPER_RETRIES = 3`), наблюдаемость (Telegram + Plane-коммент).
|
||||
5. **Условный раскат** (kill-switch + scope репозиториев), **never-raise**,
|
||||
self-hosting (`orchestrator`) — первым.
|
||||
|
||||
### 3.2 Вне объёма (v1) — **предположение (A2), отдельные WI**
|
||||
- **SAST (semgrep)** — вынесен в follow-up WI: шумнее, требует policy-тюнинга правил;
|
||||
гейт проектируется с точкой расширения под него, но в v1 не включается.
|
||||
- **Полноценный мульти-стек** (JS/npm, Android) — см. A3 ниже; в v1 целевой стек —
|
||||
Python (сам оркестратор). Связь с ORCH-9/15 фиксируется как зависимость на будущее.
|
||||
- Ретроспективное сканирование уже существующей истории `main` (гейт смотрит вперёд —
|
||||
ветку перед мержем, не чистит прошлое).
|
||||
- Управление аллоулистом ложных срабатываний через UI/Plane (в v1 — файл в репозитории).
|
||||
|
||||
### 3.3 Зафиксированные предположения по умолчанию
|
||||
> ⚠️ Интерактивный опрос Owner на стадии анализа не дал ответа; ниже —
|
||||
> **дефолты по конвенциям проекта**. Любой из них Owner/архитектор может переопределить
|
||||
> (для A4 предусмотрены конфиг-флаги порогов).
|
||||
|
||||
- **A1 (объём сканеров v1):** secret-scanning + dependency-audit. SAST отложен.
|
||||
- **A2 (SAST):** отложен в отдельный WI; гейт оставляет точку расширения.
|
||||
- **A3 (стек):** **Python-only сначала**, реально только для self-hosting
|
||||
(`is_self_hosting_repo` / scope-CSV), как ORCH-35/43/58. Прочие репо — no-op pass.
|
||||
Мульти-стек (детект стека по репо) — отдельный WI.
|
||||
- **A4 (пороги):** **секреты — всегда блок**; **зависимости — блок на HIGH/CRITICAL,
|
||||
warning на MEDIUM/LOW**. Пороги вынесены в конфиг (переопределяемы без редеплоя кода).
|
||||
|
||||
---
|
||||
|
||||
## 4. Заинтересованные стороны
|
||||
| Роль | Интерес |
|
||||
|------|---------|
|
||||
| Owner (Слава) | Прод-безопасность автономного конвейера; контроль порогов и раската. |
|
||||
| Стрим | Инициатор; снижение риска утечки/уязвимости в автономном режиме. |
|
||||
| Агент `developer` | Получает понятную причину красного гейта → быстрый фикс. |
|
||||
| Агент `reviewer` | Гейт снимает с него непосильную задачу «глазами ловить ключи». |
|
||||
| Все проекты на инстансе | Общий прод не должен получить секрет/CVE через одну задачу. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Бизнес-требования
|
||||
|
||||
| ID | Требование | Приоритет |
|
||||
|----|-----------|-----------|
|
||||
| BR-1 | Перед слиянием ветки задачи в `main` обязателен security-гейт (секреты + аудит зависимостей). | MUST |
|
||||
| BR-2 | Найден секрет (порог A4) → гейт **красный** → откат на `development`, в прод не уходит. | MUST |
|
||||
| BR-3 | Уязвимость зависимости уровня блокировки (порог A4) → гейт **красный** → откат на `development`. | MUST |
|
||||
| BR-4 | Уязвимость ниже порога блокировки → **warning**, продвижение не блокируется, но фиксируется в артефакте. | MUST |
|
||||
| BR-5 | Красный гейт ведёт себя как красный CI / merge-gate: откат на `development` + developer-retry (cap 3), затем эскалация (Telegram + Plane Blocked). | MUST |
|
||||
| BR-6 | Вердикт гейта — **машиночитаемый** (YAML-frontmatter артефакта), читается гейтом ТОЛЬКО из frontmatter (канон проекта), не из прозы. | MUST |
|
||||
| BR-7 | Гейт **детерминированный, без LLM** в критическом пути (как merge-gate / image-freshness). | MUST |
|
||||
| BR-8 | Гейт **never-raise**: внутренняя ошибка не роняет `advance_stage` и не вешает конвейер всех проектов. | MUST |
|
||||
| BR-9 | Условный раскат: глобальный kill-switch + scope-CSV репозиториев; пусто → реально только self-hosting (`orchestrator`), прочие репо — no-op pass. | MUST |
|
||||
| BR-10 | Пороги блокировки конфигурируемы (env-флаги, без редеплоя кода). | SHOULD |
|
||||
| BR-11 | Наблюдаемость: причина блокировки видна (Telegram + Plane-коммент + артефакт); проход — без шума. | MUST |
|
||||
| BR-12 | Документация (CLAUDE.md «Артефакты задачи», `docs/architecture/README.md` таблица гейтов, CHANGELOG, ADR) обновлена в том же PR. | MUST |
|
||||
| BR-13 | Аллоулист ложных срабатываний (заведомо-безопасные совпадения, напр. в `.env.example`, фикстуры тестов) поддерживается версионируемым файлом в репозитории. | SHOULD |
|
||||
| BR-14 | Точка расширения под SAST и мульти-стек заложена, но в v1 не активна (A2/A3). | SHOULD |
|
||||
|
||||
---
|
||||
|
||||
## 6. Ограничения и риски (бизнес-уровень)
|
||||
- **Self-hosting:** гейт исполняется внутри инстанса, который правит сам себя. Запрет на
|
||||
рестарт/падение прод-контейнера в рамках задачи (CLAUDE.md §self-hosting) сохраняется —
|
||||
гейт ничего не деплоит и не рестартит, только читает/сканирует.
|
||||
- **Ложные срабатывания** (false positives) могут зациклить откат `→ development`
|
||||
(прецедент ORCH-061 со staging-петлёй). Митигировано: cap retry=3 + аллоулист (BR-13)
|
||||
+ конфигурируемые пороги (BR-10) + kill-switch (BR-9).
|
||||
- **Внешние БД уязвимостей** (CVE-фиды) — сетевая зависимость; недоступность фида не
|
||||
должна давать ложный красный (см. AC: degrade-поведение при недоступности фида —
|
||||
решение порога «fail-open vs fail-closed для аудита» закрепляется в acceptance + ADR).
|
||||
- **Стоимость/время** сканирования добавляется к каждому прогону задачи — должно быть
|
||||
ограничено таймаутом (как merge-retest).
|
||||
|
||||
---
|
||||
|
||||
## 7. Критерий успеха (бизнес)
|
||||
Ветка с подсаженным тестовым секретом и/или зависимостью с известной CRITICAL-CVE
|
||||
**не может** дойти до `main`/прода: гейт краснеет, задача откатывается на `development`
|
||||
с понятной причиной. Чистая ветка проходит гейт без задержек и без шума. Для не-self
|
||||
репозиториев конвейер не меняется (no-op). Прод-контейнер не рестартится гейтом.
|
||||
|
||||
---
|
||||
|
||||
## 8. Открытые вопросы (для архитектора / Owner)
|
||||
1. **Размещение гейта** (решение архитектора): (а) на стадии `review`, либо (б) отдельный
|
||||
под-гейт перед мержем на ребре `deploy-staging → deploy` (где уже живёт merge-gate
|
||||
ORCH-043 / image-freshness ORCH-058). Требование BRD — «перед слиянием в `main`»;
|
||||
обе опции его удовлетворяют. См. 02-trz §4.
|
||||
2. **Где запускается сканер**: новый job в `.gitea/workflows/ci.yml` (тогда вердикт может
|
||||
течь через существующий `check_ci_green`) **или** отдельный QG-чек/под-гейт в `src/qg`.
|
||||
Решение — архитектор (02-trz фиксирует требования к обоим путям).
|
||||
3. **Аудит зависимостей при недоступном CVE-фиде:** fail-open (warning) или fail-closed
|
||||
(блок)? Дефолт-предложение — **fail-open с громким warning** (не плодить ложные
|
||||
завороты), закрепить в ADR.
|
||||
4. **Выбор конкретных инструментов** (gitleaks vs trufflehog; pip-audit vs trivy) —
|
||||
технологическое решение архитектора; BRD фиксирует только функцию.
|
||||
175
docs/work-items/ORCH-022/02-trz.md
Normal file
175
docs/work-items/ORCH-022/02-trz.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 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_*`).
|
||||
140
docs/work-items/ORCH-022/03-acceptance-criteria.md
Normal file
140
docs/work-items/ORCH-022/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 03 — Критерии приёмки: Security-гейт (ORCH-022)
|
||||
|
||||
Формат: каждый критерий имеет чёткое условие **PASS/FAIL**. Привязка к
|
||||
`01-brd.md` (BR-*) и `02-trz.md` (FR-*).
|
||||
|
||||
---
|
||||
|
||||
## A. Secret-scanning (FR-1, BR-1/BR-2)
|
||||
|
||||
### AC-1 — Подсаженный секрет блокирует гейт
|
||||
- **PASS:** ветка с тестовым секретом (напр. фиктивный AWS-ключ формата `AKIA…` вне
|
||||
аллоулиста) → `security_status: FAIL`; гейт возвращает `(False, reason)`, причина
|
||||
называет секрет/файл.
|
||||
- **FAIL:** секрет не обнаружен ИЛИ гейт зелёный при наличии секрета.
|
||||
|
||||
### AC-2 — Чистая ветка проходит
|
||||
- **PASS:** ветка без секретов → `security_status: PASS`; `secrets_found: 0`;
|
||||
гейт возвращает `(True, …)`.
|
||||
- **FAIL:** ложное срабатывание (FAIL на чистой ветке).
|
||||
|
||||
### AC-3 — Аллоулист подавляет заведомо-безопасное (BR-13)
|
||||
- **PASS:** совпадение, явно занесённое в версионируемый аллоулист (напр. плейсхолдер в
|
||||
`.env.example` / фикстура теста), **не** даёт FAIL.
|
||||
- **FAIL:** аллоулист игнорируется и даёт ложный FAIL.
|
||||
|
||||
---
|
||||
|
||||
## B. Dependency audit (FR-2, BR-3/BR-4)
|
||||
|
||||
### AC-4 — CVE уровня блокировки краснит гейт
|
||||
- **PASS:** зависимость с известной `CRITICAL`/`HIGH` CVE (при пороге по умолчанию
|
||||
`HIGH`) → вклад в `security_status: FAIL`; `deps_blocking >= 1`.
|
||||
- **FAIL:** блокирующая уязвимость не приводит к FAIL.
|
||||
|
||||
### AC-5 — Низкая severity = warning, не блок
|
||||
- **PASS:** только `MEDIUM`/`LOW` уязвимости → `security_status: PASS`, при этом
|
||||
`deps_warning >= 1` и находки перечислены в теле артефакта.
|
||||
- **FAIL:** `MEDIUM`/`LOW` блокирует продвижение.
|
||||
|
||||
### AC-6 — Порог блокировки конфигурируем (BR-10)
|
||||
- **PASS:** при `ORCH_SECURITY_DEP_BLOCK_SEVERITY=CRITICAL` та же `HIGH`-уязвимость
|
||||
становится warning (не блок); при `=HIGH` — блок. Поведение детерминированно
|
||||
определяется флагом.
|
||||
- **FAIL:** флаг не влияет на классификацию.
|
||||
|
||||
### AC-7 — Degrade при недоступном CVE-фиде
|
||||
- **PASS:** недоступность фида обрабатывается по решению ADR детерминированно и
|
||||
протестированно (дефолт: fail-open + громкий warning, гейт не краснеет ложно).
|
||||
- **FAIL:** недоступность фида даёт неконтролируемый красный/исключение.
|
||||
|
||||
---
|
||||
|
||||
## C. Вердикт и артефакт (FR-3, BR-6)
|
||||
|
||||
### AC-8 — Машинный вердикт только из frontmatter
|
||||
- **PASS:** вердикт читается ТОЛЬКО из YAML-frontmatter `17-security-report.md`; проза с
|
||||
«PASS»/«FAIL» в теле не влияет на решение. Negative-токен (FAIL) авторитетен.
|
||||
- **FAIL:** вердикт извлекается из тела/прозы.
|
||||
|
||||
### AC-9 — Битый/отсутствующий frontmatter → fail-closed на чтении
|
||||
- **PASS:** нет frontmatter / битый YAML / нет поля `security_status` → `(False, reason)`
|
||||
(как `_parse_deploy_status`/`check_reviewer_verdict`).
|
||||
- **FAIL:** битый артефакт трактуется как PASS.
|
||||
|
||||
### AC-10 — Артефакт создаётся с корректными полями
|
||||
- **PASS:** после прогона существует `17-security-report.md` с валидным frontmatter
|
||||
(`security_status`, `secrets_found`, `deps_blocking`, `deps_warning`) и телом-списком.
|
||||
- **FAIL:** артефакт не создан/без машинных полей.
|
||||
|
||||
---
|
||||
|
||||
## D. Откат и retry (FR-4, BR-5)
|
||||
|
||||
### AC-11 — Красный гейт → откат на development + developer-retry
|
||||
- **PASS:** `FAIL` → стадия задачи становится `development`, enqueue `developer`,
|
||||
Plane-коммент + `notify_qg_failure`; счётчик developer-retry растёт.
|
||||
- **FAIL:** при FAIL задача продвигается дальше / не откатывается.
|
||||
|
||||
### AC-12 — task_desc несёт дословную причину (ORCH-046-паттерн)
|
||||
- **PASS:** `task_desc` для перезапущенного developer содержит конкретику находок
|
||||
(какие секреты/CVE), а не только ссылку на артефакт.
|
||||
- **FAIL:** developer получает только ссылку без сути.
|
||||
|
||||
### AC-13 — Cap retry и эскалация
|
||||
- **PASS:** после `MAX_DEVELOPER_RETRIES` (3) безуспешных фиксов — `set_issue_blocked` +
|
||||
Telegram-алерт; бесконечного отскока нет.
|
||||
- **FAIL:** откат зацикливается без cap/эскалации.
|
||||
|
||||
---
|
||||
|
||||
## E. Условный раскат и устойчивость (FR-5/FR-6, BR-8/BR-9)
|
||||
|
||||
### AC-14 — Не-self репозиторий = no-op pass
|
||||
- **PASS:** для repo, не входящего в scope и не self-hosting → гейт возвращает
|
||||
`(True, "security-gate N/A for <repo>")` мгновенно, конвейер такого репо не меняется.
|
||||
- **FAIL:** гейт реально запускается/блокирует чужой репо при пустом scope.
|
||||
|
||||
### AC-15 — Kill-switch отключает гейт
|
||||
- **PASS:** `ORCH_SECURITY_GATE_ENABLED=false` → гейт — no-op pass (`(True, …)`),
|
||||
поведение конвейера 1:1 как до ORCH-022.
|
||||
- **FAIL:** при выключенном флаге гейт всё ещё блокирует.
|
||||
|
||||
### AC-16 — never-raise
|
||||
- **PASS:** искусственный сбой (нет бинаря сканера / таймаут / исключение внутри) →
|
||||
`(False, reason)` без проброса исключения; `advance_stage` не падает, конвейер других
|
||||
задач/проектов не встаёт.
|
||||
- **FAIL:** внутренняя ошибка пробрасывается/вешает движок.
|
||||
|
||||
### AC-17 — Таймаут ограничен
|
||||
- **PASS:** сканирование, превысившее `ORCH_SECURITY_SCAN_TIMEOUT_S`, корректно
|
||||
прерывается → детерминированный вердикт (по политике degrade), без зависания.
|
||||
- **FAIL:** сканер висит без таймаута.
|
||||
|
||||
---
|
||||
|
||||
## F. Инварианты и интеграция (BR-7/BR-12, TRZ §7)
|
||||
|
||||
### AC-18 — STAGE_TRANSITIONS/QG_CHECKS консистентны
|
||||
- **PASS:** при варианте «под-гейт ребра» `STAGE_TRANSITIONS` не изменён; новый чек
|
||||
зарегистрирован в `QG_CHECKS`; `_run_qg` корректно его диспетчеризует. Все
|
||||
существующие тесты гейтов/стадий зелёные.
|
||||
- **FAIL:** сломан реестр/переходы/существующие тесты.
|
||||
|
||||
### AC-19 — Гейт не деплоит/не рестартит прод
|
||||
- **PASS:** код гейта не вызывает деплой-хук/рестарт прод-контейнера; только
|
||||
чтение/сканирование.
|
||||
- **FAIL:** гейт инициирует рестарт/деплой.
|
||||
|
||||
### AC-20 — Документация обновлена в том же PR (BR-12)
|
||||
- **PASS:** обновлены `CLAUDE.md` (артефакт 17-…), `docs/architecture/README.md`
|
||||
(таблица гейтов + реестр QG + раздел ORCH-022), `CHANGELOG.md`, `.env.example`
|
||||
(`ORCH_SECURITY_*`); заведён ADR `06-adr/ADR-001-*`.
|
||||
- **FAIL:** функционал есть, документация/ADR не обновлены → reviewer обязан
|
||||
REQUEST_CHANGES (CLAUDE.md §6).
|
||||
|
||||
### AC-21 — End-to-end на тестовой задаче
|
||||
- **PASS:** прогон на self-hosting-репо: грязная ветка (секрет/CVE) → откат на
|
||||
`development`; после фикса чистая ветка → гейт зелёный → конвейер идёт дальше; прод не
|
||||
затронут в процессе.
|
||||
- **FAIL:** любой шаг E2E не воспроизводится.
|
||||
126
docs/work-items/ORCH-022/04-test-plan.yaml
Normal file
126
docs/work-items/ORCH-022/04-test-plan.yaml
Normal file
@@ -0,0 +1,126 @@
|
||||
work_item: ORCH-022
|
||||
title: "Security-гейт: secret-scanning + dependency audit перед мержем"
|
||||
notes: >
|
||||
План тестов для security-гейта. Чистая логика выносится в leaf-модуль
|
||||
src/security_gate.py (never-raise) — основной предмет unit-тестов (по образцу
|
||||
tests для merge_gate / image_freshness / post_deploy / staging_verdict).
|
||||
Интеграция врезки в advance_stage и условный раскат — integration-тесты.
|
||||
Имена модулей тестов финализирует разработчик/архитектор по факту реализации.
|
||||
|
||||
tests:
|
||||
# --- Secret-scanning (FR-1 / AC-1..AC-3) ---
|
||||
- id: TC-01
|
||||
type: unit
|
||||
description: "Подсаженный тестовый секрет в diff -> вердикт FAIL, secrets_found>=1, причина называет находку."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-02
|
||||
type: unit
|
||||
description: "Чистая ветка без секретов -> вердикт PASS, secrets_found=0."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-03
|
||||
type: unit
|
||||
description: "Совпадение из аллоулиста (плейсхолдер .env.example / фикстура) НЕ даёт FAIL."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
|
||||
# --- Dependency audit + пороги (FR-2 / AC-4..AC-7) ---
|
||||
- id: TC-04
|
||||
type: unit
|
||||
description: "CVE уровня HIGH/CRITICAL при пороге HIGH -> вклад в FAIL, deps_blocking>=1."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-05
|
||||
type: unit
|
||||
description: "Только MEDIUM/LOW уязвимости -> PASS, deps_warning>=1, находки в теле артефакта."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-06
|
||||
type: unit
|
||||
description: "Конфиг порога: severity=CRITICAL делает HIGH-CVE warning; severity=HIGH делает её блоком."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-07
|
||||
type: unit
|
||||
description: "Недоступный CVE-фид -> детерминированный degrade по политике ADR (дефолт fail-open + warning), без исключения и без ложного FAIL."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
|
||||
# --- Вердикт / парсер frontmatter (FR-3 / AC-8..AC-10) ---
|
||||
- id: TC-08
|
||||
type: unit
|
||||
description: "Вердикт читается ТОЛЬКО из YAML-frontmatter; проза PASS/FAIL в теле не влияет; negative-токен авторитетен."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-09
|
||||
type: unit
|
||||
description: "Нет frontmatter / битый YAML / нет поля security_status -> (False, reason) (fail-closed на чтении)."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-10
|
||||
type: unit
|
||||
description: "Артефакт 17-security-report.md создаётся с валидным frontmatter (security_status, secrets_found, deps_blocking, deps_warning) и телом-списком."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
|
||||
# --- never-raise / таймаут / условность (FR-5/FR-6 / AC-14..AC-17) ---
|
||||
- id: TC-11
|
||||
type: unit
|
||||
description: "Отсутствие бинаря сканера / внутреннее исключение -> (False, reason), исключение не пробрасывается (never-raise)."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-12
|
||||
type: unit
|
||||
description: "Превышение ORCH_SECURITY_SCAN_TIMEOUT_S -> корректное прерывание и детерминированный вердикт, без зависания."
|
||||
module: tests/test_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-13
|
||||
type: unit
|
||||
description: "check_security_gate: не-self репо при пустом scope -> (True, 'security-gate N/A for <repo>') мгновенно."
|
||||
module: tests/test_qg_security.py
|
||||
expected: PASS
|
||||
- id: TC-14
|
||||
type: unit
|
||||
description: "check_security_gate: ORCH_SECURITY_GATE_ENABLED=false -> no-op pass (True)."
|
||||
module: tests/test_qg_security.py
|
||||
expected: PASS
|
||||
- id: TC-15
|
||||
type: unit
|
||||
description: "Новый чек зарегистрирован в QG_CHECKS и корректно диспетчеризуется _run_qg."
|
||||
module: tests/test_qg_security.py
|
||||
expected: PASS
|
||||
|
||||
# --- Откат / retry в stage_engine (FR-4 / AC-11..AC-13) ---
|
||||
- id: TC-16
|
||||
type: integration
|
||||
description: "security_status FAIL -> advance_stage откатывает на development, enqueue developer, Plane-коммент + notify_qg_failure."
|
||||
module: tests/test_stage_engine_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-17
|
||||
type: integration
|
||||
description: "task_desc перезапущенного developer содержит дословную причину находок (ORCH-046-паттерн), не только ссылку."
|
||||
module: tests/test_stage_engine_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-18
|
||||
type: integration
|
||||
description: "После MAX_DEVELOPER_RETRIES (3) -> set_issue_blocked + Telegram-алерт; бесконечного отскока нет."
|
||||
module: tests/test_stage_engine_security_gate.py
|
||||
expected: PASS
|
||||
- id: TC-19
|
||||
type: integration
|
||||
description: "security_status PASS -> advance_stage продвигает конвейер штатно (без отката, без шумных нотификаций)."
|
||||
module: tests/test_stage_engine_security_gate.py
|
||||
expected: PASS
|
||||
|
||||
# --- Инварианты / интеграция (BR-7/BR-12 / AC-18..AC-19) ---
|
||||
- id: TC-20
|
||||
type: integration
|
||||
description: "При варианте 'под-гейт ребра' STAGE_TRANSITIONS не изменён; существующие тесты стадий/гейтов остаются зелёными."
|
||||
module: tests/test_stages.py
|
||||
expected: PASS
|
||||
- id: TC-21
|
||||
type: integration
|
||||
description: "Гейт не вызывает деплой-хук/рестарт прод-контейнера (self-hosting safety)."
|
||||
module: tests/test_stage_engine_security_gate.py
|
||||
expected: PASS
|
||||
235
docs/work-items/ORCH-022/06-adr/ADR-001-security-gate.md
Normal file
235
docs/work-items/ORCH-022/06-adr/ADR-001-security-gate.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# ADR-001: Security-гейт — secret-scanning + dependency audit перед мержем
|
||||
|
||||
- **Статус:** Accepted (proposed → принято архитектором ORCH-022)
|
||||
- **Дата:** 2026-06-07
|
||||
- **Задача:** ORCH-022
|
||||
- **Связанный global ADR:** `docs/architecture/adr/adr-0012-security-gate.md`
|
||||
- **Источники:** `01-brd.md` (BR-1..BR-14), `02-trz.md` (FR-1..FR-7, §4 варианты, §7 инварианты),
|
||||
`03-acceptance-criteria.md` (AC-1..AC-21).
|
||||
|
||||
---
|
||||
|
||||
## Контекст
|
||||
|
||||
Оркестратор автономен: `developer`-агент пишет код без человека-фильтра. Перед слиянием
|
||||
ветки задачи в `main` нет автоматической проверки на утёкший секрет (ключ/токен/пароль/
|
||||
приватный ключ) и на уязвимую зависимость (известная CVE). Для self-hosting это особенно
|
||||
опасно: один общий прод-инстанс обслуживает все проекты с общей БД — секрет или CVE,
|
||||
просочившийся через одну задачу, попадает в прод всех проектов (CLAUDE.md §self-hosting, §8).
|
||||
|
||||
Конвейер уже содержит линию детерминированных страховок на ребре `deploy-staging → deploy`
|
||||
(непосредственно перед фактическим мержем PR в `main`, который делает `deployer` в начале
|
||||
стадии `deploy`):
|
||||
|
||||
- **merge-gate** (ORCH-043, `check_branch_mergeable`) — догон `main` + re-test + сериализация;
|
||||
- **image-freshness** (ORCH-058, `check_staging_image_fresh`) — провенанс staging-образа.
|
||||
|
||||
Оба построены по одному паттерну: **leaf-модуль чистой логики (never-raise) + тонкая обёртка
|
||||
в `QG_CHECKS` + врезка-обработчик `_handle_*` в `advance_stage`**, с условным раскатом
|
||||
(`*_enabled` + `*_repos`, реально только для self-hosting при пустом scope) и откатом на
|
||||
`development` с developer-retry (cap `MAX_DEVELOPER_RETRIES = 3`).
|
||||
|
||||
Открытые вопросы BRD §8 / TRZ §4, требующие решения архитектора:
|
||||
1. Размещение гейта в пайплайне (review / merge-edge / CI-job).
|
||||
2. Где запускается сканер (CI-job через `check_ci_green` / отдельный QG-чек).
|
||||
3. Degrade при недоступном CVE-фиде (fail-open / fail-closed).
|
||||
4. Выбор инструментов (gitleaks/trufflehog; pip-audit/trivy).
|
||||
|
||||
---
|
||||
|
||||
## Решение
|
||||
|
||||
### Р-1. Размещение — Вариант M (под-гейт ребра `deploy-staging → deploy`), ПЕРВЫМ среди edge-под-гейтов
|
||||
|
||||
Security-гейт реализуется как **детерминированный под-гейт того же ребра**
|
||||
`deploy-staging → deploy`, что merge-gate и image-freshness, и исполняется **ПЕРВЫМ** —
|
||||
**ДО** merge-gate. `STAGE_TRANSITIONS` **не меняется** (триггер — то же событие «staging-
|
||||
deployer завершился»; инвариант TRZ §7.1).
|
||||
|
||||
Порядок врезок в `advance_stage` (блок `current_stage == "deploy-staging"`):
|
||||
|
||||
```
|
||||
check_staging_status (PASS, существующий QG стадии)
|
||||
→ security-gate (НОВЫЙ, _handle_security_gate) ← первым
|
||||
→ merge-gate (_handle_merge_gate)
|
||||
→ image-freshness (_handle_image_freshness)
|
||||
→ Phase A (self-deploy approve)
|
||||
```
|
||||
|
||||
**Почему merge-edge, а не review (Вариант R):**
|
||||
- BRD-требование «перед слиянием в `main`» удовлетворяют оба, но на review-стадии diff
|
||||
может разойтись с тем, что реально вольётся в `main` (параллельная задача двигает `main`
|
||||
вперёд между review и merge). Merge-edge — последняя точка перед фактическим мержем.
|
||||
- Переиспользуется готовая машинерия отката/retry/нотификаций edge-под-гейтов
|
||||
(минимальный blast-radius, инвариант TRZ §7).
|
||||
|
||||
**Почему ПЕРВЫМ (до merge-gate), а не после image-freshness:**
|
||||
- **Дёшево фейлить.** merge-gate (rebase + re-test, минуты) и image-freshness (docker
|
||||
rebuild, до 1200с) — дорогие. Нет смысла гонять их на ветке с секретом/CVE.
|
||||
- **Корректность для секретов.** Секрет живёт в собственных коммитах ветки;
|
||||
rebase онто `main` его не добавляет и не убирает → скан диапазона `origin/main..HEAD`
|
||||
до rebase ловит ровно те коммиты, что попадут в `main`.
|
||||
- **Анти-петля для зависимостей.** Аудит ветки **до** rebase оценивает то, что вносит
|
||||
ИМЕННО эта задача (её `requirements.txt`/diff), а не уязвимость, которую притащил в
|
||||
ветку обновившийся `main`. Аудит после rebase «обвинял» бы задачу в чужой (main'овой)
|
||||
CVE → ложный откат `→ development` → петля (прецедент ORCH-061). Скан до rebase этого
|
||||
избегает.
|
||||
- **Проще, чем image-freshness.** Гейт исполняется ДО захвата merge-lease → при FAIL
|
||||
**lease освобождать не нужно** (в отличие от `_handle_image_freshness`). Чистый откат.
|
||||
|
||||
**Почему не CI-job (Вариант C):** пороги severity, warning-vs-block, аллоулист и
|
||||
машиночитаемый артефакт-вердикт плохо выражаются одним статусом коммита Gitea; путь
|
||||
коуплится с CI-раннером. Отклонено для v1; оставлено как точка расширения (BR-14).
|
||||
|
||||
### Р-2. Инструменты
|
||||
|
||||
- **Secret-scanning — `gitleaks`.** Полностью **offline** (без сетевого фида → гарантия
|
||||
«секрет всегда блокирует» не зависит от сети, BR-2), один статический бинарь,
|
||||
детерминированный, конфиг + аллоулист в репо (`.gitleaks.toml`, BR-13), поддержка
|
||||
`--log-opts="origin/main..HEAD"` (скан диапазона), JSON-отчёт, exit-code контракт
|
||||
(0 = чисто, 1 = найдены секреты, ≥2 = ошибка инструмента). Бинарь устанавливается в
|
||||
`Dockerfile` (Go-бинарь, не pip-пакет) — см. `07-infra-requirements.md`.
|
||||
- **Dependency audit — `pip-audit`.** Python-native (v1-стек — сам оркестратор, Python),
|
||||
читает `requirements.txt`, источник advisory — OSV/PyPI, JSON-выход, ставится через
|
||||
`requirements.txt`. trivy/trufflehog отклонены как тяжелее/контейнер-ориентированные для
|
||||
v1-цели «Python-only» (A3).
|
||||
|
||||
Конкретные инструменты — деталь реализации; контракт гейта (вход: repo/branch/wi,
|
||||
выход: `(bool, reason)` + артефакт) от них не зависит, заменяемы за leaf-модулем.
|
||||
|
||||
### Р-3. Degrade при недоступном CVE-фиде — **fail-open + громкий warning** (дефолт)
|
||||
|
||||
`pip-audit` требует сети (OSV/PyPI advisory DB). Недоступность фида **по умолчанию**:
|
||||
- **fail-open**: dep-audit не даёт FAIL по причине недоступности фида (иначе — ложные
|
||||
откаты `→ development` → петля при сетевых проблемах прод-инстанса, прецедент ORCH-061);
|
||||
- **громко**: в артефакте `deps_audit_degraded: true`, лог `logger.warning`, Telegram-алерт.
|
||||
- **Секреты не деградируют:** gitleaks offline → гарантия BR-2 безусловна даже при
|
||||
отсутствии сети. Деградирует ТОЛЬКО dep-audit.
|
||||
- **Конфигурируемо:** флаг `security_dep_audit_fail_closed` (дефолт `false`) позволяет
|
||||
Owner'у переключить на fail-closed (недоступность фида → FAIL) без редеплоя кода.
|
||||
|
||||
Это разделяет две гарантии: «нет секрета в прод» — **безусловная**; «нет известной CVE» —
|
||||
**best-effort при доступности фида**. Закреплено в acceptance (AC-7).
|
||||
|
||||
### Р-4. Пороги классификации (A4, BR-10)
|
||||
|
||||
- **Секреты:** любой подтверждённый (не из аллоулиста) секрет → **вклад в FAIL** (всегда
|
||||
блок; флаг `security_secrets_block`, дефолт `true`).
|
||||
- **Зависимости:** severity ≥ `security_dep_block_severity` (дефолт `HIGH`) → **вклад в
|
||||
FAIL** (`deps_blocking`); ниже порога (`MEDIUM`/`LOW`) → **warning** (`deps_warning`,
|
||||
не блокирует, фиксируется в теле).
|
||||
- **Severity = UNKNOWN** (OSV/advisory без CVSS — частый случай pip-audit): трактуется как
|
||||
**ниже порога → warning**, никогда не авто-блок (анти-петля). Логируется.
|
||||
|
||||
### Р-5. Артефакт и вердикт (FR-3, BR-6, канон проекта)
|
||||
|
||||
- Новый артефакт **`17-security-report.md`** (следующий свободный номер; финализировано).
|
||||
- YAML-frontmatter:
|
||||
```
|
||||
---
|
||||
security_status: PASS # PASS | FAIL
|
||||
secrets_found: 0
|
||||
deps_blocking: 0
|
||||
deps_warning: 2
|
||||
deps_audit_degraded: false
|
||||
---
|
||||
```
|
||||
Тело — человекочитаемый список находок (секреты: файл/правило/маскированное совпадение;
|
||||
CVE: пакет/версия/идентификатор/severity).
|
||||
- **Единый источник истины:** гейт вычисляет находки → пишет артефакт → **читает вердикт
|
||||
обратно через `parse_security_status(content)`** (frontmatter-парсер по образцу
|
||||
`_parse_deploy_status`/`_parse_staging_status`) → возвращает этот вердикт. Так возвращаемый
|
||||
`(bool, reason)` гарантированно == frontmatter артефакта (канон «машинный вердикт — только
|
||||
из YAML-frontmatter, никогда из прозы», AC-8). Negative-токен (`FAIL`) авторитетен.
|
||||
- Битый/отсутствующий frontmatter / нет поля `security_status` → `(False, reason)` —
|
||||
fail-closed на чтении вердикта (AC-9).
|
||||
|
||||
### Р-6. Поведение красного гейта (FR-4, BR-5)
|
||||
|
||||
`security_status: FAIL` → врезка `_handle_security_gate` (по образцу
|
||||
`_handle_image_freshness`, но БЕЗ работы с lease — гейт до его захвата):
|
||||
- `update_task_stage(development)` + `enqueue_job("developer", …)`;
|
||||
- retry-счётчик — **существующий** `_developer_retry_count` (общий с merge/freshness;
|
||||
без новой колонки, TRZ §6); cap `MAX_DEVELOPER_RETRIES = 3` → при исчерпании
|
||||
`set_issue_blocked` + Telegram;
|
||||
- `task_desc` несёт **дословную причину** (какие секреты/файлы, какие пакеты/CVE/severity)
|
||||
по образцу ORCH-046 — не только ссылку на артефакт (AC-12);
|
||||
- `notify_qg_failure` + Plane-коммент (наблюдаемость BR-11).
|
||||
|
||||
PASS → `return False` из обработчика → `advance_stage` идёт к merge-gate (тишина, без шума).
|
||||
|
||||
### Р-7. Условный раскат и устойчивость (FR-5/FR-6)
|
||||
|
||||
- `check_security_gate(repo, work_item_id, branch)` в `QG_CHECKS`; обёртка делегирует в
|
||||
`src/security_gate.py` (ленивый импорт во избежание цикла — по образцу
|
||||
`_check_staging_image_fresh`).
|
||||
- Условность: `security_gate_enabled=False` → `(True, "security-gate disabled")`;
|
||||
`security_gate_repos` (CSV) пусто → реально только `is_self_hosting_repo` → прочие репо
|
||||
`(True, "security-gate N/A for <repo>")` (AC-14/AC-15).
|
||||
- **never-raise** (двойной guard как `check_branch_mergeable`): любая ошибка (нет бинаря,
|
||||
таймаут, исключение) → `(False, reason)`, исключение не уходит в `advance_stage` (AC-16).
|
||||
- Таймаут сканирования `security_scan_timeout_s` (дефолт 300) на каждый внешний вызов
|
||||
(`subprocess … timeout=`) — превышение → детерминированный degrade-вердикт (AC-17).
|
||||
|
||||
### Р-8. Self-hosting safety (инвариант TRZ §7.5, AC-19)
|
||||
|
||||
Гейт **только читает/сканирует** (git, gitleaks, pip-audit, запись артефакта). Не вызывает
|
||||
деплой-хук, не рестартит и не трогает прод-контейнер (8500/8501).
|
||||
|
||||
---
|
||||
|
||||
## Точки касания (для developer; reviewer проверяет полноту — AC-20)
|
||||
|
||||
| Модуль | Изменение |
|
||||
|--------|-----------|
|
||||
| `src/security_gate.py` (**новый leaf**) | `security_gate_applies`, `scan_secrets`, `audit_dependencies`, `classify_severity`, `compute_verdict`, `write_security_report`, `parse_security_status`, `check_security_gate`. never-raise, fail-closed на чтении вердикта. По образцу `image_freshness.py`. |
|
||||
| `src/qg/checks.py` | `check_security_gate` (тонкая обёртка, ленивый импорт) + регистрация в `QG_CHECKS`. |
|
||||
| `src/stage_engine.py` | `_handle_security_gate(...)` + врезка ПЕРВОЙ в блоке `current_stage == "deploy-staging"` (до `_handle_merge_gate`). FAIL → откат на `development`. never-raise. **`STAGE_TRANSITIONS` НЕ меняется.** |
|
||||
| `src/config.py` | `security_gate_enabled` (True), `security_gate_repos` (""), `security_dep_block_severity` ("HIGH"), `security_scan_timeout_s` (300), `security_dep_audit_fail_closed` (False), `security_secrets_block` (True) — с docstring по образцу ORCH-043/058. |
|
||||
| `Dockerfile` | Установка `gitleaks` (release-бинарь). |
|
||||
| `requirements.txt` | `pip-audit`. |
|
||||
| `.gitleaks.toml` (**новый, корень репо**) | Конфиг правил + аллоулист (`.env.example`-плейсхолдеры, тест-фикстуры) — BR-13. |
|
||||
| `.openclaw/agents/developer.md` | (Опц.) краткая инструкция про устранение security-находок при заворотах. |
|
||||
| `tests/` | `test_security_gate.py`, `test_qg_security.py`, `test_stage_engine_security_gate.py` (см. `04-test-plan.yaml`). |
|
||||
| **Документация** | `CLAUDE.md` (артефакт 17-…), `docs/architecture/README.md` (таблица гейтов + реестр QG + раздел), `CHANGELOG.md`, `.env.example` (`ORCH_SECURITY_*`), global `adr-0012`. |
|
||||
|
||||
---
|
||||
|
||||
## Альтернативы (отклонены)
|
||||
|
||||
- **Вариант R (review-стадия):** раньше/дешевле, но diff может разойтись с тем, что
|
||||
вольётся в `main`; merge-edge уже закрывает «последнюю страховку».
|
||||
- **Вариант C (CI-job через `check_ci_green`):** пороги/severity/аллоулист/артефакт плохо
|
||||
выражаются статусом коммита; коуплинг с CI-раннером. → точка расширения BR-14.
|
||||
- **fail-closed dep-audit по умолчанию:** ложные откаты при сетевых сбоях → петля. →
|
||||
только опционально через флаг.
|
||||
- **Аудит после rebase (как анкер image-freshness):** обвиняет задачу в CVE из `main` →
|
||||
петля. → скан ветки ДО merge-gate.
|
||||
- **Новая стадия `security`:** «пустая» стадия без агента не имеет триггера (как
|
||||
отклонено в ORCH-043). → под-гейт ребра.
|
||||
- **Новая колонка retry в БД:** не нужна — переиспользуем `_developer_retry_count`.
|
||||
|
||||
---
|
||||
|
||||
## Последствия
|
||||
|
||||
**Плюсы.** Структурно невозможно тихо влить секрет (безусловно) или известную CVE
|
||||
(best-effort) в `main`/прод автономной системы. Самоприменение CLAUDE.md §8. Минимальный
|
||||
blast-radius: `STAGE_TRANSITIONS`/схема БД не меняются, переиспользован готовый паттерн.
|
||||
|
||||
**Минусы / плата.** Ещё один «скрытый» под-гейт ребра (нет в `STAGE_TRANSITIONS`).
|
||||
Добавлены внешние инструменты (gitleaks-бинарь в образ, pip-audit в зависимости). Время
|
||||
сканирования добавляется к каждому прогону (ограничено таймаутом). Dep-audit best-effort
|
||||
при сетевых сбоях (осознанный компромисс против петли). v1 — Python-only (A3); мульти-стек
|
||||
и SAST — follow-up WI (BR-14).
|
||||
|
||||
**Раскат.** Сквозное изменение конвейера (новый QG + новый edge-под-гейт) → лейбл
|
||||
`arch:major-change`. Прод-деплой ORCH-022 — строго через staging-гейт (8501), без рестарта
|
||||
прод-контейнера в рамках задачи (self-hosting safety).
|
||||
|
||||
## Связи
|
||||
|
||||
adr-0006 (merge-gate — паттерн edge-под-гейта/отката), adr-0008 (image-freshness —
|
||||
условность/never-raise/fail-closed), adr-0003 (`is_self_hosting_repo` — образец условности),
|
||||
adr-0009/ORCH-061 (анти-петля ложных FAIL), ORCH-046 (дословный reason в `task_desc`),
|
||||
ORCH-9/15 (мульти-стек — будущая зависимость).
|
||||
56
docs/work-items/ORCH-022/07-infra-requirements.md
Normal file
56
docs/work-items/ORCH-022/07-infra-requirements.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 07 — Инфраструктурные требования: Security-гейт (ORCH-022)
|
||||
|
||||
См. `06-adr/ADR-001-security-gate.md` (Р-2, Р-3, Р-8). Топология не меняется (один сервер
|
||||
mva154, Docker Compose). Новые требования — только инструменты сканирования и сетевой доступ
|
||||
к CVE-фиду.
|
||||
|
||||
## I-1. Бинарь `gitleaks` в образе
|
||||
- **Что:** статический Go-бинарь `gitleaks` (secret-scanning), устанавливается в `Dockerfile`
|
||||
(НЕ pip-пакет). Зафиксировать версию (pinned release) для детерминизма.
|
||||
- **Почему в образе, а не на хосте:** гейт исполняется внутри контейнера оркестратора
|
||||
(`advance_stage`); сканируется per-task worktree, смонтированный в контейнер.
|
||||
- **Оффлайн:** gitleaks не требует сети (правила локальны) → гарантия «секрет всегда
|
||||
блокирует» (BR-2) не зависит от доступности интернета.
|
||||
- **Контракт exit-кодов:** 0 = чисто, 1 = найдены секреты, ≥2 = ошибка инструмента
|
||||
(≥2 → never-raise degrade-вердикт гейта).
|
||||
|
||||
## I-2. `pip-audit` в зависимостях
|
||||
- **Что:** Python-пакет `pip-audit` (dependency audit), добавляется в `requirements.txt`
|
||||
(pinned-версия).
|
||||
- **Источник advisory:** OSV / PyPI advisory DB — **требует сетевого доступа** (исходящий
|
||||
HTTPS к OSV/PyPI).
|
||||
- **Цель v1:** аудит `requirements.txt` корня репо (Python-стек, A3). Мульти-стек — follow-up.
|
||||
|
||||
## I-3. Сетевой доступ к CVE-фиду (degrade-политика)
|
||||
- **Требование:** исходящий HTTPS из прод-контейнера к OSV/PyPI advisory.
|
||||
- **При недоступности (Р-3):** **fail-open + громкий warning** по умолчанию — dep-audit не
|
||||
краснит гейт из-за сетевого сбоя (анти-петля ORCH-061); фиксируется
|
||||
`deps_audit_degraded: true` + Telegram + лог. Флаг `security_dep_audit_fail_closed`
|
||||
(дефолт `false`) — для перевода в строгий режим без редеплоя кода.
|
||||
- **Секреты не зависят от сети** (I-1) — критическая гарантия безусловна.
|
||||
|
||||
## I-4. Конфиг-файлы в репозитории (версионируемые, BR-13)
|
||||
- `.gitleaks.toml` (корень репо): правила + аллоулист заведомо-безопасных совпадений
|
||||
(плейсхолдеры `.env.example`, тест-фикстуры). Версионируется, ревьюится как код.
|
||||
|
||||
## I-5. Env-флаги (`.env.example` + хост `.env`/`.env.staging`)
|
||||
| Переменная | Дефолт | Назначение |
|
||||
|------------|--------|-----------|
|
||||
| `ORCH_SECURITY_GATE_ENABLED` | `true` | глобальный kill-switch |
|
||||
| `ORCH_SECURITY_GATE_REPOS` | `` (пусто) | CSV scope; пусто → только self-hosting |
|
||||
| `ORCH_SECURITY_DEP_BLOCK_SEVERITY` | `HIGH` | порог блокировки зависимостей |
|
||||
| `ORCH_SECURITY_SCAN_TIMEOUT_S` | `300` | таймаут каждого внешнего вызова сканера |
|
||||
| `ORCH_SECURITY_DEP_AUDIT_FAIL_CLOSED` | `false` | строгий режим при недоступном фиде |
|
||||
| `ORCH_SECURITY_SECRETS_BLOCK` | `true` | секреты блокируют (всегда по дефолту) |
|
||||
|
||||
Секреты-значения в гит НЕ коммитятся (CLAUDE.md §8) — только дефолты в `.env.example`.
|
||||
|
||||
## I-6. Ресурсы и тайминги
|
||||
- Время сканирования добавляется к каждому прогону задачи на ребре `deploy-staging → deploy`,
|
||||
ограничено `ORCH_SECURITY_SCAN_TIMEOUT_S` (по образцу `merge_retest_timeout_s`).
|
||||
- Гейт исполняется ДО merge-gate/image-freshness (дёшево фейлить до дорогих rebase/rebuild).
|
||||
|
||||
## I-7. Self-hosting safety (инвариант)
|
||||
Гейт **только читает/сканирует** (git, gitleaks, pip-audit, запись артефакта). Не вызывает
|
||||
деплой-хук, не рестартит/не трогает прод-контейнер (8500/8501). Прод-деплой ORCH-022 — строго
|
||||
через staging-гейт (8501).
|
||||
26
docs/work-items/ORCH-022/08-data-requirements.md
Normal file
26
docs/work-items/ORCH-022/08-data-requirements.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 08 — Требования к схеме БД: Security-гейт (ORCH-022)
|
||||
|
||||
## Решение: схема БД НЕ меняется
|
||||
|
||||
Миграций нет. Обоснование (соответствует TRZ §6 и паттерну edge-под-гейтов ORCH-043/058):
|
||||
|
||||
1. **Вердикт гейта — артефакт-файл** `17-security-report.md` (YAML-frontmatter), как
|
||||
`14-deploy-log.md` / `15-staging-log.md`. Не хранится в БД.
|
||||
2. **Состояние/идемпотентность** — детерминированная пересборка вердикта при каждом тике
|
||||
(гейт чистый, без долгоживущего состояния между прогонами); sentinel-файлы НЕ требуются
|
||||
(в отличие от deploy-state/post-deploy-state — там асинхронный self-restart).
|
||||
3. **Retry-счётчик** — переиспользуется существующий `_developer_retry_count(task_id)`
|
||||
(подсчёт по `jobs`/`agent_runs`), общий с merge-gate/image-freshness. **Новой колонки
|
||||
`security_retry` НЕ вводим** (TRZ §6: предпочесть подсчёт по `jobs`/`agent_runs`). Это
|
||||
корректно: security-FAIL, как merge/freshness-FAIL, откатывает на `development` и
|
||||
запускает developer — он и есть единица retry; общий cap=3 защищает от петли.
|
||||
|
||||
## Используемые существующие таблицы (без изменений)
|
||||
- `tasks` — стадия задачи (`update_task_stage` при откате на `development`).
|
||||
- `jobs` — enqueue `developer` при FAIL; основа `_developer_retry_count`.
|
||||
- `agent_runs` — usage/duration; основа подсчёта retry.
|
||||
|
||||
## Что НЕ делаем
|
||||
- Не добавляем таблицу findings/CVE-журнала (история находок — в артефактах per-task; петля
|
||||
уроков ORCH-8 читает артефакт).
|
||||
- Не добавляем колонок в `tasks`/`jobs`.
|
||||
16
docs/work-items/ORCH-022/10-tech-risks.md
Normal file
16
docs/work-items/ORCH-022/10-tech-risks.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 10 — Технические риски: Security-гейт (ORCH-022)
|
||||
|
||||
| ID | Риск | Вероятность / Влияние | Митигация (заложена в ADR-001) |
|
||||
|----|------|----------------------|-------------------------------|
|
||||
| R-1 | **Ложные срабатывания → петля отката** `→ development` (прецедент ORCH-061 staging-loop). | Средн. / Выс. | Аллоулист `.gitleaks.toml` (BR-13); cap `MAX_DEVELOPER_RETRIES=3` → эскалация (`set_issue_blocked`+Telegram); конфигурируемый порог severity; kill-switch; UNKNOWN-severity → warning, не блок. |
|
||||
| R-2 | **Недоступность CVE-фида** даёт ложный красный/исключение. | Средн. / Выс. | fail-open + громкий warning по умолчанию (Р-3); `deps_audit_degraded:true`; флаг `security_dep_audit_fail_closed` для строгого режима. Секреты offline → не затронуты. |
|
||||
| R-3 | **Скан вешает worker-слот** (зависший gitleaks/pip-audit) → стоит конвейер всех проектов (общий инстанс, `max_concurrency`). | Низк. / Выс. | `security_scan_timeout_s` (300) на каждый внешний вызов; never-raise degrade-вердикт; гейт ПЕРВЫМ на ребре (фейлит до дорогих rebase/rebuild). |
|
||||
| R-4 | **Исключение гейта роняет `advance_stage`** → встаёт движок. | Низк. / Выс. | Двойной never-raise guard (внешний+внутренний) как `check_branch_mergeable`; AC-16/TC-11. |
|
||||
| R-5 | **Скан после rebase обвиняет задачу в CVE из `main`** → петля. | — (устранён дизайном) | Гейт исполняется ДО merge-gate (скан ветки до rebase); Р-1. |
|
||||
| R-6 | **Отсутствие бинаря `gitleaks` в образе** (забыт в Dockerfile) → гейт всегда degrade. | Низк. / Средн. | Установка в Dockerfile (I-1), pinned-версия; TC-11 (нет бинаря → `(False,reason)`, never-raise); проверяется на staging (8501) до прода. |
|
||||
| R-7 | **pip-audit без severity (UNKNOWN)** → либо ложный блок, либо пропуск. | Средн. / Средн. | UNKNOWN → warning (не блок), логируется; осознанный анти-петля компромисс; ужесточение — follow-up. |
|
||||
| R-8 | **Self-hosting: гейт трогает прод** (рестарт/деплой). | — (запрещено дизайном) | Гейт только читает/сканирует; AC-19/TC-21; прод-деплой ORCH-022 — через staging-гейт. |
|
||||
| R-9 | **Drift вердикта vs артефакта** (возврат ≠ frontmatter). | Низк. / Средн. | Единый источник: гейт пишет артефакт → читает обратно через `parse_security_status` → возвращает (Р-5); AC-8. |
|
||||
| R-10 | **Регресс существующих гейтов/стадий** (сломан `QG_CHECKS`/`STAGE_TRANSITIONS`). | Низк. / Выс. | `STAGE_TRANSITIONS` не меняется; новый чек — аддитивно в реестр; полный прогон `tests/` (TC-20); staging-гейт перед прод. |
|
||||
| R-11 | **v1 Python-only** — секреты/CVE в не-Python стеке (JS/Android) не ловятся. | — (вне scope v1, A3) | Условность scope; точка расширения мульти-стек/SAST (BR-14); зависимость ORCH-9/15 зафиксирована. |
|
||||
| R-12 | **Стоимость времени** на каждом прогоне задачи. | Низк. / Низк. | Таймаут; гейт первым (ранний выход); только self-hosting по умолчанию. |
|
||||
74
docs/work-items/ORCH-022/12-review.md
Normal file
74
docs/work-items/ORCH-022/12-review.md
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
type: review
|
||||
work_item_id: ORCH-022
|
||||
verdict: APPROVED
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Review ORCH-022
|
||||
|
||||
## Summary
|
||||
Security-гейт (secret-scanning `gitleaks` + dependency audit `pip-audit`) реализован как
|
||||
детерминированный под-гейт ребра `deploy-staging → deploy`, исполняемый ПЕРВЫМ среди
|
||||
edge-под-гейтов — в точности по ADR-001 (Вариант M) и эталонному паттерну соседей
|
||||
(merge-gate ORCH-043 / image-freshness ORCH-058): leaf-модуль `src/security_gate.py`
|
||||
(never-raise) + тонкая обёртка `check_security_gate` в `QG_CHECKS` (lazy-import, нет цикла)
|
||||
+ врезка `_handle_security_gate` ПЕРВОЙ в блоке `current_stage == "deploy-staging"`.
|
||||
`STAGE_TRANSITIONS` и схема БД не тронуты. Все 772 теста зелёные (25 из них —
|
||||
security-специфичные: `test_security_gate.py`, `test_qg_security.py`,
|
||||
`test_stage_engine_security_gate.py`). Документация обновлена полностью и в этом же PR.
|
||||
|
||||
### Соответствие ТЗ (02-trz)
|
||||
- FR-1 secret-scan offline `origin/main..HEAD`, любой секрет вне аллоулиста → FAIL ✓
|
||||
- FR-2 dep-audit по severity (`HIGH` дефолт), MEDIUM/LOW/UNKNOWN → warning ✓
|
||||
- FR-3 машинный вердикт ТОЛЬКО из frontmatter `17-security-report.md`, negative-токен
|
||||
авторитетен, write→read-back (единый источник истины) ✓
|
||||
- FR-4 FAIL → откат на `development` + developer-retry (cap 3) + `task_desc` с дословными
|
||||
находками (ORCH-046) ✓
|
||||
- FR-5 условность `security_gate_enabled` / `security_gate_repos` (пусто → self-hosting) ✓
|
||||
- FR-6 never-raise + таймаут `security_scan_timeout_s` ✓
|
||||
- FR-7 наблюдаемость (Telegram при degraded/FAIL, лог при PASS) ✓
|
||||
- §6 без миграций БД, §7 инварианты соблюдены (STAGE_TRANSITIONS/QG_CHECKS консистентны,
|
||||
gate не деплоит/не рестартит прод) ✓
|
||||
|
||||
### Соответствие ADR (06-adr/ADR-001 + global adr-0012)
|
||||
Р-1 (размещение ПЕРВЫМ, до merge-gate, до захвата merge-lease → lease не освобождается),
|
||||
Р-2 (gitleaks pinned Go-бинарь в Dockerfile, pip-audit в requirements), Р-3 (fail-open
|
||||
degrade + флаг `security_dep_audit_fail_closed`), Р-4 (пороги, UNKNOWN→warning), Р-5
|
||||
(артефакт + read-back), Р-6 (откат/cap/эскалация), Р-7 (lazy-import, double-guard
|
||||
never-raise), Р-8 (self-hosting safety) — все реализованы как описано.
|
||||
|
||||
### Критерии приёмки (03)
|
||||
AC-1..AC-21 покрыты тестами TC-01..TC-21 (incl. rollback TC-16, verbatim task_desc TC-17,
|
||||
cap+blocked TC-18, PASS-advance TC-19, no-deploy-on-FAIL TC-21). AC-20 (документация) —
|
||||
подтверждён ниже.
|
||||
|
||||
## Findings
|
||||
|
||||
### P0 — Blocker
|
||||
- нет
|
||||
|
||||
### P1 — Must fix
|
||||
- нет
|
||||
|
||||
### P2 — Should fix
|
||||
- нет
|
||||
|
||||
### P3 — Nice-to-have
|
||||
- Глобальный `docs/architecture/adr/adr-0012-security-gate.md` помечен `Статус: proposed`,
|
||||
тогда как per-WI `06-adr/ADR-001` — `Accepted`. Косметическая рассинхронизация статуса,
|
||||
на функциональность/гейты не влияет.
|
||||
|
||||
## Документация
|
||||
Обновлена в том же PR (AC-20, CLAUDE.md §6 соблюдён):
|
||||
- `CLAUDE.md` — раздел «Артефакты задачи» (добавлен `17-security-report.md`) + строка о
|
||||
машинных вердиктах (`security_status:`).
|
||||
- `docs/architecture/README.md` — реестр `QG_CHECKS` (`check_security_gate (ORCH-022)`),
|
||||
новый раздел «Security-гейт …», статусная сноска внизу.
|
||||
- `docs/architecture/adr/adr-0012-security-gate.md` — новый global ADR (+ per-WI ADR-001).
|
||||
- `CHANGELOG.md` — подробная запись в `[Unreleased] / Added`.
|
||||
- `.env.example` — все шесть `ORCH_SECURITY_*` с комментариями.
|
||||
- `Dockerfile` (pinned gitleaks), `requirements.txt` (pip-audit), `.gitleaks.toml` (корень,
|
||||
правила + аллоулист) — инфраструктура версионирована.
|
||||
|
||||
Статус: документация = golden source — синхронна с кодом. Замечаний нет.
|
||||
76
docs/work-items/ORCH-022/13-test-report.md
Normal file
76
docs/work-items/ORCH-022/13-test-report.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
type: test-report
|
||||
work_item_id: ORCH-022
|
||||
result: PASS
|
||||
---
|
||||
|
||||
# Test Report — ORCH-022
|
||||
|
||||
Security-гейт: secret-scanning (gitleaks) + dependency audit (pip-audit) как под-гейт
|
||||
ребра `deploy-staging → deploy`.
|
||||
|
||||
## Окружение
|
||||
- Python: 3.12.13
|
||||
- pytest: 8.3.3
|
||||
- Дата: 2026-06-07
|
||||
- Ветка: `feature/ORCH-022-security-secret-scanning`
|
||||
- Review verdict: APPROVED (`12-review.md`)
|
||||
|
||||
## Smoke test API (prod 8500, self-hosting — не трогаем контейнер)
|
||||
| Endpoint | Результат |
|
||||
|----------|-----------|
|
||||
| `GET /health` | `{"status":"ok","service":"orchestrator"}` — OK |
|
||||
| `GET /status` | OK (active task ORCH-022 в stage=testing виден) |
|
||||
| `GET /queue` | OK (counts/resilience/reconcile/reaper/post_deploy присутствуют) |
|
||||
|
||||
## Результаты (привязка к 04-test-plan.yaml)
|
||||
|
||||
| TC ID | Описание | Тест | Результат |
|
||||
|-------|----------|------|-----------|
|
||||
| TC-01 | Секрет в diff → FAIL, secrets_found>=1, причина называет находку | test_security_gate.py::test_tc01_secret_in_diff_fails | PASS |
|
||||
| TC-02 | Чистая ветка → PASS, secrets_found=0 | test_tc02_clean_branch_passes | PASS |
|
||||
| TC-03 | Аллоулист подавляет заведомо-безопасное | test_tc03_allowlisted_match_does_not_fail | PASS |
|
||||
| TC-04 | HIGH/CRITICAL CVE при пороге HIGH → FAIL, deps_blocking>=1 | test_tc04_high_cve_at_high_threshold_blocks | PASS |
|
||||
| TC-05 | Только MEDIUM/LOW → PASS, deps_warning>=1 | test_tc05_only_medium_low_warns_passes | PASS |
|
||||
| TC-06 | Конфиг порога severity влияет на классификацию | test_tc06_threshold_config_changes_classification | PASS |
|
||||
| TC-07 | Недоступный фид → детерминированный degrade (fail-open default / fail-closed strict) | test_tc07_degraded_feed_failopen_default_failclosed_strict | PASS |
|
||||
| TC-08 | Вердикт ТОЛЬКО из frontmatter; negative-токен авторитетен | test_tc08_verdict_only_from_frontmatter | PASS |
|
||||
| TC-09 | Нет/битый frontmatter → (False, reason) fail-closed | test_tc09_missing_or_broken_frontmatter_failclosed | PASS |
|
||||
| TC-10 | Артефакт 17-security-report.md с валидным frontmatter + телом | test_tc10_artifact_has_valid_frontmatter_and_body | PASS |
|
||||
| TC-11 | Нет бинаря / исключение → (False, reason), never-raise | test_tc11_missing_binary_failclosed_never_raises | PASS |
|
||||
| TC-12 | Таймаут → детерминированный fail-closed, без зависания | test_tc12_timeout_is_deterministic_failclosed | PASS |
|
||||
| TC-13 | Не-self репо при пустом scope → (True, N/A) мгновенно | test_qg_security.py::test_tc13_non_self_repo_empty_scope_is_na | PASS |
|
||||
| TC-14 | ORCH_SECURITY_GATE_ENABLED=false → no-op pass | test_tc14_disabled_is_noop_pass | PASS |
|
||||
| TC-15 | Зарегистрирован в QG_CHECKS и диспетчеризуется _run_qg | test_tc15_registered_in_qg_checks / test_tc15_dispatched_by_run_qg | PASS |
|
||||
| TC-16 | FAIL → откат на development, enqueue developer, notify_qg_failure | test_stage_engine_security_gate.py::test_tc16_fail_rolls_back_and_enqueues_developer | PASS |
|
||||
| TC-17 | task_desc несёт дословную причину (ORCH-046) | test_tc17_task_desc_has_verbatim_findings | PASS |
|
||||
| TC-18 | После MAX_DEVELOPER_RETRIES (3) → set_issue_blocked + Telegram | test_tc18_retry_cap_blocks_and_alerts | PASS |
|
||||
| TC-19 | PASS → штатное продвижение конвейера | test_tc19_pass_advances_normally | PASS |
|
||||
| TC-20 | STAGE_TRANSITIONS не изменён; тесты стадий зелёные | tests/test_stages.py (полный прогон) | PASS |
|
||||
| TC-21 | Гейт не вызывает деплой-хук/рестарт прод (self-hosting safety) | test_tc21_fail_never_triggers_deploy | PASS |
|
||||
|
||||
Все 21 TC покрыты и зелёные. Соответствие критериям приёмки (03-acceptance-criteria):
|
||||
AC-1..AC-21 закрыты соответствующими TC (AC-N ↔ TC-N для N=1..21; AC-20 «документация»
|
||||
подтверждён в review 12-review.md).
|
||||
|
||||
## Вывод pytest
|
||||
|
||||
### Security-специфичные тесты (25 шт.)
|
||||
```
|
||||
tests/test_security_gate.py ............... (15)
|
||||
tests/test_qg_security.py ...... (6)
|
||||
tests/test_stage_engine_security_gate.py ..... (5)
|
||||
======================== 25 passed, 1 warning in 0.49s =========================
|
||||
```
|
||||
|
||||
### Полный регресс
|
||||
```
|
||||
======================= 772 passed, 1 warning in 14.70s ========================
|
||||
```
|
||||
(1 warning — PydanticDeprecatedSince20 в src/config.py, не связан с ORCH-022,
|
||||
существовал до задачи.)
|
||||
|
||||
## Итог
|
||||
**PASS** — полный регресс 772/772 зелёный, 25 security-тестов покрывают все 21 TC
|
||||
плана и AC-1..AC-21, smoke-тесты API прод-инстанса OK. Прод-контейнер в процессе
|
||||
тестирования не затронут (тесты офлайн/изолированы). Задача готова к стадии deploy-staging.
|
||||
12
docs/work-items/ORCH-022/14-deploy-log.md
Normal file
12
docs/work-items/ORCH-022/14-deploy-log.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
deploy_status: SUCCESS
|
||||
work_item: ORCH-022
|
||||
hook_exit_code: 0
|
||||
deployed_by: deploy-finalizer
|
||||
---
|
||||
|
||||
# Deploy log — ORCH-036 executable self-deploy
|
||||
|
||||
Прод-деплой завершён хост-хуком с exit-code `0` -> `deploy_status: SUCCESS`.
|
||||
|
||||
Вердикт зафиксирован детерминированным finalizer'ом (Фаза C), не LLM.
|
||||
14
docs/work-items/ORCH-022/16-post-deploy-log.md
Normal file
14
docs/work-items/ORCH-022/16-post-deploy-log.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
post_deploy_status: HEALTHY
|
||||
action_taken: NONE
|
||||
work_item: ORCH-022
|
||||
window_s: 900
|
||||
checks_total: 30
|
||||
checks_failed: 0
|
||||
---
|
||||
|
||||
# Post-deploy log — ORCH-021 post-deploy monitor
|
||||
|
||||
Наблюдение прода завершено: `post_deploy_status: HEALTHY`, `action_taken: NONE`.
|
||||
|
||||
Окно наблюдения: 900s; опросов всего: 30, из них с провалом: 0.
|
||||
Reference in New Issue
Block a user