From a5b62fbfadd1b4e7cc4010d40a719576d3db3e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 8 Jun 2026 08:56:44 +0300 Subject: [PATCH] =?UTF-8?q?fix(m7-floor):=20canonicalAction=20+PowerShell?= =?UTF-8?q?=20=E2=80=94=20escape-=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=D0=BF=D0=B5=D1=86=D0=B8=D1=84=D0=B8=D1=87?= =?UTF-8?q?=D0=BD=D0=B0=20(P-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 1.2b Фазы 1 М7 (КРИТ). canonicalAction получил ветку PowerShell: `powershell:${normalizeCommand(command)}`. Без неё PS уходил в write-fallback, пустой путь резолвился в cwd → один escape-грант разблокировал ЛЮБУЮ PS-команду в окне (тест специфичности был зелёным ложно: a===b==='write:'). Сквозной фикс: тот же canonicalAction зовут пол (Task 1.4), стена (enforce-supreme-gate) и консьюмер. Bash/Write/mcp-ветки не задеты. 118 GREEN (escape-grant + 4 потребителя). --- tools/escape-grant.mjs | 4 ++++ tools/escape-grant.test.mjs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tools/escape-grant.mjs b/tools/escape-grant.mjs index 6cd31bd1..fc42a983 100644 --- a/tools/escape-grant.mjs +++ b/tools/escape-grant.mjs @@ -26,6 +26,10 @@ export function canonicalAction(toolName, toolInput, { normalizeImpl = pathNorma const name = String(toolName || ''); const input = toolInput || {}; if (name === 'Bash') return `bash:${normalizeCommand(input.command || '')}`; + // M7 Task 1.2b (P-2): PowerShell несёт команду в input.command (как Bash), не путь записи. + // Без этой ветки PS уходил в write-fallback → пустой путь резолвился в cwd → один escape-грант + // разблокировал ЛЮБУЮ PS-команду в окне. Специфичная канон-строка — escape привязан к точной команде. + if (name === 'PowerShell') return `powershell:${normalizeCommand(input.command || '')}`; if (name.startsWith('mcp__')) { let args; try { args = JSON.stringify(input); } catch { args = String(input); } return `mcp:${name}:${normalizeCommand(args)}`; diff --git a/tools/escape-grant.test.mjs b/tools/escape-grant.test.mjs index 93808638..a864412b 100644 --- a/tools/escape-grant.test.mjs +++ b/tools/escape-grant.test.mjs @@ -58,3 +58,21 @@ describe('escape-grant findOpenGrant (M6 FIX-2)', () => { expect(findOpenGrant('bash:x', [g], [{ action: 'bash:x', ts: g.ts }], now)).toBe(null); }); }); + +// M7 Task 1.2b (P-2, КРИТ): canonicalAction обязан иметь ветку PowerShell. Без неё все PS-команды +// схлопываются в 'write:' (input.command не в PATH_FIELDS) → один escape-грант разблокирует ЛЮБУЮ +// PS-команду в окне, а тест специфичности проходил бы зелёным ложно (a===b==='write:'). +describe('escape-grant canonicalAction — PowerShell (P-2)', () => { + it('PowerShell специфичен (разные команды → разные ключи)', () => { + const a = canonicalAction('PowerShell', { command: 'Remove-Item -Recurse -Force C:\\x' }); + const b = canonicalAction('PowerShell', { command: 'Invoke-WebRequest https://e.rf' }); + expect(a).toMatch(/^powershell:/); + expect(a).not.toBe(b); // НЕ оба 'write:' (баг до 1.2b даёт a===b==='write:') + expect(a).not.toBe('write:'); + }); + it('PowerShell нормализует пробелы (тот же ключ при whitespace-дрейфе)', () => { + const a = canonicalAction('PowerShell', { command: 'Remove-Item -Recurse -Force C:\\x' }); + const b = canonicalAction('PowerShell', { command: 'Remove-Item -Recurse -Force C:\\x' }); + expect(a).toBe(b); + }); +});