Files

15 KiB
Raw Permalink Blame History

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-043src/merge_gate.py + check_branch_mergeable + stage_engine._handle_merge_gate (ребро deploy-staging → deploy);
    • image-freshness ORCH-058src/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, LOWwarning (фиксируется в артефакте, не блокирует).
  • Недоступность 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_*).