/** * Política de solo lectura para agentes con `acceptanceRole: read-only`. * * Lista de comandos bash considerados seguros (solo lectura). Cualquier * comando destructivo se bloquea aunque contenga un comando seguro como * subcadena (p. ej. `cat fichero && rm -rf /`). */ const DESTRUCTIVE_PATTERNS: RegExp[] = [ /\brm\b/i, /\brmdir\b/i, /\bmv\b/i, /\bcp\b/i, /\bmkdir\b/i, /\btouch\b/i, /\bchmod\b/i, /\bchown\b/i, /\bchgrp\b/i, /\bln\b/i, /\btee\b/i, /\btruncate\b/i, /\bdd\b/i, /\bshred\b/i, /(^|[^<])\s*>\s*[^&]/, /\s>>\s/, /\bnpm\s+(install|uninstall|update|ci|link|publish|patch)/i, /\byarn\s+(add|remove|install|publish)/i, /\bpnpm\s+(add|remove|install|publish)/i, /\bbun\s+(add|remove|install|pm)/i, /\bpip\s+(install|uninstall)/i, /\buv\s+(pip\s+)?(install|sync|add)/i, /\bapt(-get)?\s+(install|remove|purge|update|upgrade)/i, /\bbrew\s+(install|uninstall|upgrade|update)/i, /\bgit\s+(add|commit|push|pull|merge|rebase|reset|checkout|branch\s+-[dD]|stash|cherry-pick|revert|tag|init|clone|restore|apply)/i, /\bsudo\b/i, /\bsu\b/i, /\bkill\b/i, /\bpkill\b/i, /\bkillall\b/i, /\breboot\b/i, /\bshutdown\b/i, /\bsystemctl\s+(start|stop|restart|enable|disable)/i, /\bservice\s+\S+\s+(start|stop|restart)/i, /\b(vim?|nano|emacs|code|subl|helix)\b/i, ]; const SAFE_PATTERNS: RegExp[] = [ /^\s*cat\b/, /^\s*head\b/, /^\s*tail\b/, /^\s*less\b/, /^\s*more\b/, /^\s*grep\b/, /^\s*find\b/, /^\s*fd\b/, /^\s*rg\b/, /^\s*ls\b/, /^\s*pwd\b/, /^\s*echo\b/, /^\s*printf\b/, /^\s*wc\b/, /^\s*sort\b/, /^\s*uniq\b/, /^\s*diff\b/, /^\s*file\b/, /^\s*stat\b/, /^\s*du\b/, /^\s*df\b/, /^\s*tree\b/, /^\s*which\b/, /^\s*whereis\b/, /^\s*type\b/, /^\s*env\b/, /^\s*printenv\b/, /^\s*uname\b/, /^\s*whoami\b/, /^\s*id\b/, /^\s*date\b/, /^\s*cal\b/, /^\s*uptime\b/, /^\s*ps\b/, /^\s*top\b/, /^\s*htop\b/, /^\s*free\b/, /^\s*bat\b/, /^\s*eza\b/, /^\s*jq\b/, /^\s*sed\s+-n/i, /^\s*awk\b/, /^\s*git\s+(status|log|diff|show|branch|remote|config\s+--get|blame|shortlog|describe|ls-files|ls-remote|fetch\s+--dry-run)/i, /^\s*git\s+ls-/i, /^\s*npm\s+(list|ls|view|info|search|outdated|audit|explain)/i, /^\s*yarn\s+(list|info|why|audit)/i, /^\s*node\s+--version/i, /^\s*python[0-9.]*\s+--version/i, /^\s*curl\s+[^\n]*(\s--\s*$|\s-[A-Za-z]*I|\s--head|\s--location.*--remote-header-name)/i, /^\s*wget\s+-O\s*-|\s*wget\s+--spider/i, /^\s*jq\b/, ]; /** * Redirecciones inocuas que no deben contarse como destructivas: * duplicación de descriptores (`2>&1`, `>&2`) y envío a /dev/null * (`2>/dev/null`, `>>/dev/null`). */ function stripHarmlessRedirects(command: string): string { return command .replace(/(\d*)>{1,2}\s*\/dev\/null/g, " ") .replace(/(\d*)>\s*&\d+/g, " "); } /** * true si el comando bash es seguro en modo solo lectura: * coincide con la lista de comandos seguros y no contiene nada destructivo. */ export function isReadOnlySafeCommand(command: string): boolean { const normalized = command.trim(); if (!normalized) return false; const stripped = stripHarmlessRedirects(normalized); const isDestructive = DESTRUCTIVE_PATTERNS.some((p) => p.test(stripped)); if (isDestructive) return false; return SAFE_PATTERNS.some((p) => p.test(normalized)); }