/** Permission + shell policy with no Bare imports (unit-testable on Node). */ const WRITE_TOOLS = new Set(['search_replace', 'write_file', 'run_terminal_cmd', 'use_tool']); const ASK_TOOLS = new Set(['run_terminal_cmd', 'web_fetch', 'web_search', 'use_tool']); const SHELL_ALLOW = new Set([ 'git', 'rg', 'grep', 'ls', 'cat', 'head', 'tail', 'pwd', 'echo', 'node', 'npm', 'npx', 'python3', 'python', 'cargo', 'go', 'make', 'bare', 'wc', 'sort', 'uniq', 'find', 'sed', 'awk', ]); const SHELL_UNSAFE = /[;|`$()<>\n]|&&|\|\|/; const SHELL_REMEMBER_PREFIXES = ['git status', 'git diff']; function needsPermission(toolName, mode) { if (mode === 'always-approve') return false; if (mode === 'allowlist') return ASK_TOOLS.has(toolName); return WRITE_TOOLS.has(toolName) || ASK_TOOLS.has(toolName); } function shellName(command) { const c = String(command || '').trim(); const first = c.split(/\s+/)[0] || ''; return first.replace(/^["']|["']$/g, '').split(/[/\\]/).pop(); } function shellAllowlisted(command) { return SHELL_ALLOW.has(shellName(command)); } function shellSafe(command) { const c = String(command || ''); if (!c.trim()) return false; if (SHELL_UNSAFE.test(c)) return false; return shellAllowlisted(c); } function shellPrefix(command, n) { return String(command || '') .trim() .split(/\s+/) .slice(0, n || 2) .join(' '); } function matchesCommandPrefix(command, pattern) { const c = String(command || '').trim(); const p = String(pattern || '').trim(); if (!p) return false; if (c === p) return true; if (c.indexOf(p + ' ') === 0) return true; return false; } function isRememberableShell(command) { return SHELL_REMEMBER_PREFIXES.some((p) => matchesCommandPrefix(command, p)); } module.exports = { WRITE_TOOLS, ASK_TOOLS, SHELL_ALLOW, SHELL_REMEMBER_PREFIXES, needsPermission, shellName, shellAllowlisted, shellSafe, shellPrefix, matchesCommandPrefix, isRememberableShell, };