import type { ToolCallEvent, ToolCallDecision } from '../../src/types.js'; import { writeAuditLog } from '../../src/utils/logger.js'; // Rule 1: Destructive commands — rm -rf, mkfs, dd, format, chmod 777, raw device write const DESTRUCTIVE_PATTERNS = [ { regex: /\brm\s+(-[rRf]+\s+|--recursive|--force).*\//, label: 'rm -rf' }, { regex: /\bmkfs\b/, label: 'mkfs' }, { regex: /\bdd\s+if=/, label: 'dd' }, { regex: /\b(format|fdisk|wipefs)\b/, label: 'disk-tool' }, { regex: /\bchmod\s+(-R\s+)?777\s+\//, label: 'chmod-777' }, { regex: />\s*\/dev\/sd[a-z]/, label: 'raw-device-write' }, ]; // Rule 2: Credential exfiltration — curl/wget piping secrets to external servers const EXFIL_PATTERNS = [ { regex: /curl\s+.*\$[\({].*(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)/i, label: 'curl-env-secret' }, { regex: /wget\s+.*\$[\({].*(?:KEY|SECRET|TOKEN|PASSWORD)/i, label: 'wget-env-secret' }, { regex: /curl\s+.*--data.*\.env/i, label: 'curl-dotenv' }, { regex: /cat\s+.*\.env.*\|\s*curl/i, label: 'cat-env-pipe-curl' }, { regex: /curl\s+.*--upload-file.*\.(env|pem|key)/i, label: 'curl-upload-secret-file' }, ]; // Rule 3: Sensitive path writes — SSH, OpenClaw config, system auth files const SENSITIVE_PATH_PATTERNS = [ { regex: /[~$].*\/\.ssh\//, label: 'ssh-dir' }, { regex: /[~$].*\/\.openclaw\//, label: 'openclaw-dir' }, { regex: /\/etc\/(passwd|shadow|sudoers)/, label: 'system-auth' }, { regex: /[~$].*\/\.git-credentials/, label: 'git-credentials' }, { regex: /[~$].*\/\.aws\/credentials/, label: 'aws-credentials' }, { regex: /[~$].*\/\.kube\/config/, label: 'kube-config' }, { regex: /[~$].*\/\.gnupg\//, label: 'gnupg-dir' }, ]; // Tool names that execute commands const EXEC_TOOLS = new Set(['exec', 'shell', 'bash', 'terminal']); // Tool names that write files const WRITE_TOOLS = new Set(['write', 'edit', 'create_file']); /** * before_tool_call handler — returns { block: true } to prevent execution. * * NOTE: This is a Plugin Hook (not an internal event hook). It works when * the hook pack is installed as a plugin via `openclaw plugins install`. * Requires OpenClaw v2026.3.x+ with PR #54241 merged. */ const handler = async (event: ToolCallEvent): Promise => { try { const toolName = event.toolName ?? ''; const params = event.params ?? {}; // Check exec/shell/bash tools for destructive commands and exfiltration if (EXEC_TOOLS.has(toolName)) { const command = String(params.command ?? params.cmd ?? ''); if (!command) return; for (const rule of DESTRUCTIVE_PATTERNS) { if (rule.regex.test(command)) { logBlock(event, 'destructive_command', rule.label, command); return { block: true, reason: `Blocked: destructive command detected (${rule.label})` }; } } for (const rule of EXFIL_PATTERNS) { if (rule.regex.test(command)) { logBlock(event, 'credential_exfiltration', rule.label, command); return { block: true, reason: `Blocked: potential credential exfiltration (${rule.label})` }; } } } // Check write/edit tools for sensitive path writes if (WRITE_TOOLS.has(toolName)) { const filePath = String(params.path ?? params.file_path ?? params.filePath ?? ''); if (!filePath) return; for (const rule of SENSITIVE_PATH_PATTERNS) { if (rule.regex.test(filePath)) { logBlock(event, 'sensitive_path_write', rule.label, filePath); return { block: true, reason: `Blocked: write to sensitive path (${rule.label})` }; } } } // No rules matched — allow return; } catch (err) { console.error( '[security-guardrails:tool-guard] Error:', err instanceof Error ? err.message : String(err), ); // On error, fail open to avoid breaking the agent loop return; } }; function logBlock( event: ToolCallEvent, category: string, ruleLabel: string, value: string, ): void { writeAuditLog({ timestamp: new Date().toISOString(), event: 'tool_call_blocked', sessionKey: event.sessionKey ?? 'unknown', alerts: [{ rule: ruleLabel, hook: 'tool-guard', severity: 'critical', matched: value.slice(0, 100), }], metadata: { category, toolName: event.toolName, toolCallId: event.toolCallId, }, }); } export default handler;