UNPKG

pm-orchestrator-enhancement

Version:

PM Orchestrator Enhancement - Multi-agent parallel execution system

311 lines 9.7 kB
"use strict"; /** * Input Validator Module * * ユーザー入力の検証とサニタイズを提供します。 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InputValidator = void 0; class InputValidator { constructor() { this.rules = []; this.initializeDefaultRules(); } /** * デフォルトルールの初期化 */ initializeDefaultRules() { // コマンドインジェクション防止 this.addRule({ name: 'no-command-injection', validate: (input) => { const dangerousPatterns = [ /;.*rm\s+-rf/i, /\|\|/, /&&/, /`[^`]+`/, /\$\([^)]+\)/ ]; return !dangerousPatterns.some(pattern => pattern.test(input)); }, message: 'Input contains potentially dangerous command patterns' }); // パストラバーサル防止 this.addRule({ name: 'no-path-traversal', validate: (input) => { return !input.includes('../') && !input.includes('..\\'); }, message: 'Input contains path traversal patterns' }); // SQLインジェクション防止 this.addRule({ name: 'no-sql-injection', validate: (input) => { const sqlPatterns = [ /'\s*OR\s+'1'\s*=\s*'1/i, /'\s*OR\s+1\s*=\s*1/i, /--/, /;\s*DROP\s+TABLE/i, /UNION\s+SELECT/i ]; return !sqlPatterns.some(pattern => pattern.test(input)); }, message: 'Input contains SQL injection patterns' }); // XSS防止 this.addRule({ name: 'no-xss', validate: (input) => { const xssPatterns = [ /<script[^>]*>.*<\/script>/i, /javascript:/i, /on\w+\s*=/i, // onclick=, onerror=, etc. /<iframe/i ]; return !xssPatterns.some(pattern => pattern.test(input)); }, message: 'Input contains XSS patterns' }); // 絶対パスのみ許可 this.addRule({ name: 'absolute-path-only', validate: (input) => { // ファイルパスの場合、絶対パスかチェック if (input.includes('/') || input.includes('\\')) { return input.startsWith('/') || /^[A-Z]:\\/.test(input); } return true; // ファイルパスでない場合はOK }, message: 'File paths must be absolute' }); } /** * ルールを追加 */ addRule(rule) { this.rules.push(rule); } /** * 入力を検証 */ validate(input) { const errors = []; const warnings = []; // 空入力チェック if (!input || input.trim().length === 0) { errors.push('Input is empty'); return { valid: false, errors, warnings }; } // 長さチェック if (input.length > 10000) { warnings.push('Input is very long (>10000 characters)'); } // 各ルールで検証 for (const rule of this.rules) { if (!rule.validate(input)) { errors.push(`${rule.name}: ${rule.message}`); } } const valid = errors.length === 0; const sanitized = valid ? this.sanitize(input) : undefined; return { valid, errors, warnings, sanitized }; } /** * 入力をサニタイズ */ sanitize(input) { let result = input; // HTMLエスケープ result = this.escapeHtml(result); // 制御文字を削除 // eslint-disable-next-line no-control-regex result = result.replace(/[\x00-\x1F\x7F]/g, ''); // 連続する空白を1つに result = result.replace(/\s+/g, ' '); // 前後の空白を削除 result = result.trim(); return result; } /** * HTMLエスケープ */ escapeHtml(input) { const htmlEscapeMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#x27;', '/': '&#x2F;' }; return input.replace(/[&<>"'/]/g, char => htmlEscapeMap[char] || char); } /** * ファイルパスを検証 */ validateFilePath(filePath) { const errors = []; const warnings = []; // 基本検証 const baseResult = this.validate(filePath); if (!baseResult.valid) { return baseResult; } // パストラバーサルチェック if (filePath.includes('../') || filePath.includes('..\\')) { errors.push('Path traversal detected'); } // 絶対パスチェック if (!filePath.startsWith('/') && !/^[A-Z]:\\/.test(filePath)) { errors.push('Path must be absolute'); } // 危険な文字チェック if (/[;|&$`]/.test(filePath)) { errors.push('Path contains dangerous characters'); } return { valid: errors.length === 0, errors: [...baseResult.errors, ...errors], warnings: [...baseResult.warnings, ...warnings], sanitized: baseResult.sanitized }; } /** * コマンドを検証 */ validateCommand(command) { const errors = []; const warnings = []; // 基本検証 const baseResult = this.validate(command); if (!baseResult.valid) { return baseResult; } // 危険なコマンドチェック const dangerousCommands = [ 'rm -rf', 'dd if=', 'mkfs', ':(){:|:&};:', // Fork bomb 'chmod 777', '> /dev/sda' ]; for (const dangerous of dangerousCommands) { if (command.toLowerCase().includes(dangerous.toLowerCase())) { errors.push(`Dangerous command detected: ${dangerous}`); } } // シェルメタ文字チェック const shellMetaChars = ['|', '&', ';', '>', '<', '`', '$', '(', ')']; const hasMetaChars = shellMetaChars.some(char => command.includes(char)); if (hasMetaChars) { warnings.push('Command contains shell meta-characters'); } return { valid: errors.length === 0, errors: [...baseResult.errors, ...errors], warnings: [...baseResult.warnings, ...warnings], sanitized: baseResult.sanitized }; } /** * JSON入力を検証 */ validateJson(input) { const errors = []; const warnings = []; try { JSON.parse(input); } catch (error) { errors.push('Invalid JSON format'); } return { valid: errors.length === 0, errors, warnings }; } /** * 環境変数名を検証 */ validateEnvVarName(name) { const errors = []; const warnings = []; // 環境変数名の規則: A-Z, 0-9, _ のみ if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) { errors.push('Environment variable name must contain only A-Z, 0-9, and _ (and start with A-Z or _)'); } return { valid: errors.length === 0, errors, warnings }; } /** * URLを検証 */ validateUrl(url) { const errors = []; const warnings = []; try { const parsed = new URL(url); // プロトコルチェック if (!['http:', 'https:'].includes(parsed.protocol)) { warnings.push(`Non-standard protocol: ${parsed.protocol}`); } // localhostチェック if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') { warnings.push('URL points to localhost'); } } catch (error) { errors.push('Invalid URL format'); } return { valid: errors.length === 0, errors, warnings }; } /** * 検証レポートを生成 */ generateReport(result) { const lines = [ '=== Validation Report ===', '', `Status: ${result.valid ? 'VALID' : 'INVALID'}`, `Errors: ${result.errors.length}`, `Warnings: ${result.warnings.length}`, '' ]; if (result.errors.length > 0) { lines.push('Errors:'); for (const error of result.errors) { lines.push(` ❌ ${error}`); } lines.push(''); } if (result.warnings.length > 0) { lines.push('Warnings:'); for (const warning of result.warnings) { lines.push(` ⚠️ ${warning}`); } lines.push(''); } if (result.sanitized) { lines.push('Sanitized Input:'); lines.push(` ${result.sanitized}`); } return lines.join('\n'); } } exports.InputValidator = InputValidator; //# sourceMappingURL=input-validator.js.map