From 42a24278dba76e2efd41ec0f1eef6394c4a56de7 Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Tue, 24 Jun 2025 10:56:59 +0200 Subject: [PATCH] feat: implement Phase 2 validation improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 Professional Validation Features: 1. Validation Profiles: - minimal: Only required fields - runtime: Critical errors + security warnings - ai-friendly: Balanced (default) - strict: All checks + best practices 2. New Node Validators: - Webhook: Path validation, response modes, auth warnings - PostgreSQL: SQL injection detection, query safety - MySQL: Similar to Postgres with MySQL-specific checks 3. New Tools: - validate_node_minimal: Lightning-fast required field checking - Updated validate_node_operation with profile support 4. SQL Safety Features: - Detects template expressions vulnerable to injection - Warns about DELETE/UPDATE without WHERE - Catches dangerous operations (DROP, TRUNCATE) - Suggests parameterized queries 5. Enhanced Coverage: - Now supports 7+ major nodes with specific validators - Flexible validation based on use case - Professional-grade safety checks This completes the major validation system overhaul from the original plan. 🤖 Generated with Claude Code Co-Authored-By: Claude --- CLAUDE.md | 9 +- README.md | 18 +- docs/phase2-improvements.md | 135 +++++++++ src/mcp/server-update.ts | 114 +++++++- src/mcp/tools-update.ts | 26 +- src/services/config-validator.ts | 23 +- src/services/enhanced-config-validator.ts | 67 ++++- src/services/node-specific-validators.ts | 332 ++++++++++++++++++++++ 8 files changed, 689 insertions(+), 35 deletions(-) create mode 100644 docs/phase2-improvements.md diff --git a/CLAUDE.md b/CLAUDE.md index 13e0606..e4c148e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,11 +10,15 @@ n8n-mcp is a comprehensive documentation and knowledge server that provides AI a ### Update (v2.4.2) - Enhanced Node Configuration Validation: - ✅ **NEW: validate_node_operation tool** - Operation-aware validation with 80%+ fewer false positives +- ✅ **NEW: validate_node_minimal tool** - Lightning-fast validation for just required fields +- ✅ **NEW: Validation profiles** - Choose between minimal, runtime, ai-friendly, or strict validation - ✅ **NEW: EnhancedConfigValidator** - Smart validation that only checks relevant properties -- ✅ **NEW: Node-specific validators** - Custom logic for Slack, Google Sheets, OpenAI, MongoDB +- ✅ **NEW: Node-specific validators** - Custom logic for Slack, Google Sheets, OpenAI, MongoDB, Webhook, Postgres, MySQL +- ✅ **NEW: SQL safety features** - Detects SQL injection risks, unsafe DELETE/UPDATE queries - ✅ Added operation context filtering (only validates properties for selected operation) - ✅ Integrated working examples in validation responses when errors found - ✅ Added actionable next steps and auto-fix suggestions +- ✅ Basic code syntax validation for JavaScript/Python in Code node - ✅ Dramatic improvement for complex multi-operation nodes - ✅ Test results: Slack validation reduced from 45 errors to 1 error! @@ -258,7 +262,8 @@ The project implements MCP (Model Context Protocol) to expose n8n node documenta - `search_node_properties` - **NEW** Search for specific properties within a node - `get_node_for_task` - **NEW** Get pre-configured node settings for common tasks - `list_tasks` - **NEW** List all available task templates -- `validate_node_operation` - **NEW v2.4.2** Verify node configuration is correct before use +- `validate_node_operation` - **NEW v2.4.2** Verify node configuration with operation awareness and profiles +- `validate_node_minimal` - **NEW v2.4.2** Quick validation for just required fields - `get_property_dependencies` - **NEW** Analyze property dependencies and visibility conditions - `list_ai_tools` - List all AI-capable nodes (usableAsTool: true) - `get_node_documentation` - Get parsed documentation from n8n-docs diff --git a/README.md b/README.md index d51ddbc..0862293 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,8 @@ Once connected, Claude can use these powerful tools: ### Advanced Tools - **`get_node_for_task`** - Pre-configured node settings for common tasks - **`list_tasks`** - Discover available task templates -- **`validate_node_operation`** - Validate node configurations (operation-aware, 80% fewer false positives) +- **`validate_node_operation`** - Validate node configurations (operation-aware, profiles support) +- **`validate_node_minimal`** - Quick validation for just required fields - **`get_property_dependencies`** - Analyze property visibility conditions - **`get_node_documentation`** - Get parsed documentation from n8n-docs - **`get_database_statistics`** - View database metrics and coverage @@ -151,7 +152,14 @@ get_node_for_task("send_email") // Validate before deployment validate_node_operation({ nodeType: "nodes-base.httpRequest", - config: { method: "POST", url: "..." } + config: { method: "POST", url: "..." }, + profile: "runtime" // or "minimal", "ai-friendly", "strict" +}) + +// Quick required field check +validate_node_minimal({ + nodeType: "nodes-base.slack", + config: { resource: "message", operation: "send" } }) ``` @@ -343,10 +351,14 @@ Current database coverage (n8n v1.97.1): ## 🔄 Recent Updates -### v2.4.2 - Enhanced Validation System +### v2.4.2 - Professional Validation System - ✅ **NEW**: `validate_node_operation` - Operation-aware validation with 80% fewer false positives +- ✅ **NEW**: `validate_node_minimal` - Lightning-fast required field checking +- ✅ **NEW**: Validation profiles - Choose between minimal, runtime, ai-friendly, or strict +- ✅ **NEW**: Node validators for Webhook, Postgres, MySQL with SQL safety checks - ✅ **IMPROVED**: Deduplicates errors, filters internal properties - ✅ **ADDED**: Basic code syntax validation for JavaScript/Python +- ✅ **ADDED**: SQL injection detection and unsafe query warnings - ✅ **FIXED**: Removed deprecated `validate_node_config` tool ### v2.4.0 - AI-Optimized Tools & MIT License diff --git a/docs/phase2-improvements.md b/docs/phase2-improvements.md new file mode 100644 index 0000000..40ecc38 --- /dev/null +++ b/docs/phase2-improvements.md @@ -0,0 +1,135 @@ +# Phase 2 Improvements - v2.4.2 + +## 🎯 Overview + +Following the successful implementation of operation-aware validation, Phase 2 adds professional-grade features that make the validation system even more powerful and flexible. + +## ✅ Implemented Features + +### 1. **Validation Profiles** 🎨 + +Different validation levels for different use cases: + +```typescript +validate_node_operation({ + nodeType: "nodes-base.slack", + config: { ... }, + profile: "minimal" // or "runtime", "ai-friendly", "strict" +}) +``` + +#### Available Profiles: + +| Profile | Purpose | What it checks | +|---------|---------|----------------| +| **minimal** | Quick check | Only missing required fields | +| **runtime** | Pre-execution | Critical errors + security warnings | +| **ai-friendly** | Balanced (default) | Errors + helpful warnings | +| **strict** | Code review | Everything + best practices | + +### 2. **New Node Validators** 🔧 + +Added comprehensive validators for commonly used nodes: + +#### **Webhook Validator** +- Path format validation (no spaces, special chars) +- Response mode checks +- HTTP method validation +- Authentication warnings + +#### **PostgreSQL Validator** +- SQL injection detection +- DELETE/UPDATE without WHERE warnings +- Operation-specific validation (insert, update, delete, execute) +- Query safety checks + +#### **MySQL Validator** +- Similar to PostgreSQL +- MySQL-specific syntax checks +- Timezone configuration suggestions + +### 3. **validate_node_minimal Tool** ⚡ + +Lightning-fast validation for just required fields: + +```json +{ + "nodeType": "nodes-base.slack", + "displayName": "Slack", + "valid": false, + "missingRequiredFields": ["Channel"] +} +``` + +- No warnings +- No suggestions +- No examples +- Just missing required fields +- Perfect for quick checks + +### 4. **SQL Safety Features** 🛡️ + +Comprehensive SQL query validation: +- Detects template expressions that could be vulnerable +- Warns about DELETE/UPDATE without WHERE +- Catches dangerous operations (DROP, TRUNCATE) +- Suggests parameterized queries +- Database-specific checks (PostgreSQL $$ quotes, MySQL backticks) + +## 📊 Impact + +### Before Phase 2: +- Single validation mode +- Limited node coverage (4 nodes) +- No SQL safety checks +- Fixed validation behavior + +### After Phase 2: +- 4 validation profiles for different needs +- 7+ nodes with specific validators +- Comprehensive SQL injection prevention +- Flexible validation based on use case +- Ultra-fast minimal validation option + +## 🚀 Usage Examples + +### Using Validation Profiles: +```javascript +// Quick check - just required fields +validate_node_minimal({ + nodeType: "nodes-base.webhook", + config: { responseMode: "lastNode" } +}) +// Result: Missing required field "path" + +// Pre-execution validation +validate_node_operation({ + nodeType: "nodes-base.postgres", + config: { + operation: "execute", + query: "DELETE FROM users WHERE id = ${userId}" + }, + profile: "runtime" +}) +// Result: SQL injection warning + +// Strict validation for code review +validate_node_operation({ + nodeType: "nodes-base.slack", + config: { /* valid config */ }, + profile: "strict" +}) +// Result: Suggestions for best practices +``` + +## 🎉 Summary + +Phase 2 transforms the validation system from a simple checker into a comprehensive validation framework: + +1. **Flexibility** - Choose validation level based on your needs +2. **Safety** - SQL injection detection and prevention +3. **Speed** - Minimal validation for quick checks +4. **Coverage** - More nodes with specific validation logic +5. **Intelligence** - Context-aware suggestions and warnings + +The validation system now provides professional-grade safety and flexibility while maintaining the simplicity that makes it useful for AI agents. \ No newline at end of file diff --git a/src/mcp/server-update.ts b/src/mcp/server-update.ts index 293e53d..9ce5638 100644 --- a/src/mcp/server-update.ts +++ b/src/mcp/server-update.ts @@ -15,7 +15,7 @@ import { PropertyFilter } from '../services/property-filter'; import { ExampleGenerator } from '../services/example-generator'; import { TaskTemplates } from '../services/task-templates'; import { ConfigValidator } from '../services/config-validator'; -import { EnhancedConfigValidator, ValidationMode } from '../services/enhanced-config-validator'; +import { EnhancedConfigValidator, ValidationMode, ValidationProfile } from '../services/enhanced-config-validator'; import { PropertyDependencies } from '../services/property-dependencies'; import { SimpleCache } from '../utils/simple-cache'; import { TemplateService } from '../templates/template-service'; @@ -189,7 +189,9 @@ export class N8NDocumentationMCPServer { case 'list_tasks': return this.listTasks(args.category); case 'validate_node_operation': - return this.validateNodeConfig(args.nodeType, args.config, 'operation'); + return this.validateNodeConfig(args.nodeType, args.config, 'operation', args.profile); + case 'validate_node_minimal': + return this.validateNodeMinimal(args.nodeType, args.config); case 'get_property_dependencies': return this.getPropertyDependencies(args.nodeType, args.config); case 'list_node_templates': @@ -702,7 +704,12 @@ Full documentation is being prepared. For now, use get_node_essentials for confi return result; } - private async validateNodeConfig(nodeType: string, config: Record, mode: ValidationMode = 'operation'): Promise { + private async validateNodeConfig( + nodeType: string, + config: Record, + mode: ValidationMode = 'operation', + profile: ValidationProfile = 'ai-friendly' + ): Promise { await this.ensureInitialized(); if (!this.repository) throw new Error('Repository not initialized'); @@ -739,7 +746,8 @@ Full documentation is being prepared. For now, use get_node_essentials for confi node.nodeType, config, properties, - mode + mode, + profile ); // Add node context to result @@ -807,6 +815,100 @@ Full documentation is being prepared. For now, use get_node_essentials for confi } : undefined }; } + + private async validateNodeMinimal(nodeType: string, config: Record): Promise { + await this.ensureInitialized(); + if (!this.repository) throw new Error('Repository not initialized'); + + // Get node info + let node = this.repository.getNode(nodeType); + + if (!node) { + // Try alternative formats + const alternatives = [ + nodeType, + nodeType.replace('n8n-nodes-base.', ''), + `n8n-nodes-base.${nodeType}`, + nodeType.toLowerCase() + ]; + + for (const alt of alternatives) { + const found = this.repository!.getNode(alt); + if (found) { + node = found; + break; + } + } + + if (!node) { + throw new Error(`Node ${nodeType} not found`); + } + } + + // Get properties + const properties = node.properties || []; + + // Extract operation context + const operationContext = { + resource: config.resource, + operation: config.operation, + action: config.action, + mode: config.mode + }; + + // Find missing required fields + const missingFields: string[] = []; + + for (const prop of properties) { + // Skip if not required + if (!prop.required) continue; + + // Skip if not visible based on current config + if (prop.displayOptions) { + let isVisible = true; + + // Check show conditions + if (prop.displayOptions.show) { + for (const [key, values] of Object.entries(prop.displayOptions.show)) { + const configValue = config[key]; + const expectedValues = Array.isArray(values) ? values : [values]; + + if (!expectedValues.includes(configValue)) { + isVisible = false; + break; + } + } + } + + // Check hide conditions + if (isVisible && prop.displayOptions.hide) { + for (const [key, values] of Object.entries(prop.displayOptions.hide)) { + const configValue = config[key]; + const expectedValues = Array.isArray(values) ? values : [values]; + + if (expectedValues.includes(configValue)) { + isVisible = false; + break; + } + } + } + + if (!isVisible) continue; + } + + // Check if field is missing + if (!(prop.name in config)) { + missingFields.push(prop.displayName || prop.name); + } + } + + return { + nodeType: node.nodeType, + displayName: node.displayName, + valid: missingFields.length === 0, + missingRequiredFields: missingFields + }; + } private async getWorkflowGuide(topic?: string): Promise { const guides: Record = { @@ -819,7 +921,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi "1. search_nodes({query:'slack'}) - Find nodes by keyword", "2. get_node_essentials('nodes-base.slack') - Get only essential properties (<5KB)", "3. get_node_for_task('send_slack_message') - Get pre-configured settings", - "4. validate_node_config() - Validate before use" + "4. validate_node_operation() - Validate before use" ], tip: "Avoid get_node_info unless you need ALL properties (100KB+ response)" }, @@ -827,7 +929,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi discovery: "list_nodes({category:'trigger'}) - Browse by category", quick_config: "get_node_essentials() - 95% smaller than get_node_info", tasks: "list_tasks() then get_node_for_task() - Pre-configured common tasks", - validation: "validate_node_config() - Catch errors before execution" + validation: "validate_node_operation() - Catch errors before execution" } } }, diff --git a/src/mcp/tools-update.ts b/src/mcp/tools-update.ts index d1042df..5f429d6 100644 --- a/src/mcp/tools-update.ts +++ b/src/mcp/tools-update.ts @@ -181,7 +181,7 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [ }, { name: 'validate_node_operation', - description: `Verify your node configuration is correct before using it. Checks: required fields are present, values are valid types/formats, operation-specific rules are met. Returns specific errors with fixes (e.g., "Channel required to send Slack message - add channel: '#general'"), warnings about common issues, working examples when errors found, and suggested next steps. Smart validation that only checks properties relevant to your selected operation/action. Essential for Slack, Google Sheets, MongoDB, OpenAI nodes.`, + description: `Verify your node configuration is correct before using it. Checks: required fields are present, values are valid types/formats, operation-specific rules are met. Returns specific errors with fixes (e.g., "Channel required to send Slack message - add channel: '#general'"), warnings about common issues, working examples when errors found, and suggested next steps. Smart validation that only checks properties relevant to your selected operation/action. Essential for Slack, Google Sheets, MongoDB, OpenAI nodes. Supports validation profiles for different use cases.`, inputSchema: { type: 'object', properties: { @@ -193,6 +193,30 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [ type: 'object', description: 'Your node configuration. Must include operation fields (resource/operation/action) if the node has multiple operations.', }, + profile: { + type: 'string', + enum: ['strict', 'runtime', 'ai-friendly', 'minimal'], + description: 'Validation profile: minimal (only required fields), runtime (critical errors only), ai-friendly (balanced - default), strict (all checks including best practices)', + default: 'ai-friendly', + }, + }, + required: ['nodeType', 'config'], + }, + }, + { + name: 'validate_node_minimal', + description: `Quick validation that ONLY checks for missing required fields. Returns just the list of required fields that are missing. Fastest validation option - use when you only need to know if required fields are present. No warnings, no suggestions, no examples - just missing required fields.`, + inputSchema: { + type: 'object', + properties: { + nodeType: { + type: 'string', + description: 'The node type to validate (e.g., "nodes-base.slack")', + }, + config: { + type: 'object', + description: 'The node configuration to check', + }, }, required: ['nodeType', 'config'], }, diff --git a/src/services/config-validator.ts b/src/services/config-validator.ts index ce9b213..125d5df 100644 --- a/src/services/config-validator.ts +++ b/src/services/config-validator.ts @@ -304,28 +304,7 @@ export class ConfigValidator { warnings: ValidationWarning[], suggestions: string[] ): void { - // Path validation - if (config.path) { - if (config.path.startsWith('/')) { - warnings.push({ - type: 'inefficient', - property: 'path', - message: 'Webhook path should not start with /', - suggestion: 'Remove the leading / from the path' - }); - } - - if (config.path.includes(' ')) { - warnings.push({ - type: 'inefficient', - property: 'path', - message: 'Webhook path contains spaces', - suggestion: 'Use hyphens or underscores instead of spaces' - }); - } - } - - // Response mode suggestions + // Basic webhook validation - moved detailed validation to NodeSpecificValidators if (config.responseMode === 'responseNode' && !config.responseData) { suggestions.push('When using responseMode=responseNode, add a "Respond to Webhook" node to send custom responses'); } diff --git a/src/services/enhanced-config-validator.ts b/src/services/enhanced-config-validator.ts index b647004..0c3f22d 100644 --- a/src/services/enhanced-config-validator.ts +++ b/src/services/enhanced-config-validator.ts @@ -10,9 +10,11 @@ import { NodeSpecificValidators, NodeValidationContext } from './node-specific-v import { ExampleGenerator } from './example-generator'; export type ValidationMode = 'full' | 'operation' | 'minimal'; +export type ValidationProfile = 'strict' | 'runtime' | 'ai-friendly' | 'minimal'; export interface EnhancedValidationResult extends ValidationResult { mode: ValidationMode; + profile?: ValidationProfile; operation?: { resource?: string; operation?: string; @@ -40,7 +42,8 @@ export class EnhancedConfigValidator extends ConfigValidator { nodeType: string, config: Record, properties: any[], - mode: ValidationMode = 'operation' + mode: ValidationMode = 'operation', + profile: ValidationProfile = 'ai-friendly' ): EnhancedValidationResult { // Extract operation context from config const operationContext = this.extractOperationContext(config); @@ -60,11 +63,15 @@ export class EnhancedConfigValidator extends ConfigValidator { const enhancedResult: EnhancedValidationResult = { ...baseResult, mode, + profile, operation: operationContext, examples: [], nextSteps: [] }; + // Apply profile-based filtering + this.applyProfileFilters(enhancedResult, profile); + // Add operation-specific enhancements this.addOperationSpecificEnhancements(nodeType, config, enhancedResult); @@ -216,6 +223,18 @@ export class EnhancedConfigValidator extends ConfigValidator { case 'nodes-base.mongoDb': NodeSpecificValidators.validateMongoDB(context); break; + + case 'nodes-base.webhook': + NodeSpecificValidators.validateWebhook(context); + break; + + case 'nodes-base.postgres': + NodeSpecificValidators.validatePostgres(context); + break; + + case 'nodes-base.mysql': + NodeSpecificValidators.validateMySQL(context); + break; } // Update autofix if changes were made @@ -441,4 +460,50 @@ export class EnhancedConfigValidator extends ConfigValidator { return Array.from(seen.values()); } + + /** + * Apply profile-based filtering to validation results + */ + private static applyProfileFilters( + result: EnhancedValidationResult, + profile: ValidationProfile + ): void { + switch (profile) { + case 'minimal': + // Only keep missing required errors + result.errors = result.errors.filter(e => e.type === 'missing_required'); + result.warnings = []; + result.suggestions = []; + break; + + case 'runtime': + // Keep critical runtime errors only + result.errors = result.errors.filter(e => + e.type === 'missing_required' || + e.type === 'invalid_value' || + (e.type === 'invalid_type' && e.message.includes('undefined')) + ); + // Keep only security warnings + result.warnings = result.warnings.filter(w => w.type === 'security'); + result.suggestions = []; + break; + + case 'strict': + // Keep everything, add more suggestions + if (result.warnings.length === 0 && result.errors.length === 0) { + result.suggestions.push('Consider adding error handling and timeout configuration'); + result.suggestions.push('Add authentication if connecting to external services'); + } + break; + + case 'ai-friendly': + default: + // Current behavior - balanced for AI agents + // Filter out noise but keep helpful warnings + result.warnings = result.warnings.filter(w => + w.type !== 'inefficient' || !w.property?.startsWith('_') + ); + break; + } + } } \ No newline at end of file diff --git a/src/services/node-specific-validators.ts b/src/services/node-specific-validators.ts index 11fe993..1c82735 100644 --- a/src/services/node-specific-validators.ts +++ b/src/services/node-specific-validators.ts @@ -502,4 +502,336 @@ export class NodeSpecificValidators { break; } } + + /** + * Validate Webhook node configuration + */ + static validateWebhook(context: NodeValidationContext): void { + const { config, errors, warnings, suggestions } = context; + + // Path validation + if (!config.path) { + errors.push({ + type: 'missing_required', + property: 'path', + message: 'Webhook path is required', + fix: 'Set a unique path like "my-webhook" (no leading slash)' + }); + } else { + const path = config.path; + + // Check for leading slash + if (path.startsWith('/')) { + warnings.push({ + type: 'inefficient', + property: 'path', + message: 'Webhook path should not start with /', + suggestion: 'Remove the leading slash: use "my-webhook" instead of "/my-webhook"' + }); + } + + // Check for spaces + if (path.includes(' ')) { + errors.push({ + type: 'invalid_value', + property: 'path', + message: 'Webhook path cannot contain spaces', + fix: 'Replace spaces with hyphens or underscores' + }); + } + + // Check for special characters + if (!/^[a-zA-Z0-9\-_\/]+$/.test(path.replace(/^\//, ''))) { + warnings.push({ + type: 'inefficient', + property: 'path', + message: 'Webhook path contains special characters', + suggestion: 'Use only letters, numbers, hyphens, and underscores' + }); + } + } + + // Response mode validation + if (config.responseMode === 'responseNode') { + suggestions.push('Add a "Respond to Webhook" node to send custom responses'); + + if (!config.responseData) { + warnings.push({ + type: 'missing_common', + property: 'responseData', + message: 'Response data not configured for responseNode mode', + suggestion: 'Add a "Respond to Webhook" node or change responseMode' + }); + } + } + + // HTTP method validation + if (config.httpMethod && Array.isArray(config.httpMethod)) { + if (config.httpMethod.length === 0) { + errors.push({ + type: 'invalid_value', + property: 'httpMethod', + message: 'At least one HTTP method must be selected', + fix: 'Select GET, POST, or other methods your webhook should accept' + }); + } + } + + // Authentication warnings + if (!config.authentication || config.authentication === 'none') { + warnings.push({ + type: 'security', + message: 'Webhook has no authentication', + suggestion: 'Consider adding authentication to prevent unauthorized access' + }); + } + } + + /** + * Validate Postgres node configuration + */ + static validatePostgres(context: NodeValidationContext): void { + const { config, errors, warnings, suggestions, autofix } = context; + const { operation } = config; + + // Common query validation + if (['execute', 'select', 'insert', 'update', 'delete'].includes(operation)) { + this.validateSQLQuery(context, 'postgres'); + } + + // Operation-specific validation + switch (operation) { + case 'insert': + if (!config.table) { + errors.push({ + type: 'missing_required', + property: 'table', + message: 'Table name is required for insert operation', + fix: 'Specify the table to insert data into' + }); + } + + if (!config.columns && !config.dataMode) { + warnings.push({ + type: 'missing_common', + property: 'columns', + message: 'No columns specified for insert', + suggestion: 'Define which columns to insert data into' + }); + } + break; + + case 'update': + if (!config.table) { + errors.push({ + type: 'missing_required', + property: 'table', + message: 'Table name is required for update operation', + fix: 'Specify the table to update' + }); + } + + if (!config.updateKey) { + warnings.push({ + type: 'missing_common', + property: 'updateKey', + message: 'No update key specified', + suggestion: 'Set updateKey to identify which rows to update (e.g., "id")' + }); + } + break; + + case 'delete': + if (!config.table) { + errors.push({ + type: 'missing_required', + property: 'table', + message: 'Table name is required for delete operation', + fix: 'Specify the table to delete from' + }); + } + + if (!config.deleteKey) { + errors.push({ + type: 'missing_required', + property: 'deleteKey', + message: 'Delete key is required to identify rows', + fix: 'Set deleteKey (e.g., "id") to specify which rows to delete' + }); + } + break; + + case 'execute': + if (!config.query) { + errors.push({ + type: 'missing_required', + property: 'query', + message: 'SQL query is required', + fix: 'Provide the SQL query to execute' + }); + } + break; + } + + // Connection pool suggestions + if (config.connectionTimeout === undefined) { + suggestions.push('Consider setting connectionTimeout to handle slow connections'); + } + } + + /** + * Validate MySQL node configuration + */ + static validateMySQL(context: NodeValidationContext): void { + const { config, errors, warnings, suggestions } = context; + const { operation } = config; + + // MySQL uses similar validation to Postgres + if (['execute', 'insert', 'update', 'delete'].includes(operation)) { + this.validateSQLQuery(context, 'mysql'); + } + + // Operation-specific validation (similar to Postgres) + switch (operation) { + case 'insert': + if (!config.table) { + errors.push({ + type: 'missing_required', + property: 'table', + message: 'Table name is required for insert operation', + fix: 'Specify the table to insert data into' + }); + } + break; + + case 'update': + if (!config.table) { + errors.push({ + type: 'missing_required', + property: 'table', + message: 'Table name is required for update operation', + fix: 'Specify the table to update' + }); + } + + if (!config.updateKey) { + warnings.push({ + type: 'missing_common', + property: 'updateKey', + message: 'No update key specified', + suggestion: 'Set updateKey to identify which rows to update' + }); + } + break; + + case 'delete': + if (!config.table) { + errors.push({ + type: 'missing_required', + property: 'table', + message: 'Table name is required for delete operation', + fix: 'Specify the table to delete from' + }); + } + break; + + case 'execute': + if (!config.query) { + errors.push({ + type: 'missing_required', + property: 'query', + message: 'SQL query is required', + fix: 'Provide the SQL query to execute' + }); + } + break; + } + + // MySQL-specific warnings + if (config.timezone === undefined) { + suggestions.push('Consider setting timezone to ensure consistent date/time handling'); + } + } + + /** + * Validate SQL queries for injection risks and common issues + */ + private static validateSQLQuery( + context: NodeValidationContext, + dbType: 'postgres' | 'mysql' | 'generic' = 'generic' + ): void { + const { config, errors, warnings, suggestions } = context; + const query = config.query || config.deleteQuery || config.updateQuery || ''; + + if (!query) return; + + const lowerQuery = query.toLowerCase(); + + // SQL injection checks + if (query.includes('${') || query.includes('{{')) { + warnings.push({ + type: 'security', + message: 'Query contains template expressions that might be vulnerable to SQL injection', + suggestion: 'Use parameterized queries with query parameters instead of string interpolation' + }); + + suggestions.push('Example: Use "SELECT * FROM users WHERE id = $1" with queryParams: [userId]'); + } + + // DELETE without WHERE + if (lowerQuery.includes('delete') && !lowerQuery.includes('where')) { + errors.push({ + type: 'invalid_value', + property: 'query', + message: 'DELETE query without WHERE clause will delete all records', + fix: 'Add a WHERE clause to specify which records to delete' + }); + } + + // UPDATE without WHERE + if (lowerQuery.includes('update') && !lowerQuery.includes('where')) { + warnings.push({ + type: 'security', + message: 'UPDATE query without WHERE clause will update all records', + suggestion: 'Add a WHERE clause to specify which records to update' + }); + } + + // TRUNCATE warning + if (lowerQuery.includes('truncate')) { + warnings.push({ + type: 'security', + message: 'TRUNCATE will remove all data from the table', + suggestion: 'Consider using DELETE with WHERE clause if you need to keep some data' + }); + } + + // DROP warning + if (lowerQuery.includes('drop')) { + errors.push({ + type: 'invalid_value', + property: 'query', + message: 'DROP operations are extremely dangerous and will permanently delete database objects', + fix: 'Use this only if you really intend to delete tables/databases permanently' + }); + } + + // Performance suggestions + if (lowerQuery.includes('select *')) { + suggestions.push('Consider selecting specific columns instead of * for better performance'); + } + + // Database-specific checks + if (dbType === 'postgres') { + // PostgreSQL specific validations + if (query.includes('$$')) { + suggestions.push('Dollar-quoted strings detected - ensure they are properly closed'); + } + } else if (dbType === 'mysql') { + // MySQL specific validations + if (query.includes('`')) { + suggestions.push('Using backticks for identifiers - ensure they are properly paired'); + } + } + } } \ No newline at end of file