Files
brain/docs/superpowers/specs/2026-06-15-task4-security-protected-paths-spec.md
T

5.3 KiB

Спека: Task 4 security — config-augment protected_paths (fail-CLOSED union)

Дата: 2026-06-15 Статус: Draft (Фаза 1 config-seam, security-часть Task 4) Канон: design v6 §3.3 / §5 / §5.1; план Фазы 1; handoff №2 §2.

Цель

Дать двум защитным гейтам движка (enforce-normative-content-rules, shell-content-rules) config-управляемое РАСШИРЕНИЕ списка защищённых путей через ключ protected_paths (из .claude/brain.local.md), по принципу fail-CLOSED union: базовая защита остаётся хардкодом и неизменной, конфиг только ДОБАВЛЯЕТ пути и никогда не убирает. Пусто/нет конфига → база защищает полностью. Поведение claude-brain не меняется (protected_paths: [] → байт-в-байт). Подключение значений в main() хуков — Задача 7 (здесь только чистый seam + дефолтный ключ).

Контракт augment

Три точечных дополнения, все обратно-совместимые (новый параметр со значением по умолчанию):

  1. tools/brain-config.mjs — в DEFAULTS добавить ключ protected_paths: []. После этого resolveConfig({}).protected_paths равно []; произвольный список пробрасывается как есть.
  2. tools/enforce-normative-content-rules.mjsisNormativePath(filePath, extraProtectedPaths = []): результат = совпадение базы NORMATIVE_PATTERNS ИЛИ совпадение с любым непустым нормализованным путём из extraProtectedPaths (substring по нормализованному filePath). Вызов с одним аргументом → только база.
  3. tools/shell-content-rules.mjsbuildProtectedPatterns(configPaths = []) возвращает массив [...DEFAULT_PROTECTED_PATTERNS, ...<config-паттерны>]: базовые паттерны ВСЕГДА первые и не удаляются; каждый config-путь экранируется и оборачивается в (^|/)… (case-insensitive).

Принцип fail-CLOSED union

  • База (NORMATIVE_PATTERNS, DEFAULT_PROTECTED_PATTERNS) — хардкод, неизменна, всегда активна.
  • Конфиг protected_paths — только UNION (добавление). Операции «снять защиту» нет.
  • Пусто / не-массив / отсутствие ключа → augment пуст → защищает только база (полная защита).
  • Невалидный вход (не-строка, пустая строка, пробелы) — отбрасывается, не роняет функцию, база остаётся.
  • Направление отказа безопасное: при любой неясности — защищаем (больше путей под гейтом).

Крайние случаи и критерий

Крайние случаи (обязательны в тестах):

  • backward-compat: вызов с одним аргументом (isNormativePath(path)) / без аргумента (buildProtectedPatterns()) — поведение байт-в-байт как до правки.
  • extraProtectedPaths = null / не-массив → трактуется как пусто (только база), без исключения.
  • protected_paths с пустыми строками / пробелами → отбрасываются (результат = только база).
  • база сохраняется при непустом augment (isNormativePath('CLAUDE.md', ['x']) === true; isProtectedPath('CLAUDE.md', …, buildProtectedPatterns(['x'])) === true).

Критерий приёмки:

  • Полный свод tools проходит: npx vitest run --config vitest.config.tools.mjs (авторитетный прогон — в терминале владельца; через сессионный Bash воркеры нестабильны под нагрузкой).
  • Регрессия: ни один существующий тест не падает (дефолты сохранили поведение).
  • Новые тесты покрывают: дефолтный ключ, augment-добавление, fail-CLOSED пусто, база-сохранена, невалидный вход.

Конвенция: имена параметров extraProtectedPaths / configPaths; стиль tools/ (ESM, чистые функции, без новых зависимостей); нормализация путей — replace(/\\/g, '/') как в существующем коде.

[
  {"id":"D1","kind":"EXTRACTED","ref":"tools/brain-config.mjs","anchor":"const DEFAULTS = Object.freeze({"},
  {"id":"D2","kind":"EXTRACTED","ref":"tools/enforce-normative-content-rules.mjs","anchor":"const NORMATIVE_PATTERNS = ["},
  {"id":"D3","kind":"EXTRACTED","ref":"tools/shell-content-rules.mjs","anchor":"export const DEFAULT_PROTECTED_PATTERNS = ["}
]