feat: implement Phase 2 validation improvements
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
18
README.md
18
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
|
||||
|
||||
135
docs/phase2-improvements.md
Normal file
135
docs/phase2-improvements.md
Normal file
@@ -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.
|
||||
@@ -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<string, any>, mode: ValidationMode = 'operation'): Promise<any> {
|
||||
private async validateNodeConfig(
|
||||
nodeType: string,
|
||||
config: Record<string, any>,
|
||||
mode: ValidationMode = 'operation',
|
||||
profile: ValidationProfile = 'ai-friendly'
|
||||
): Promise<any> {
|
||||
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
|
||||
@@ -808,6 +816,100 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
|
||||
};
|
||||
}
|
||||
|
||||
private async validateNodeMinimal(nodeType: string, config: Record<string, any>): Promise<any> {
|
||||
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<any> {
|
||||
const guides: Record<string, any> = {
|
||||
overview: {
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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<string, any>,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user