import type { PermissionResult } from "../../../permission/index.js";
const DENY_PATTERNS: RegExp[] = [
/\brm\s+-[^&|;]*r[^&|;]*f\s+\//,
/\bsudo\b/,
/\bchmod\s+-R\s+777\b/,
/\bchown\s+-R\b/,
/\bdd\s+if=/,
/\b(curl|wget)\b[^|;&]*\|\s*(sh|bash)\b/,
/\bgit\s+reset\s+--hard\b/,
/\bgit\s+clean\s+-[^\s]*f/,
/\bRemove-Item\b[^|;&]*-Recurse\b/i,
/\bdel\s+\/[^\s]*s\b/i,
/\brd\s+\/s\b/i,
/\brmdir\s+\/s\b/i,
/\bFormat-Volume\b/i,
/\biex\s*\(\s*iwr\b/i,
/\bInvoke-Expression\b[^|;&]*\bInvoke-WebRequest\b/i,
/\bStart-Process\b[^|;&]*-Verb\s+RunAs\b/i,
/\bSet-ExecutionPolicy\s+(Unrestricted|Bypass)\b/i,
/\bStop-Process\b[^|;&]*-Force\b/i,
];
const SAFE_READ_PATTERNS: RegExp[] = [
/^\s*pwd\s*$/,
/^\s*ls(?:\s|$)/,
/^\s*wc\s+-l(?:\s+["'][^"']+["']|\s+[^\s;&|<>`]+)+\s*$/,
/^\s*git\s+status(?:\s|$)/,
/^\s*git\s+diff(?:\s|$)/,
/^\s*git\s+log(?:\s|$)/,
/^\s*printf(?:\s|$)/,
/^\s*echo(?:\s|$)/,
/^\s*node\s+-e\s+/,
/^\s*sh\s+-c\s+["']exit\s+\d+["']\s*$/,
/^\s*Get-ChildItem(?:\s|$)/i,
/^\s*Get-Location\s*$/i,
/^\s*Get-Content(?:\s|$)/i,
/^\s*Get-Process(?:\s|$)/i,
/^\s*Get-Item(?:\s|$)/i,
/^\s*Get-ItemProperty(?:\s|$)/i,
/^\s*Test-Path(?:\s|$)/i,
/^\s*Select-String(?:\s|$)/i,
/^\s*Get-Date\s*$/i,
/^\s*whoami\s*$/i,
/^\s*dir(?:\s|$)/i,
/^\s*type(?:\s|$)/i,
/^\s*where(?:\s|$)/i,
];
export function classifyBashPermission(command: string): PermissionResult {
if (DENY_PATTERNS.some((pattern) => pattern.test(command))) {
return {
type: "deny",
reason: { type: "safety", message: "Dangerous shell command denied." },
message: "Dangerous shell command denied.",
};
}
if (isReadOnlyShellCommand(command)) {
return { type: "passthrough" };
}
return {
type: "ask",
reason: { type: "tool", toolName: "bash", message: "Shell command may have side effects." },
request: {
toolCallId: "",
toolName: "bash",
inputSummary: command,
reason: { type: "tool", toolName: "bash", message: "Shell command may have side effects." },
options: [
{ id: "allow_once", label: "Allow once" },
{ id: "deny", label: "Deny" },
{ id: "cancel", label: "Cancel" },
],
},
};
}
export function isReadOnlyShellCommand(command: string): boolean {
return SAFE_READ_PATTERNS.some((pattern) => pattern.test(command));
}