66 lines
3.9 KiB
JavaScript
66 lines
3.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* PreToolUse PowerShell gate (router-gate v4 §5.1.2). Зеркало Bash-гейта:
|
|
* default-deny whitelist + hard-blacklist (keep v3.8 F1 + v4.1 G10) +
|
|
* injection + path-deny + git через shared classifyGitCommand. Fail-CLOSE.
|
|
*/
|
|
import { fileURLToPath } from 'url';
|
|
import {
|
|
defaultPathNormalize,
|
|
DEFAULT_PROTECTED_PATTERNS,
|
|
pathDenyOverlay,
|
|
matchAny,
|
|
hasInjection,
|
|
classifyGitCommand,
|
|
loadApprovedGitOps,
|
|
} from './shell-content-rules.mjs';
|
|
import { readStdin, parseEventJson, exitDecision } from './enforce-hook-helpers.mjs';
|
|
|
|
// PowerShell — лёгкий сплиттер по ; | && || (без shell-quote: иной синтаксис).
|
|
export function tokenizePowerShell(command) {
|
|
const parts = String(command || '').split(/\s*(?:\|\||&&|[;|])\s*/).filter((p) => p.trim() !== '');
|
|
return parts.map((p) => {
|
|
const trimmed = p.trim();
|
|
const m = trimmed.match(/^([A-Za-z][\w-]*|\[[^\]]+\]::\w+|\$env:[A-Za-z_]+)/);
|
|
return { raw: trimmed, cmd: (m ? m[1] : trimmed).toLowerCase() };
|
|
});
|
|
}
|
|
|
|
export const PS_HARD_BLACKLIST = [
|
|
// keep v3.8 F1
|
|
{ re: /\b(?:Remove-Item|ri|del|erase|rd)\b/i, reason: 'Remove-Item/del запрещён' },
|
|
{ re: /\b(?:Move-Item|mi|move)\b/i, reason: 'Move-Item запрещён' },
|
|
{ re: /\b(?:Copy-Item|cpi|copy)\b/i, reason: 'Copy-Item запрещён' },
|
|
{ re: /\b(?:Set-Content|sc|Add-Content|ac|Out-File)\b/i, reason: 'Set/Add-Content/Out-File запрещён' },
|
|
{ re: /(?:^|[^0-9>&])>{1,2}(?![>&])/, reason: 'redirect (>/>>) запрещён' },
|
|
{ re: /\b(?:Invoke-Expression|iex)\b/i, reason: 'Invoke-Expression/iex запрещён' },
|
|
{ re: /\b(?:Invoke-WebRequest|iwr|curl|wget)\b[^\n]*\|\s*(?:iex|Invoke-Expression)/i, reason: 'IWR | iex запрещён' },
|
|
{ re: /\bStart-Process\b/i, reason: 'Start-Process запрещён' },
|
|
{ re: /\[System\.IO\.File\]::(?:Delete|WriteAllText|WriteAllBytes|AppendAllText)\b/i, reason: '[IO.File] write/delete запрещён' },
|
|
{ re: /\[System\.IO\.Directory\]::(?:Delete|CreateDirectory)\b/i, reason: '[IO.Directory] mutate запрещён' },
|
|
{ re: /\b(?:Stop-Process|kill|spps)\b/i, reason: 'Stop-Process/kill запрещён' },
|
|
{ re: /\b(?:Stop-Service|Remove-Service|Set-Service|New-Service)\b/i, reason: 'service mutate запрещён' },
|
|
{ re: /\bSet-ExecutionPolicy\b/i, reason: 'Set-ExecutionPolicy запрещён' },
|
|
{ re: /\bSet-ItemProperty\b/i, reason: 'Set-ItemProperty запрещён' },
|
|
{ re: /\b(?:Get-Credential|Export-PSSession)\b/i, reason: 'Get-Credential/Export-PSSession запрещён' },
|
|
{ re: /\b(?:Restart-Computer|Stop-Computer)\b/i, reason: 'Restart/Stop-Computer запрещён' },
|
|
{ re: /\b(?:Register-ScheduledTask|Set-ScheduledTask)\b/i, reason: 'ScheduledTask mutate запрещён' },
|
|
{ re: /\b(?:Set-Acl|icacls)\b/i, reason: 'Set-Acl/icacls запрещён' },
|
|
{ re: /\bNew-Item\b[^\n]*-ItemType\s+(?:File|Directory)\b/i, reason: 'New-Item (mutate) запрещён' },
|
|
// v4.1 G10
|
|
{ re: /\$env:[A-Za-z_]+\s*=/i, reason: 'G10: $env:X = ... запрещён' },
|
|
{ re: /\[System\.Environment\]::SetEnvironmentVariable\b/i, reason: 'G10: SetEnvironmentVariable запрещён' },
|
|
{ re: /\bSet-Item\s+-Path\s+Env:/i, reason: 'G10: Set-Item Env: запрещён' },
|
|
{ re: /\bNew-PSDrive\b/i, reason: 'G10: New-PSDrive запрещён' },
|
|
{ re: /\bInvoke-Azure[A-Z]/, reason: 'G10: Azure cmdlet запрещён' },
|
|
{ re: /\b(?:Get|New|Set|Remove)-Az[A-Z]/, reason: 'G10: Az cmdlet запрещён' },
|
|
{ re: /\b(?:Get|New|Set|Remove)-AWS[A-Z]/, reason: 'G10: AWS cmdlet запрещён' },
|
|
{ re: /\bgcloud\s+(?:auth|compute|iam|storage)\b/, reason: 'G10: gcloud запрещён' },
|
|
];
|
|
|
|
export function matchPsHardBlacklist(command) {
|
|
const s = String(command || '');
|
|
if (hasInjection(s)) return '#34: Write-Output/echo prompt-injection запрещён';
|
|
return matchAny(PS_HARD_BLACKLIST, s);
|
|
}
|