export type RiskLevel = 'high' | 'low';
export interface SafetyValidationResult {
isValid: boolean;
riskLevel: RiskLevel;
warnings: string[];
sanitizedCommand?: string;
error?: string;
}
const DESTRUCTIVE_KEYWORDS = new Set(['delete', 'remove', 'purge', 'destroy', 'drop', 'wipe', 'clear']);
const VALID_AZ_PREFIXES = ['az ', 'az.cmd ', 'az.exe '];
const INJECTION_PATTERNS = new Map<RegExp, string>([
[/[;&|`$]/, 'Command chaining detected'],
[/\$\(.*\)/, 'Command substitution detected'],
[/`.*`/, 'Backtick substitution detected'],
[/>\s*\//, 'Redirect to root path detected'],
[/\|\s*(?:sh|bash|cmd|powershell)/i, 'Pipe to shell detected'],
[/(?:^|\s)(?:rm|del|format|shutdown|reboot)\s/i, 'Dangerous system command'],
]);
export function isValidAzureCommand(cmd: string): boolean {
const lower = cmd.trim().toLowerCase();
return VALID_AZ_PREFIXES.some(p => lower.startsWith(p));
}
export function isDestructiveCommand(cmd: string): boolean {
const lower = cmd.toLowerCase();
for (const kw of DESTRUCTIVE_KEYWORDS) {
if (new RegExp(`\\b${kw}\\b`, 'i').test(lower)) return true;
}
return false;
}
export function assessRiskLevel(cmd: string): RiskLevel {
if (isDestructiveCommand(cmd)) return 'high';
return 'low';
}
function detectInjection(cmd: string): string[] {
const detected: string[] = [];
for (const [pattern, msg] of INJECTION_PATTERNS) {
if (pattern.test(cmd)) detected.push(msg);
}
return detected;
}
export function sanitizeInput(command: string): SafetyValidationResult {
const warnings: string[] = [];
if (!command?.trim()) {
return { isValid: false, riskLevel: 'low', warnings: [], error: 'Command cannot be empty' };
}
const cmd = command.trim();
if (!isValidAzureCommand(cmd)) {
return { isValid: false, riskLevel: 'high', warnings: [], error: 'Must be a valid Azure CLI command starting with "az"' };
}
const injections = detectInjection(cmd);
if (injections.length) {
return { isValid: false, riskLevel: 'high', warnings: injections, error: 'Command contains dangerous patterns' };
}
const riskLevel = assessRiskLevel(cmd);
if (riskLevel === 'high') {
warnings.push('⚠️ DESTRUCTIVE: Will permanently delete resources');
warnings.push('⚠️ This action cannot be undone');
}
if (cmd.includes('--yes') || cmd.includes('-y')) {
warnings.push('Auto-confirm flag detected');
}
if (cmd.includes('--no-wait')) {
warnings.push('Async operation flag detected');
}
return { isValid: true, riskLevel, warnings, sanitizedCommand: cmd };
}
export function generateCommandSummary(command: string): string {
const parts = command.trim().split(/\s+/);
if (parts[0]?.toLowerCase().startsWith('az')) parts.shift();
const service = parts[0] || 'unknown';
const action = parts[1] || 'unknown';
const params = new Map<string, string>();
for (let i = 0; i < parts.length; i++) {
if ((parts[i] === '--name' || parts[i] === '-n') && parts[i + 1]) {
params.set('name', parts[i + 1]);
}
if ((parts[i] === '--resource-group' || parts[i] === '-g') && parts[i + 1]) {
params.set('rg', parts[i + 1]);
}
}
let summary = `${action} ${service} resource`;
if (params.get('name')) summary += ` "${params.get('name')}"`;
if (params.get('rg')) summary += ` in RG "${params.get('rg')}"`;
return summary;
}