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
Три точечных дополнения, все обратно-совместимые (новый параметр со значением по умолчанию):
tools/brain-config.mjs— вDEFAULTSдобавить ключprotected_paths: []. После этогоresolveConfig({}).protected_pathsравно[]; произвольный список пробрасывается как есть.tools/enforce-normative-content-rules.mjs—isNormativePath(filePath, extraProtectedPaths = []): результат = совпадение базыNORMATIVE_PATTERNSИЛИ совпадение с любым непустым нормализованным путём изextraProtectedPaths(substring по нормализованномуfilePath). Вызов с одним аргументом → только база.tools/shell-content-rules.mjs—buildProtectedPatterns(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 = ["}
]