397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
3.9 KiB
JavaScript
99 lines
3.9 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
||
import { tokenizePowerShell, matchPsHardBlacklist } from './enforce-powershell-gate.mjs';
|
||
|
||
describe('tokenizePowerShell', () => {
|
||
it('splits on ; and | into segments', () => {
|
||
const segs = tokenizePowerShell('Get-Content a | Select-String x ; Get-Item b');
|
||
expect(segs.map((s) => s.cmd)).toEqual(['get-content', 'select-string', 'get-item']);
|
||
});
|
||
});
|
||
|
||
describe('matchPsHardBlacklist — keep', () => {
|
||
it.each([
|
||
'Remove-Item x',
|
||
'ri x',
|
||
'del x',
|
||
'Move-Item a b',
|
||
'Copy-Item a b',
|
||
'Set-Content x "y"',
|
||
'Add-Content x "y"',
|
||
'Out-File -FilePath x',
|
||
'cmd > out.txt',
|
||
'Invoke-Expression $x',
|
||
'iex $x',
|
||
'Start-Process notepad',
|
||
'[System.IO.File]::Delete("x")',
|
||
'Stop-Process -Name node',
|
||
'Set-ExecutionPolicy Bypass',
|
||
'icacls x /grant y',
|
||
])('blocks %s', (cmd) => {
|
||
expect(matchPsHardBlacklist(cmd)).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
describe('matchPsHardBlacklist — v4.1 G10', () => {
|
||
it.each([
|
||
'$env:PATH = "x"',
|
||
'$env:ROUTER_LLM_KEY="leak"',
|
||
'[System.Environment]::SetEnvironmentVariable("X","Y")',
|
||
'Set-Item -Path Env:FOO -Value bar',
|
||
'New-PSDrive -Name X -PSProvider FileSystem -Root C:\\',
|
||
'Get-AzVM',
|
||
'New-AzResourceGroup x',
|
||
'Get-AWSCredential',
|
||
'gcloud auth login',
|
||
])('blocks %s', (cmd) => {
|
||
expect(matchPsHardBlacklist(cmd)).toBeTruthy();
|
||
});
|
||
});
|
||
|
||
describe('matchPsHardBlacklist — allows benign', () => {
|
||
it.each(['Get-ChildItem', 'Get-Content app/x.php', 'Select-String x file', 'git status'])('allows %s', (cmd) => {
|
||
expect(matchPsHardBlacklist(cmd)).toBe(null);
|
||
});
|
||
});
|
||
|
||
import { classifyPowerShellCommand } from './enforce-powershell-gate.mjs';
|
||
|
||
describe('classifyPowerShellCommand', () => {
|
||
const now = 4_000_000;
|
||
it('allows whitelisted reading cmdlet', () => {
|
||
expect(classifyPowerShellCommand('Get-ChildItem -Path app', {}).result).toBe('allow');
|
||
});
|
||
it('allows alias gci', () => {
|
||
expect(classifyPowerShellCommand('gci', {}).result).toBe('allow');
|
||
});
|
||
it('blocks hard-blacklisted Remove-Item', () => {
|
||
expect(classifyPowerShellCommand('Remove-Item x', {}).result).toBe('block');
|
||
});
|
||
it('blocks G10 $env set', () => {
|
||
expect(classifyPowerShellCommand('$env:PATH="x"', {}).result).toBe('block');
|
||
});
|
||
it('blocks reading a protected path', () => {
|
||
expect(classifyPowerShellCommand('Get-Content ~/.claude/settings.json', {}).result).toBe('block');
|
||
});
|
||
it('routes git through shared classifier (block unapproved commit)', () => {
|
||
expect(classifyPowerShellCommand('git commit -m "x"', { approvedGitOps: [], now }).result).toBe('block');
|
||
});
|
||
it('allows readonly git through PowerShell', () => {
|
||
expect(classifyPowerShellCommand('git status', {}).result).toBe('allow');
|
||
});
|
||
it('default-denies unknown cmdlet', () => {
|
||
expect(classifyPowerShellCommand('Frobnicate-Thing', {}).result).toBe('block');
|
||
});
|
||
});
|
||
|
||
// M7 PS single-source: powershell-gate ре-экспортирует матчер из единого дома shell-content-rules.
|
||
// Идентичность ссылки доказывает «единый источник, не копия» (зеркало Bash P-1). content-floor (М5)
|
||
// импортирует ТОТ ЖЕ матчер → дрейф подмножествами невозможен по конструкции.
|
||
import { matchPsHardBlacklist as PG_MATCH, PS_HARD_BLACKLIST as PG_LIST } from './enforce-powershell-gate.mjs';
|
||
import { matchPsHardBlacklist as SCR_PS_MATCH, PS_HARD_BLACKLIST as SCR_PS_LIST } from './shell-content-rules.mjs';
|
||
describe('powershell-gate re-exports single-source PS matcher (M7)', () => {
|
||
it('matchPsHardBlacklist is the SAME reference as shell-content-rules', () => {
|
||
expect(PG_MATCH).toBe(SCR_PS_MATCH);
|
||
});
|
||
it('PS_HARD_BLACKLIST is the SAME reference as shell-content-rules', () => {
|
||
expect(PG_LIST).toBe(SCR_PS_LIST);
|
||
});
|
||
});
|