fix(m5): F-3 — манифест floor-manifest-check проверяет весь защитный контур

DEFAULT_REQUIRED_HOOKS проверял только enforce-floor — owner мог зарегистрировать пол,
забыть верховную стену / exfil-стражей и получить зелёный «protected». Расширено до
security-load-bearing набора: enforce-floor + enforce-supreme-gate + normative-content
+ read-path-deny + mcp-classification. «Пол подтверждён» теперь = весь контур. WARN-only
(Δ8 — сигнал, не блок); owner может передать иной requiredHooks.

Аудит Машины 5 (объектив sharp-edges). TDD RED->GREEN. Регрессия tools-only 2789 + 2 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-07 16:18:26 +03:00
parent 9c3205ad7c
commit 7dd3a16531
2 changed files with 36 additions and 3 deletions
+12 -3
View File
@@ -21,9 +21,18 @@ const HOOK_EVENTS = [
'SessionEnd', 'UserPromptSubmit', 'PreCompact', 'Notification',
];
// Минимально-обязательный набор для «пол подтверждён» — несущий пол-хук. Живая обёртка/
// владелец может передать расширенный защитный набор; контракт checkManifest — по requiredHooks.
const DEFAULT_REQUIRED_HOOKS = ['enforce-floor.mjs'];
// Security-load-bearing набор для «пол подтверждён» (F-3, аудит 2026-06-07): не один пол, а
// весь защитный контур — несущий пол (вето-до-плана) + верховная стена (вне-плана) + стражи
// нормативки/read-exfil/egress-exfil. Раньше проверялся только enforce-floor → owner мог
// забыть стену/стражей и получить зелёный «protected». Живая обёртка/владелец может передать
// иной набор; контракт checkManifest — по requiredHooks (DEFAULT — фолбэк main()). WARN-only.
const DEFAULT_REQUIRED_HOOKS = [
'enforce-floor.mjs', // несущий пол: вето-до-плана на необратимое
'enforce-supreme-gate.mjs', // верховная стена: default-deny вне плана (нужны ОБА)
'enforce-normative-content-rules.mjs', // защита нормативки/памяти от подмены
'enforce-read-path-deny.mjs', // read-exfil (transcript/runtime/secrets)
'enforce-mcp-classification.mjs', // egress-exfil (исходящий MCP-payload)
];
/** Собрать все command-строки хуков из settings (битые/пустые секции игнорируются, не бросает). */
export function collectHookCommands(settings) {
+24
View File
@@ -57,3 +57,27 @@ describe('checkManifest (6.5, Δ8): WARN при отсутствии регис
expect(r.block).toBeUndefined();
});
});
// F-3 (аудит 2026-06-07): DEFAULT_REQUIRED_HOOKS манифеста floor-manifest-check проверял только
// enforce-floor — owner мог зарегистрировать пол, забыть стену/exfil-стражей и получить зелёный
// «protected». Расширяем DEFAULT до security-load-bearing набора (пол + стена + normative/read/mcp
// стражи), чтобы «пол подтверждён» означал весь защитный контур, а не один хук. WARN-only.
describe('floor-manifest-check — F-3: DEFAULT защитный набор шире одного enforce-floor', () => {
it('settings с одним enforce-floor (без стены) → cry (DEFAULT теперь требует защитный набор)', () => {
const onlyFloor = { hooks: { PreToolUse: [{ matcher: '*', hooks: [{ type: 'command', command: 'node tools/enforce-floor.mjs' }] }] } };
const r = checkManifest({ settings: onlyFloor }); // без requiredHooks → DEFAULT
expect(r.cry).toBe(true);
expect(r.missing).toContain('enforce-supreme-gate.mjs');
});
it('settings с полным защитным набором → ok (не кричит)', () => {
const full = { hooks: { PreToolUse: [{ matcher: '*', hooks: [
{ type: 'command', command: 'node tools/enforce-floor.mjs' },
{ type: 'command', command: 'node tools/enforce-supreme-gate.mjs' },
{ type: 'command', command: 'node tools/enforce-normative-content-rules.mjs' },
{ type: 'command', command: 'node tools/enforce-read-path-deny.mjs' },
{ type: 'command', command: 'node tools/enforce-mcp-classification.mjs' },
] }] } };
const r = checkManifest({ settings: full }); // без requiredHooks → DEFAULT
expect(r.ok).toBe(true);
});
});