- Add intelligent-router.sh hook for automatic agent routing - Add AUTO-TRIGGER-SUMMARY.md documentation - Add FINAL-INTEGRATION-SUMMARY.md documentation - Complete Prometheus integration (6 commands + 4 tools) - Complete Dexto integration (12 commands + 5 tools) - Enhanced Ralph with access to all agents - Fix /clawd command (removed disable-model-invocation) - Update hooks.json to v5 with intelligent routing - 291 total skills now available - All 21 commands with automatic routing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
473 lines
15 KiB
TypeScript
473 lines
15 KiB
TypeScript
/**
|
|
* Command Validator
|
|
*
|
|
* Security-focused command validation for process execution
|
|
*/
|
|
|
|
import { ProcessConfig, CommandValidation } from './types.js';
|
|
import type { IDextoLogger } from '@dexto/core';
|
|
|
|
const MAX_COMMAND_LENGTH = 10000; // 10K characters
|
|
|
|
// Dangerous command patterns that should be blocked
|
|
// Validated against common security vulnerabilities and dangerous command patterns
|
|
const DANGEROUS_PATTERNS = [
|
|
// File system destruction
|
|
/rm\s+-rf\s+\//, // rm -rf /
|
|
/rm\s+-rf\s+\/\s*$/, // rm -rf / (end of line)
|
|
/rm\s+-rf\s+\/\s*2/, // rm -rf / 2>/dev/null (with error suppression)
|
|
|
|
// Fork bomb variations
|
|
/:\(\)\{\s*:\|:&\s*\};:/, // Classic fork bomb
|
|
/:\(\)\{\s*:\|:&\s*\};/, // Fork bomb without final colon
|
|
/:\(\)\{\s*:\|:&\s*\}/, // Fork bomb without semicolon
|
|
|
|
// Disk operations
|
|
/dd\s+if=.*of=\/dev\//, // dd to disk devices
|
|
/dd\s+if=\/dev\/zero.*of=\/dev\//, // dd zero to disk
|
|
/dd\s+if=\/dev\/urandom.*of=\/dev\//, // dd random to disk
|
|
/>\s*\/dev\/sd[a-z]/, // Write to disk devices
|
|
/>>\s*\/dev\/sd[a-z]/, // Append to disk devices
|
|
|
|
// Filesystem operations
|
|
/mkfs\./, // Format filesystem
|
|
/mkfs\s+/, // Format filesystem with space
|
|
/fdisk\s+\/dev\/sd[a-z]/, // Partition disk
|
|
/parted\s+\/dev\/sd[a-z]/, // Partition disk with parted
|
|
|
|
// Download and execute patterns
|
|
/wget.*\|\s*sh/, // wget | sh
|
|
/wget.*\|\s*bash/, // wget | bash
|
|
/curl.*\|\s*sh/, // curl | sh
|
|
/curl.*\|\s*bash/, // curl | bash
|
|
/wget.*\|\s*python/, // wget | python
|
|
/curl.*\|\s*python/, // curl | python
|
|
|
|
// Shell execution
|
|
/\|\s*bash/, // Pipe to bash
|
|
/\|\s*sh/, // Pipe to sh
|
|
/\|\s*zsh/, // Pipe to zsh
|
|
/\|\s*fish/, // Pipe to fish
|
|
|
|
// Command evaluation
|
|
/eval\s+\$\(/, // eval $()
|
|
/eval\s+`/, // eval backticks
|
|
/eval\s+"/, // eval double quotes
|
|
/eval\s+'/, // eval single quotes
|
|
|
|
// Permission changes
|
|
/chmod\s+777\s+\//, // chmod 777 /
|
|
/chmod\s+777\s+\/\s*$/, // chmod 777 / (end of line)
|
|
/chmod\s+-R\s+777\s+\//, // chmod -R 777 /
|
|
/chown\s+-R\s+root\s+\//, // chown -R root /
|
|
|
|
// Network operations
|
|
/nc\s+-l\s+-p\s+\d+/, // netcat listener
|
|
/ncat\s+-l\s+-p\s+\d+/, // ncat listener
|
|
/socat\s+.*LISTEN/, // socat listener
|
|
|
|
// Process manipulation
|
|
/killall\s+-9/, // killall -9
|
|
/pkill\s+-9/, // pkill -9
|
|
/kill\s+-9\s+-1/, // kill -9 -1 (kill all processes)
|
|
|
|
// System shutdown/reboot
|
|
/shutdown\s+now/, // shutdown now
|
|
/reboot/, // reboot
|
|
/halt/, // halt
|
|
/poweroff/, // poweroff
|
|
|
|
// Memory operations
|
|
/echo\s+3\s*>\s*\/proc\/sys\/vm\/drop_caches/, // Clear page cache
|
|
/sync\s*;\s*echo\s+3\s*>\s*\/proc\/sys\/vm\/drop_caches/, // Sync and clear cache
|
|
|
|
// Network interface manipulation
|
|
/ifconfig\s+.*down/, // Bring interface down
|
|
/ip\s+link\s+set\s+.*down/, // Bring interface down with ip
|
|
|
|
// Package manager operations
|
|
/apt\s+remove\s+--purge\s+.*/, // Remove packages
|
|
/yum\s+remove\s+.*/, // Remove packages
|
|
/dnf\s+remove\s+.*/, // Remove packages
|
|
/pacman\s+-R\s+.*/, // Remove packages
|
|
];
|
|
|
|
// Command injection patterns
|
|
// Note: We don't block compound commands with && here, as they're handled by
|
|
// the compound command detection logic in determineApprovalRequirement()
|
|
const INJECTION_PATTERNS = [
|
|
// Command chaining with dangerous commands using semicolon (more suspicious)
|
|
/;\s*rm\s+-rf/, // ; rm -rf
|
|
/;\s*chmod\s+777/, // ; chmod 777
|
|
/;\s*chown\s+root/, // ; chown root
|
|
|
|
// Command substitution with dangerous commands
|
|
/`.*rm.*`/, // backticks with rm
|
|
/\$\(.*rm.*\)/, // $() with rm
|
|
/`.*chmod.*`/, // backticks with chmod
|
|
/\$\(.*chmod.*\)/, // $() with chmod
|
|
/`.*chown.*`/, // backticks with chown
|
|
/\$\(.*chown.*\)/, // $() with chown
|
|
|
|
// Multiple command separators
|
|
/;\s*;\s*/, // Multiple semicolons
|
|
/&&\s*&&\s*/, // Multiple && operators
|
|
/\|\|\s*\|\|\s*/, // Multiple || operators
|
|
|
|
// Redirection with dangerous commands
|
|
/rm\s+.*>\s*\/dev\/null/, // rm with output redirection
|
|
/chmod\s+.*>\s*\/dev\/null/, // chmod with output redirection
|
|
/chown\s+.*>\s*\/dev\/null/, // chown with output redirection
|
|
|
|
// Environment variable manipulation
|
|
/\$[A-Z_]+\s*=\s*.*rm/, // Environment variable with rm
|
|
/\$[A-Z_]+\s*=\s*.*chmod/, // Environment variable with chmod
|
|
/\$[A-Z_]+\s*=\s*.*chown/, // Environment variable with chown
|
|
];
|
|
|
|
// Commands that require approval
|
|
const REQUIRES_APPROVAL_PATTERNS = [
|
|
// File operations
|
|
/^rm\s+/, // rm (removal)
|
|
/^mv\s+/, // move files
|
|
/^cp\s+/, // copy files
|
|
/^chmod\s+/, // chmod
|
|
/^chown\s+/, // chown
|
|
/^chgrp\s+/, // chgrp
|
|
/^ln\s+/, // create links
|
|
/^unlink\s+/, // unlink files
|
|
|
|
// Git operations
|
|
/^git\s+push/, // git push
|
|
/^git\s+commit/, // git commit
|
|
/^git\s+reset/, // git reset
|
|
/^git\s+rebase/, // git rebase
|
|
/^git\s+merge/, // git merge
|
|
/^git\s+checkout/, // git checkout
|
|
/^git\s+branch/, // git branch
|
|
/^git\s+tag/, // git tag
|
|
|
|
// Package management
|
|
/^npm\s+publish/, // npm publish
|
|
/^npm\s+uninstall/, // npm uninstall
|
|
/^yarn\s+publish/, // yarn publish
|
|
/^yarn\s+remove/, // yarn remove
|
|
/^pip\s+install/, // pip install
|
|
/^pip\s+uninstall/, // pip uninstall
|
|
/^apt\s+install/, // apt install
|
|
/^apt\s+remove/, // apt remove
|
|
/^yum\s+install/, // yum install
|
|
/^yum\s+remove/, // yum remove
|
|
/^dnf\s+install/, // dnf install
|
|
/^dnf\s+remove/, // dnf remove
|
|
/^pacman\s+-S/, // pacman install
|
|
/^pacman\s+-R/, // pacman remove
|
|
|
|
// Container operations
|
|
/^docker\s+/, // docker commands
|
|
/^podman\s+/, // podman commands
|
|
/^kubectl\s+/, // kubectl commands
|
|
|
|
// System operations
|
|
/^sudo\s+/, // sudo commands
|
|
/^su\s+/, // su commands
|
|
/^systemctl\s+/, // systemctl commands
|
|
/^service\s+/, // service commands
|
|
/^mount\s+/, // mount commands
|
|
/^umount\s+/, // umount commands
|
|
/^fdisk\s+/, // fdisk commands
|
|
/^parted\s+/, // parted commands
|
|
/^mkfs\s+/, // mkfs commands
|
|
/^fsck\s+/, // fsck commands
|
|
|
|
// Network operations
|
|
/^iptables\s+/, // iptables commands
|
|
/^ufw\s+/, // ufw commands
|
|
/^firewall-cmd\s+/, // firewall-cmd commands
|
|
/^sshd\s+/, // sshd commands
|
|
/^ssh\s+/, // ssh commands
|
|
/^scp\s+/, // scp commands
|
|
/^rsync\s+/, // rsync commands
|
|
|
|
// Process management
|
|
/^kill\s+/, // kill commands
|
|
/^killall\s+/, // killall commands
|
|
/^pkill\s+/, // pkill commands
|
|
/^nohup\s+/, // nohup commands
|
|
/^screen\s+/, // screen commands
|
|
/^tmux\s+/, // tmux commands
|
|
|
|
// Database operations
|
|
/^mysql\s+/, // mysql commands
|
|
/^psql\s+/, // psql commands
|
|
/^sqlite3\s+/, // sqlite3 commands
|
|
/^mongodb\s+/, // mongodb commands
|
|
/^redis-cli\s+/, // redis-cli commands
|
|
];
|
|
|
|
// Safe command patterns for strict mode
|
|
const SAFE_PATTERNS = [
|
|
// Directory navigation with commands
|
|
/^cd\s+.*&&\s+\w+/, // cd && command
|
|
/^cd\s+.*;\s+\w+/, // cd ; command
|
|
|
|
// Safe pipe operations
|
|
/\|\s*grep/, // | grep
|
|
/\|\s*head/, // | head
|
|
/\|\s*tail/, // | tail
|
|
/\|\s*sort/, // | sort
|
|
/\|\s*uniq/, // | uniq
|
|
/\|\s*wc/, // | wc
|
|
/\|\s*cat/, // | cat
|
|
/\|\s*less/, // | less
|
|
/\|\s*more/, // | more
|
|
/\|\s*awk/, // | awk
|
|
/\|\s*sed/, // | sed
|
|
/\|\s*cut/, // | cut
|
|
/\|\s*tr/, // | tr
|
|
/\|\s*xargs/, // | xargs
|
|
|
|
// Safe redirection
|
|
/^ls\s+.*>/, // ls with output redirection
|
|
/^find\s+.*>/, // find with output redirection
|
|
/^grep\s+.*>/, // grep with output redirection
|
|
/^cat\s+.*>/, // cat with output redirection
|
|
];
|
|
|
|
// Write operation patterns for moderate mode
|
|
const WRITE_PATTERNS = [
|
|
// Output redirection
|
|
/>/, // output redirection
|
|
/>>/, // append redirection
|
|
/2>/, // error redirection
|
|
/2>>/, // error append redirection
|
|
/&>/, // both output and error redirection
|
|
/&>>/, // both output and error append redirection
|
|
|
|
// File operations
|
|
/tee\s+/, // tee command
|
|
/touch\s+/, // touch command
|
|
/mkdir\s+/, // mkdir command
|
|
/rmdir\s+/, // rmdir command
|
|
|
|
// Text editors
|
|
/vim\s+/, // vim command
|
|
/nano\s+/, // nano command
|
|
/emacs\s+/, // emacs command
|
|
/code\s+/, // code command (VS Code)
|
|
|
|
// File copying and moving
|
|
/cp\s+/, // cp command
|
|
/mv\s+/, // mv command
|
|
/scp\s+/, // scp command
|
|
/rsync\s+/, // rsync command
|
|
];
|
|
|
|
/**
|
|
* CommandValidator - Validates commands for security and policy compliance
|
|
*
|
|
* Security checks:
|
|
* 1. Command length limits
|
|
* 2. Dangerous command patterns
|
|
* 3. Command injection detection
|
|
* 4. Allowed/blocked command lists
|
|
* 5. Shell metacharacter analysis
|
|
* TODO: Add tests for this class
|
|
*/
|
|
export class CommandValidator {
|
|
private config: ProcessConfig;
|
|
private logger: IDextoLogger;
|
|
|
|
constructor(config: ProcessConfig, logger: IDextoLogger) {
|
|
this.config = config;
|
|
this.logger = logger;
|
|
this.logger.debug(
|
|
`CommandValidator initialized with security level: ${config.securityLevel}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Validate a command for security and policy compliance
|
|
*/
|
|
validateCommand(command: string): CommandValidation {
|
|
// 1. Check for empty command
|
|
if (!command || command.trim() === '') {
|
|
return {
|
|
isValid: false,
|
|
error: 'Command cannot be empty',
|
|
};
|
|
}
|
|
|
|
const trimmedCommand = command.trim();
|
|
|
|
// 2. Check for shell backgrounding (trailing &)
|
|
// This bypasses timeout and creates orphaned processes that can't be controlled
|
|
if (/&\s*$/.test(trimmedCommand)) {
|
|
return {
|
|
isValid: false,
|
|
error: 'Commands ending with & (shell backgrounding) are not allowed. Use run_in_background parameter instead for proper process management.',
|
|
};
|
|
}
|
|
|
|
// 3. Check command length
|
|
if (trimmedCommand.length > MAX_COMMAND_LENGTH) {
|
|
return {
|
|
isValid: false,
|
|
error: `Command too long: ${trimmedCommand.length} characters. Maximum: ${MAX_COMMAND_LENGTH}`,
|
|
};
|
|
}
|
|
|
|
// 4. Check against dangerous patterns (strict and moderate)
|
|
if (this.config.securityLevel !== 'permissive') {
|
|
for (const pattern of DANGEROUS_PATTERNS) {
|
|
if (pattern.test(trimmedCommand)) {
|
|
return {
|
|
isValid: false,
|
|
error: `Command matches dangerous pattern: ${pattern.source}`,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// 5. Check for command injection attempts (all security levels)
|
|
const injectionResult = this.detectInjection(trimmedCommand);
|
|
if (!injectionResult.isValid) {
|
|
return injectionResult;
|
|
}
|
|
|
|
// 6. Check against blocked commands list
|
|
for (const blockedPattern of this.config.blockedCommands) {
|
|
if (trimmedCommand.includes(blockedPattern)) {
|
|
return {
|
|
isValid: false,
|
|
error: `Command is blocked: matches "${blockedPattern}"`,
|
|
};
|
|
}
|
|
}
|
|
|
|
// 7. Check against allowed commands list (if not empty)
|
|
if (this.config.allowedCommands.length > 0) {
|
|
const isAllowed = this.config.allowedCommands.some((allowedCmd) =>
|
|
trimmedCommand.startsWith(allowedCmd)
|
|
);
|
|
|
|
if (!isAllowed) {
|
|
return {
|
|
isValid: false,
|
|
error: `Command not in allowed list. Allowed: ${this.config.allowedCommands.join(', ')}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
// 8. Determine if approval is required based on security level
|
|
const requiresApproval = this.determineApprovalRequirement(trimmedCommand);
|
|
|
|
return {
|
|
isValid: true,
|
|
normalizedCommand: trimmedCommand,
|
|
requiresApproval,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Detect command injection attempts
|
|
*/
|
|
private detectInjection(command: string): CommandValidation {
|
|
// Check for obvious injection patterns
|
|
for (const pattern of INJECTION_PATTERNS) {
|
|
if (pattern.test(command)) {
|
|
return {
|
|
isValid: false,
|
|
error: `Potential command injection detected: ${pattern.source}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
// In strict mode, be more aggressive
|
|
if (this.config.securityLevel === 'strict') {
|
|
// Check for multiple commands chained together (except safe ones)
|
|
const hasMultipleCommands = /;|\|{1,2}|&&/.test(command);
|
|
if (hasMultipleCommands) {
|
|
// Allow safe patterns like "cd dir && ls" or "command | grep pattern"
|
|
const isSafe = SAFE_PATTERNS.some((pattern) => pattern.test(command));
|
|
if (!isSafe) {
|
|
return {
|
|
isValid: false,
|
|
error: 'Multiple commands detected in strict mode. Use moderate or permissive mode if this is intentional.',
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
isValid: true,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Determine if a command requires approval
|
|
* Handles compound commands (with &&, ||, ;) by checking each sub-command
|
|
*/
|
|
private determineApprovalRequirement(command: string): boolean {
|
|
// Split compound commands by &&, ||, or ; to check each part independently
|
|
// This ensures dangerous operations in the middle of compound commands are detected
|
|
const subCommands = command.split(/\s*(?:&&|\|\||;)\s*/).map((cmd) => cmd.trim());
|
|
|
|
// Check if ANY sub-command requires approval
|
|
for (const subCmd of subCommands) {
|
|
if (!subCmd) continue; // Skip empty parts
|
|
|
|
// Strip leading shell keywords and braces to get the actual command
|
|
// This prevents bypassing approval checks via control-flow wrapping
|
|
const normalizedSubCmd = subCmd
|
|
.replace(/^(?:then|do|else)\b\s*/, '')
|
|
.replace(/^\{\s*/, '')
|
|
.trim();
|
|
if (!normalizedSubCmd) continue;
|
|
|
|
// Commands that modify system state always require approval
|
|
for (const pattern of REQUIRES_APPROVAL_PATTERNS) {
|
|
if (pattern.test(normalizedSubCmd)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// In strict mode, all commands require approval
|
|
if (this.config.securityLevel === 'strict') {
|
|
return true;
|
|
}
|
|
|
|
// In moderate mode, write operations require approval
|
|
if (this.config.securityLevel === 'moderate') {
|
|
if (WRITE_PATTERNS.some((pattern) => pattern.test(normalizedSubCmd))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Permissive mode - no additional approval required
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get list of blocked commands
|
|
*/
|
|
getBlockedCommands(): string[] {
|
|
return [...this.config.blockedCommands];
|
|
}
|
|
|
|
/**
|
|
* Get list of allowed commands
|
|
*/
|
|
getAllowedCommands(): string[] {
|
|
return [...this.config.allowedCommands];
|
|
}
|
|
|
|
/**
|
|
* Get security level
|
|
*/
|
|
getSecurityLevel(): string {
|
|
return this.config.securityLevel;
|
|
}
|
|
}
|