claude-flow-multilang
Version:
Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture
196 lines (174 loc) • 5.55 kB
text/typescript
/**
* Hook validation utilities
*/
import type { HookType } from './hook-types.js';
export interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}
/**
* Validate hook parameters before execution
*/
export function validateHookParams(
hookType: HookType,
params: Record<string, any>,
): ValidationResult {
const result: ValidationResult = {
valid: true,
errors: [],
warnings: [],
};
// Common validations
if (params.metadata && typeof params.metadata === 'string') {
try {
JSON.parse(params.metadata);
} catch {
result.errors.push('Invalid JSON in --metadata parameter');
result.valid = false;
}
}
// Hook-specific validations
switch (hookType) {
case 'pre-task':
if (params.complexity && !['low', 'medium', 'high'].includes(params.complexity)) {
result.errors.push('--complexity must be one of: low, medium, high');
result.valid = false;
}
if (params.estimatedMinutes && isNaN(Number(params.estimatedMinutes))) {
result.errors.push('--estimated-minutes must be a number');
result.valid = false;
}
break;
case 'post-task':
if (!params.taskId) {
result.errors.push('--task-id is required for post-task hook');
result.valid = false;
}
break;
case 'pre-edit':
case 'post-edit':
if (!params.file) {
result.errors.push(`--file is required for ${hookType} hook`);
result.valid = false;
}
if (hookType === 'pre-edit' && params.operation) {
if (!['read', 'write', 'edit', 'delete'].includes(params.operation)) {
result.errors.push('--operation must be one of: read, write, edit, delete');
result.valid = false;
}
}
break;
case 'pre-command':
case 'post-command':
if (!params.command) {
result.errors.push(`--command is required for ${hookType} hook`);
result.valid = false;
}
if (hookType === 'post-command' && params.exitCode) {
if (isNaN(Number(params.exitCode))) {
result.errors.push('--exit-code must be a number');
result.valid = false;
}
}
break;
case 'session-restore':
if (!params.sessionId) {
result.errors.push('--session-id is required for session-restore hook');
result.valid = false;
}
break;
case 'pre-search':
if (!params.query) {
result.errors.push('--query is required for pre-search hook');
result.valid = false;
}
if (params.maxResults && isNaN(Number(params.maxResults))) {
result.errors.push('--max-results must be a number');
result.valid = false;
}
break;
case 'notification':
if (!params.message) {
result.errors.push('--message is required for notification hook');
result.valid = false;
}
if (params.level && !['info', 'warning', 'error'].includes(params.level)) {
result.errors.push('--level must be one of: info, warning, error');
result.valid = false;
}
break;
case 'performance':
if (params.duration && isNaN(Number(params.duration))) {
result.errors.push('--duration must be a number');
result.valid = false;
}
if (params.metrics && typeof params.metrics === 'string') {
try {
JSON.parse(params.metrics);
} catch {
result.errors.push('Invalid JSON in --metrics parameter');
result.valid = false;
}
}
break;
case 'memory-sync':
if (params.direction && !['push', 'pull', 'sync'].includes(params.direction)) {
result.errors.push('--direction must be one of: push, pull, sync');
result.valid = false;
}
break;
case 'telemetry':
if (!params.event) {
result.errors.push('--event is required for telemetry hook');
result.valid = false;
}
if (params.data && typeof params.data === 'string') {
try {
JSON.parse(params.data);
} catch {
result.errors.push('Invalid JSON in --data parameter');
result.valid = false;
}
}
break;
}
// Add warnings for deprecated or unusual usage
if (hookType === 'session-start' && params.loadPrevious && !params.sessionId) {
result.warnings.push('--load-previous without --session-id may load unexpected data');
}
if (
hookType === 'post-edit' &&
params.format &&
!params.file?.match(/\.(js|ts|jsx|tsx|py|java|cpp|cs)$/)
) {
result.warnings.push('--format may not work correctly for this file type');
}
return result;
}
/**
* Sanitize hook parameters for safe execution
*/
export function sanitizeHookParams(params: Record<string, any>): Record<string, any> {
const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null) {
continue;
}
// Sanitize file paths
if (['file', 'saveTo', 'target'].includes(key) && typeof value === 'string') {
// Remove potentially dangerous characters
sanitized[key] = value.replace(/[<>"|?*]/g, '');
}
// Sanitize commands
else if (key === 'command' && typeof value === 'string') {
// Basic command injection prevention
sanitized[key] = value.replace(/[;&|`$()]/g, '');
}
// Keep other values as-is
else {
sanitized[key] = value;
}
}
return sanitized;
}