agentic-qe 2.5.5 → 2.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/n8n/n8n-base-agent.md +376 -0
- package/.claude/agents/n8n/n8n-bdd-scenario-tester.md +613 -0
- package/.claude/agents/n8n/n8n-chaos-tester.md +654 -0
- package/.claude/agents/n8n/n8n-ci-orchestrator.md +850 -0
- package/.claude/agents/n8n/n8n-compliance-validator.md +685 -0
- package/.claude/agents/n8n/n8n-expression-validator.md +560 -0
- package/.claude/agents/n8n/n8n-integration-test.md +602 -0
- package/.claude/agents/n8n/n8n-monitoring-validator.md +589 -0
- package/.claude/agents/n8n/n8n-node-validator.md +455 -0
- package/.claude/agents/n8n/n8n-performance-tester.md +630 -0
- package/.claude/agents/n8n/n8n-security-auditor.md +786 -0
- package/.claude/agents/n8n/n8n-trigger-test.md +500 -0
- package/.claude/agents/n8n/n8n-unit-tester.md +633 -0
- package/.claude/agents/n8n/n8n-version-comparator.md +567 -0
- package/.claude/agents/n8n/n8n-workflow-executor.md +392 -0
- package/.claude/skills/n8n-expression-testing/SKILL.md +434 -0
- package/.claude/skills/n8n-integration-testing-patterns/SKILL.md +540 -0
- package/.claude/skills/n8n-security-testing/SKILL.md +599 -0
- package/.claude/skills/n8n-trigger-testing-strategies/SKILL.md +541 -0
- package/.claude/skills/n8n-workflow-testing-fundamentals/SKILL.md +447 -0
- package/CHANGELOG.md +111 -0
- package/README.md +7 -4
- package/dist/adapters/MemoryStoreAdapter.d.ts +75 -123
- package/dist/adapters/MemoryStoreAdapter.d.ts.map +1 -1
- package/dist/adapters/MemoryStoreAdapter.js +204 -219
- package/dist/adapters/MemoryStoreAdapter.js.map +1 -1
- package/dist/agents/AccessibilityAllyAgent.d.ts.map +1 -1
- package/dist/agents/AccessibilityAllyAgent.js +17 -1
- package/dist/agents/AccessibilityAllyAgent.js.map +1 -1
- package/dist/agents/BaseAgent.d.ts +18 -250
- package/dist/agents/BaseAgent.d.ts.map +1 -1
- package/dist/agents/BaseAgent.js +122 -520
- package/dist/agents/BaseAgent.js.map +1 -1
- package/dist/agents/n8n/N8nAPIClient.d.ts +121 -0
- package/dist/agents/n8n/N8nAPIClient.d.ts.map +1 -0
- package/dist/agents/n8n/N8nAPIClient.js +367 -0
- package/dist/agents/n8n/N8nAPIClient.js.map +1 -0
- package/dist/agents/n8n/N8nAuditPersistence.d.ts +120 -0
- package/dist/agents/n8n/N8nAuditPersistence.d.ts.map +1 -0
- package/dist/agents/n8n/N8nAuditPersistence.js +473 -0
- package/dist/agents/n8n/N8nAuditPersistence.js.map +1 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts +159 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js +697 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts +126 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nBaseAgent.js +446 -0
- package/dist/agents/n8n/N8nBaseAgent.js.map +1 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts +164 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.js +610 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.d.ts +205 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.js +729 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts +228 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.js +986 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nContractTesterAgent.d.ts +213 -0
- package/dist/agents/n8n/N8nContractTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nContractTesterAgent.js +989 -0
- package/dist/agents/n8n/N8nContractTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts +99 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.js +632 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts +238 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.js +956 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts +242 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.js +992 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts +104 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.js +653 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.js.map +1 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts +210 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.js +669 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts +142 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.js +1090 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts +198 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.js +653 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts +245 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.js +952 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts +325 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js +1187 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts +91 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.js +825 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nTestHarness.d.ts +131 -0
- package/dist/agents/n8n/N8nTestHarness.d.ts.map +1 -0
- package/dist/agents/n8n/N8nTestHarness.js +456 -0
- package/dist/agents/n8n/N8nTestHarness.js.map +1 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.d.ts +119 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.js +652 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.js.map +1 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.d.ts +130 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.js +522 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts +201 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.js +645 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts +120 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.js +347 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.js.map +1 -0
- package/dist/agents/n8n/index.d.ts +119 -0
- package/dist/agents/n8n/index.d.ts.map +1 -0
- package/dist/agents/n8n/index.js +298 -0
- package/dist/agents/n8n/index.js.map +1 -0
- package/dist/agents/n8n/types.d.ts +486 -0
- package/dist/agents/n8n/types.d.ts.map +1 -0
- package/dist/agents/n8n/types.js +8 -0
- package/dist/agents/n8n/types.js.map +1 -0
- package/dist/agents/utils/generators.d.ts +30 -0
- package/dist/agents/utils/generators.d.ts.map +1 -0
- package/dist/agents/utils/generators.js +44 -0
- package/dist/agents/utils/generators.js.map +1 -0
- package/dist/agents/utils/index.d.ts +10 -0
- package/dist/agents/utils/index.d.ts.map +1 -0
- package/dist/agents/utils/index.js +19 -0
- package/dist/agents/utils/index.js.map +1 -0
- package/dist/agents/utils/validation.d.ts +72 -0
- package/dist/agents/utils/validation.d.ts.map +1 -0
- package/dist/agents/utils/validation.js +75 -0
- package/dist/agents/utils/validation.js.map +1 -0
- package/dist/cli/init/agents.d.ts.map +1 -1
- package/dist/cli/init/agents.js +29 -0
- package/dist/cli/init/agents.js.map +1 -1
- package/dist/cli/init/skills.d.ts.map +1 -1
- package/dist/cli/init/skills.js +7 -1
- package/dist/cli/init/skills.js.map +1 -1
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/core/memory/SwarmMemoryManager.d.ts +114 -90
- package/dist/core/memory/SwarmMemoryManager.d.ts.map +1 -1
- package/dist/core/memory/SwarmMemoryManager.js +277 -235
- package/dist/core/memory/SwarmMemoryManager.js.map +1 -1
- package/dist/learning/baselines/StandardTaskSuite.d.ts.map +1 -1
- package/dist/learning/baselines/StandardTaskSuite.js +38 -0
- package/dist/learning/baselines/StandardTaskSuite.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/types/memory-interfaces.d.ts +76 -68
- package/dist/types/memory-interfaces.d.ts.map +1 -1
- package/dist/types/memory-interfaces.js +3 -0
- package/dist/types/memory-interfaces.js.map +1 -1
- package/docs/reference/agents.md +91 -2
- package/docs/reference/skills.md +97 -2
- package/package.json +2 -2
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* N8nSecurityAuditorAgent
|
|
4
|
+
*
|
|
5
|
+
* Security vulnerability scanning for n8n workflows:
|
|
6
|
+
* - Credential exposure detection
|
|
7
|
+
* - Secret scanning in expressions
|
|
8
|
+
* - SQL/NoSQL injection risk analysis
|
|
9
|
+
* - Command injection detection
|
|
10
|
+
* - SSRF detection
|
|
11
|
+
* - OWASP compliance checking
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.N8nSecurityAuditorAgent = void 0;
|
|
15
|
+
const N8nBaseAgent_1 = require("./N8nBaseAgent");
|
|
16
|
+
// Secret patterns to detect - Extended: 40+ patterns (from ~11)
|
|
17
|
+
const SECRET_PATTERNS = [
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Generic Secrets
|
|
20
|
+
// ============================================================================
|
|
21
|
+
{ pattern: /api[_-]?key["\s:=]+["']?[\w-]{20,}/i, name: 'API Key', severity: 'critical', category: 'generic' },
|
|
22
|
+
{ pattern: /bearer\s+[\w-]{20,}/i, name: 'Bearer Token', severity: 'critical', category: 'generic' },
|
|
23
|
+
{ pattern: /password["\s:=]+["']?[^"'\s]{8,}/i, name: 'Password', severity: 'critical', category: 'generic' },
|
|
24
|
+
{ pattern: /secret["\s:=]+["']?[\w-]{20,}/i, name: 'Secret', severity: 'critical', category: 'generic' },
|
|
25
|
+
{ pattern: /token["\s:=]+["']?[\w-]{20,}/i, name: 'Token', severity: 'critical', category: 'generic' },
|
|
26
|
+
{ pattern: /-----BEGIN.*PRIVATE KEY-----/, name: 'Private Key', severity: 'critical', category: 'crypto' },
|
|
27
|
+
{ pattern: /-----BEGIN.*RSA PRIVATE KEY-----/, name: 'RSA Private Key', severity: 'critical', category: 'crypto' },
|
|
28
|
+
{ pattern: /-----BEGIN.*EC PRIVATE KEY-----/, name: 'EC Private Key', severity: 'critical', category: 'crypto' },
|
|
29
|
+
{ pattern: /-----BEGIN.*OPENSSH PRIVATE KEY-----/, name: 'OpenSSH Private Key', severity: 'critical', category: 'crypto' },
|
|
30
|
+
{ pattern: /-----BEGIN.*PGP PRIVATE KEY BLOCK-----/, name: 'PGP Private Key', severity: 'critical', category: 'crypto' },
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Cloud Providers
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// AWS
|
|
35
|
+
{ pattern: /AKIA[0-9A-Z]{16}/i, name: 'AWS Access Key ID', severity: 'critical', category: 'aws' },
|
|
36
|
+
{ pattern: /aws[_-]?secret[_-]?access[_-]?key/i, name: 'AWS Secret Key Reference', severity: 'critical', category: 'aws' },
|
|
37
|
+
{ pattern: /[a-zA-Z0-9\/+]{40}/, name: 'AWS Secret Access Key (potential)', severity: 'high', category: 'aws' },
|
|
38
|
+
// GCP
|
|
39
|
+
{ pattern: /AIza[0-9A-Za-z-_]{35}/, name: 'Google API Key', severity: 'critical', category: 'gcp' },
|
|
40
|
+
{ pattern: /[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com/, name: 'Google OAuth Client ID', severity: 'high', category: 'gcp' },
|
|
41
|
+
{ pattern: /"type"\s*:\s*"service_account"/, name: 'GCP Service Account JSON', severity: 'critical', category: 'gcp' },
|
|
42
|
+
// Azure
|
|
43
|
+
{ pattern: /[a-zA-Z0-9+\/]{43}=/, name: 'Azure Storage Key (potential)', severity: 'high', category: 'azure' },
|
|
44
|
+
{ pattern: /DefaultEndpointsProtocol=https;AccountName=/i, name: 'Azure Connection String', severity: 'critical', category: 'azure' },
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// AI/LLM Providers
|
|
47
|
+
// ============================================================================
|
|
48
|
+
{ pattern: /sk-[a-zA-Z0-9]{32,}/, name: 'OpenAI API Key', severity: 'critical', category: 'ai' },
|
|
49
|
+
{ pattern: /sk-ant-api[0-9a-zA-Z-]{90,}/, name: 'Anthropic API Key', severity: 'critical', category: 'ai' },
|
|
50
|
+
{ pattern: /sk-or-[a-zA-Z0-9-]{40,}/, name: 'OpenRouter API Key', severity: 'critical', category: 'ai' },
|
|
51
|
+
{ pattern: /r8_[a-zA-Z0-9]{37}/, name: 'Replicate API Key', severity: 'critical', category: 'ai' },
|
|
52
|
+
{ pattern: /hf_[a-zA-Z0-9]{34}/, name: 'Hugging Face Token', severity: 'critical', category: 'ai' },
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Version Control & CI/CD
|
|
55
|
+
// ============================================================================
|
|
56
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/, name: 'GitHub Personal Access Token', severity: 'critical', category: 'vcs' },
|
|
57
|
+
{ pattern: /gho_[a-zA-Z0-9]{36}/, name: 'GitHub OAuth Token', severity: 'critical', category: 'vcs' },
|
|
58
|
+
{ pattern: /ghu_[a-zA-Z0-9]{36}/, name: 'GitHub User Token', severity: 'critical', category: 'vcs' },
|
|
59
|
+
{ pattern: /ghs_[a-zA-Z0-9]{36}/, name: 'GitHub Server Token', severity: 'critical', category: 'vcs' },
|
|
60
|
+
{ pattern: /github_pat_[a-zA-Z0-9_]{22,}/, name: 'GitHub Fine-grained PAT', severity: 'critical', category: 'vcs' },
|
|
61
|
+
{ pattern: /glpat-[a-zA-Z0-9_-]{20}/, name: 'GitLab Personal Access Token', severity: 'critical', category: 'vcs' },
|
|
62
|
+
{ pattern: /gloas-[a-zA-Z0-9_-]{20}/, name: 'GitLab OAuth App Secret', severity: 'critical', category: 'vcs' },
|
|
63
|
+
{ pattern: /ATBB[a-zA-Z0-9]{24}/, name: 'Bitbucket App Token', severity: 'critical', category: 'vcs' },
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Communication Platforms
|
|
66
|
+
// ============================================================================
|
|
67
|
+
{ pattern: /xox[baprs]-[\w-]+/, name: 'Slack Token', severity: 'critical', category: 'comms' },
|
|
68
|
+
{ pattern: /hooks\.slack\.com\/services\/T[a-zA-Z0-9_]+\/B[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+/, name: 'Slack Webhook URL', severity: 'high', category: 'comms' },
|
|
69
|
+
{ pattern: /[MN][A-Za-z\d]{23,27}\.[A-Za-z\d_-]{6}\.[A-Za-z\d_-]{27,}/, name: 'Discord Bot Token', severity: 'critical', category: 'comms' },
|
|
70
|
+
{ pattern: /discord\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/, name: 'Discord Webhook URL', severity: 'high', category: 'comms' },
|
|
71
|
+
{ pattern: /[0-9]+:AA[a-zA-Z0-9_-]{33}/, name: 'Telegram Bot Token', severity: 'critical', category: 'comms' },
|
|
72
|
+
{ pattern: /EAA[a-zA-Z0-9]+/, name: 'Facebook Access Token', severity: 'critical', category: 'comms' },
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// Payment & Financial
|
|
75
|
+
// ============================================================================
|
|
76
|
+
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/, name: 'Stripe Live Secret Key', severity: 'critical', category: 'payments' },
|
|
77
|
+
{ pattern: /sk_test_[a-zA-Z0-9]{24,}/, name: 'Stripe Test Secret Key', severity: 'high', category: 'payments' },
|
|
78
|
+
{ pattern: /pk_live_[a-zA-Z0-9]{24,}/, name: 'Stripe Live Publishable Key', severity: 'high', category: 'payments' },
|
|
79
|
+
{ pattern: /rk_live_[a-zA-Z0-9]{24,}/, name: 'Stripe Live Restricted Key', severity: 'critical', category: 'payments' },
|
|
80
|
+
{ pattern: /sq0atp-[a-zA-Z0-9_-]{22}/, name: 'Square Access Token', severity: 'critical', category: 'payments' },
|
|
81
|
+
{ pattern: /sq0csp-[a-zA-Z0-9_-]{43}/, name: 'Square OAuth Secret', severity: 'critical', category: 'payments' },
|
|
82
|
+
{ pattern: /access_token\$production\$[a-z0-9]{16}\$[a-f0-9]{32}/, name: 'PayPal Access Token', severity: 'critical', category: 'payments' },
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Database Connection Strings
|
|
85
|
+
// ============================================================================
|
|
86
|
+
{ pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@/, name: 'MongoDB Connection String', severity: 'critical', category: 'database' },
|
|
87
|
+
{ pattern: /postgres:\/\/[^:]+:[^@]+@/, name: 'PostgreSQL Connection String', severity: 'critical', category: 'database' },
|
|
88
|
+
{ pattern: /mysql:\/\/[^:]+:[^@]+@/, name: 'MySQL Connection String', severity: 'critical', category: 'database' },
|
|
89
|
+
{ pattern: /redis:\/\/:[^@]+@/, name: 'Redis Connection String', severity: 'critical', category: 'database' },
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Email & SMS Providers
|
|
92
|
+
// ============================================================================
|
|
93
|
+
{ pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/, name: 'SendGrid API Key', severity: 'critical', category: 'email' },
|
|
94
|
+
{ pattern: /key-[a-f0-9]{32}/, name: 'Mailgun API Key', severity: 'critical', category: 'email' },
|
|
95
|
+
{ pattern: /AC[a-f0-9]{32}/, name: 'Twilio Account SID', severity: 'high', category: 'sms' },
|
|
96
|
+
{ pattern: /SK[a-f0-9]{32}/, name: 'Twilio API Key', severity: 'critical', category: 'sms' },
|
|
97
|
+
];
|
|
98
|
+
// Injection patterns to detect - Extended: 20+ patterns (from ~4)
|
|
99
|
+
const INJECTION_PATTERNS = [
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// SQL Injection Patterns
|
|
102
|
+
// ============================================================================
|
|
103
|
+
{
|
|
104
|
+
pattern: /'?\s*\+\s*\$json\./,
|
|
105
|
+
name: 'SQL String Concatenation',
|
|
106
|
+
type: 'sql_injection',
|
|
107
|
+
severity: 'high',
|
|
108
|
+
cwe: 'CWE-89',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
pattern: /'\{\{\s*\$json\.[^}]+\}\}'/,
|
|
112
|
+
name: 'SQL Injection via Expression',
|
|
113
|
+
type: 'sql_injection',
|
|
114
|
+
severity: 'high',
|
|
115
|
+
cwe: 'CWE-89',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
pattern: /SELECT\s+.*\s+FROM\s+.*\s+WHERE\s+.*\{\{/i,
|
|
119
|
+
name: 'Dynamic SQL WHERE clause',
|
|
120
|
+
type: 'sql_injection',
|
|
121
|
+
severity: 'high',
|
|
122
|
+
cwe: 'CWE-89',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
pattern: /INSERT\s+INTO\s+.*VALUES\s*\(.*\{\{/i,
|
|
126
|
+
name: 'Dynamic SQL INSERT values',
|
|
127
|
+
type: 'sql_injection',
|
|
128
|
+
severity: 'high',
|
|
129
|
+
cwe: 'CWE-89',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
pattern: /UPDATE\s+.*SET\s+.*=\s*.*\{\{/i,
|
|
133
|
+
name: 'Dynamic SQL UPDATE values',
|
|
134
|
+
type: 'sql_injection',
|
|
135
|
+
severity: 'high',
|
|
136
|
+
cwe: 'CWE-89',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
pattern: /ORDER\s+BY\s+\{\{/i,
|
|
140
|
+
name: 'Dynamic SQL ORDER BY',
|
|
141
|
+
type: 'sql_injection',
|
|
142
|
+
severity: 'medium',
|
|
143
|
+
cwe: 'CWE-89',
|
|
144
|
+
},
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// NoSQL Injection Patterns
|
|
147
|
+
// ============================================================================
|
|
148
|
+
{
|
|
149
|
+
pattern: /\$where\s*:\s*.*\{\{/,
|
|
150
|
+
name: 'MongoDB $where injection',
|
|
151
|
+
type: 'sql_injection',
|
|
152
|
+
severity: 'critical',
|
|
153
|
+
cwe: 'CWE-943',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
pattern: /\$regex\s*:\s*.*\{\{/,
|
|
157
|
+
name: 'MongoDB $regex injection',
|
|
158
|
+
type: 'sql_injection',
|
|
159
|
+
severity: 'high',
|
|
160
|
+
cwe: 'CWE-943',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
pattern: /\{\s*"\$[a-z]+"\s*:\s*\{\{/i,
|
|
164
|
+
name: 'MongoDB operator injection',
|
|
165
|
+
type: 'sql_injection',
|
|
166
|
+
severity: 'high',
|
|
167
|
+
cwe: 'CWE-943',
|
|
168
|
+
},
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Command Injection Patterns
|
|
171
|
+
// ============================================================================
|
|
172
|
+
{
|
|
173
|
+
pattern: /\$\{?\$json\.[^}]*\}?\s*[;&|`]/,
|
|
174
|
+
name: 'Shell Metacharacter Injection',
|
|
175
|
+
type: 'command_injection',
|
|
176
|
+
severity: 'critical',
|
|
177
|
+
cwe: 'CWE-78',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
pattern: /\{\{\s*\$json\..*\}\}\s*[;&|`$]/,
|
|
181
|
+
name: 'Command Chaining via Expression',
|
|
182
|
+
type: 'command_injection',
|
|
183
|
+
severity: 'critical',
|
|
184
|
+
cwe: 'CWE-78',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
pattern: /eval\s*\(\s*\{\{/,
|
|
188
|
+
name: 'Dynamic Code Evaluation',
|
|
189
|
+
type: 'command_injection',
|
|
190
|
+
severity: 'critical',
|
|
191
|
+
cwe: 'CWE-95',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
pattern: /exec\s*\(\s*["'].*\{\{/,
|
|
195
|
+
name: 'exec() with user input',
|
|
196
|
+
type: 'command_injection',
|
|
197
|
+
severity: 'critical',
|
|
198
|
+
cwe: 'CWE-78',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
pattern: /child_process.*spawn.*\{\{/,
|
|
202
|
+
name: 'Child process with user input',
|
|
203
|
+
type: 'command_injection',
|
|
204
|
+
severity: 'critical',
|
|
205
|
+
cwe: 'CWE-78',
|
|
206
|
+
},
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// XSS Patterns
|
|
209
|
+
// ============================================================================
|
|
210
|
+
{
|
|
211
|
+
pattern: /\{\{\s*\$json\.[^}]+\}\}/,
|
|
212
|
+
name: 'Unescaped Template Expression',
|
|
213
|
+
type: 'xss',
|
|
214
|
+
severity: 'medium',
|
|
215
|
+
cwe: 'CWE-79',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
pattern: /<script[^>]*>.*\{\{/i,
|
|
219
|
+
name: 'Script Tag with Expression',
|
|
220
|
+
type: 'xss',
|
|
221
|
+
severity: 'critical',
|
|
222
|
+
cwe: 'CWE-79',
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
pattern: /on\w+\s*=\s*["'][^"']*\{\{/i,
|
|
226
|
+
name: 'Event Handler with Expression',
|
|
227
|
+
type: 'xss',
|
|
228
|
+
severity: 'high',
|
|
229
|
+
cwe: 'CWE-79',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
pattern: /javascript\s*:\s*.*\{\{/i,
|
|
233
|
+
name: 'javascript: URI with Expression',
|
|
234
|
+
type: 'xss',
|
|
235
|
+
severity: 'high',
|
|
236
|
+
cwe: 'CWE-79',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
pattern: /innerHTML\s*=\s*.*\{\{/,
|
|
240
|
+
name: 'innerHTML with Expression',
|
|
241
|
+
type: 'xss',
|
|
242
|
+
severity: 'high',
|
|
243
|
+
cwe: 'CWE-79',
|
|
244
|
+
},
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// Path Traversal Patterns
|
|
247
|
+
// ============================================================================
|
|
248
|
+
{
|
|
249
|
+
pattern: /\.\.\/.*\{\{/,
|
|
250
|
+
name: 'Path Traversal via Expression',
|
|
251
|
+
type: 'ssrf',
|
|
252
|
+
severity: 'high',
|
|
253
|
+
cwe: 'CWE-22',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
pattern: /file:\/\/.*\{\{/,
|
|
257
|
+
name: 'Local File Access via Expression',
|
|
258
|
+
type: 'ssrf',
|
|
259
|
+
severity: 'critical',
|
|
260
|
+
cwe: 'CWE-22',
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
pattern: /\{\{\s*\$json\..*\}\}.*\.(txt|log|json|xml|csv|yml|yaml|env|config)/i,
|
|
264
|
+
name: 'Dynamic File Extension',
|
|
265
|
+
type: 'ssrf',
|
|
266
|
+
severity: 'medium',
|
|
267
|
+
cwe: 'CWE-22',
|
|
268
|
+
},
|
|
269
|
+
// ============================================================================
|
|
270
|
+
// SSRF Patterns
|
|
271
|
+
// ============================================================================
|
|
272
|
+
{
|
|
273
|
+
pattern: /https?:\/\/\{\{/,
|
|
274
|
+
name: 'Full URL from User Input',
|
|
275
|
+
type: 'ssrf',
|
|
276
|
+
severity: 'high',
|
|
277
|
+
cwe: 'CWE-918',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
pattern: /https?:\/\/[^/]*\{\{\s*\$json/,
|
|
281
|
+
name: 'Dynamic Host in URL',
|
|
282
|
+
type: 'ssrf',
|
|
283
|
+
severity: 'critical',
|
|
284
|
+
cwe: 'CWE-918',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
pattern: /localhost|127\.0\.0\.1|0\.0\.0\.0|::1/,
|
|
288
|
+
name: 'Internal IP Reference',
|
|
289
|
+
type: 'ssrf',
|
|
290
|
+
severity: 'medium',
|
|
291
|
+
cwe: 'CWE-918',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
pattern: /169\.254\.169\.254/,
|
|
295
|
+
name: 'AWS Metadata URL',
|
|
296
|
+
type: 'ssrf',
|
|
297
|
+
severity: 'critical',
|
|
298
|
+
cwe: 'CWE-918',
|
|
299
|
+
},
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// XML/XXE Patterns
|
|
302
|
+
// ============================================================================
|
|
303
|
+
{
|
|
304
|
+
pattern: /<!ENTITY\s+/i,
|
|
305
|
+
name: 'XML Entity Declaration',
|
|
306
|
+
type: 'xss',
|
|
307
|
+
severity: 'high',
|
|
308
|
+
cwe: 'CWE-611',
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
pattern: /<!DOCTYPE\s+.*\[/i,
|
|
312
|
+
name: 'XML DOCTYPE with DTD',
|
|
313
|
+
type: 'xss',
|
|
314
|
+
severity: 'medium',
|
|
315
|
+
cwe: 'CWE-611',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
pattern: /SYSTEM\s+["']file:\/\//i,
|
|
319
|
+
name: 'XXE Local File Inclusion',
|
|
320
|
+
type: 'xss',
|
|
321
|
+
severity: 'critical',
|
|
322
|
+
cwe: 'CWE-611',
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
class N8nSecurityAuditorAgent extends N8nBaseAgent_1.N8nBaseAgent {
|
|
326
|
+
constructor(config) {
|
|
327
|
+
const capabilities = [
|
|
328
|
+
{
|
|
329
|
+
name: 'secret-scanning',
|
|
330
|
+
version: '1.0.0',
|
|
331
|
+
description: 'Detect exposed secrets and credentials',
|
|
332
|
+
parameters: {},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'injection-detection',
|
|
336
|
+
version: '1.0.0',
|
|
337
|
+
description: 'Detect SQL, command, and other injection risks',
|
|
338
|
+
parameters: {},
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'authentication-audit',
|
|
342
|
+
version: '1.0.0',
|
|
343
|
+
description: 'Audit authentication configurations',
|
|
344
|
+
parameters: {},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'owasp-compliance',
|
|
348
|
+
version: '1.0.0',
|
|
349
|
+
description: 'Check OWASP Top 10 compliance',
|
|
350
|
+
parameters: {},
|
|
351
|
+
},
|
|
352
|
+
];
|
|
353
|
+
super({
|
|
354
|
+
...config,
|
|
355
|
+
type: 'n8n-security-auditor',
|
|
356
|
+
capabilities: [...capabilities, ...(config.capabilities || [])],
|
|
357
|
+
});
|
|
358
|
+
this.securityConfig = {
|
|
359
|
+
n8nConfig: config.n8nConfig,
|
|
360
|
+
secretPatterns: config.secretPatterns || SECRET_PATTERNS.map(p => p.pattern),
|
|
361
|
+
owaspChecks: config.owaspChecks || [
|
|
362
|
+
'A01', 'A02', 'A03', 'A04', 'A05',
|
|
363
|
+
'A06', 'A07', 'A08', 'A09', 'A10',
|
|
364
|
+
],
|
|
365
|
+
severityThreshold: config.severityThreshold || 'low',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async performTask(task) {
|
|
369
|
+
const securityTask = task;
|
|
370
|
+
if (securityTask.type !== 'security-audit') {
|
|
371
|
+
throw new Error(`Unsupported task type: ${securityTask.type}`);
|
|
372
|
+
}
|
|
373
|
+
return this.auditWorkflow(securityTask.target, securityTask.options);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Perform full security audit on workflow
|
|
377
|
+
*
|
|
378
|
+
* PRODUCTION DEFAULT: Runtime checks are ENABLED by default.
|
|
379
|
+
* This ensures actual secret leakage detection, not just static analysis.
|
|
380
|
+
* Set executeRuntimeChecks: false to disable if workflow cannot be executed.
|
|
381
|
+
*/
|
|
382
|
+
async auditWorkflow(workflowId, options) {
|
|
383
|
+
const workflow = await this.getWorkflow(workflowId);
|
|
384
|
+
const findings = [];
|
|
385
|
+
// Secret scanning (static analysis)
|
|
386
|
+
if (options?.checkSecrets !== false) {
|
|
387
|
+
findings.push(...this.scanForSecrets(workflow));
|
|
388
|
+
}
|
|
389
|
+
// Injection detection (static analysis)
|
|
390
|
+
if (options?.checkInjection !== false) {
|
|
391
|
+
findings.push(...this.detectInjectionRisks(workflow));
|
|
392
|
+
}
|
|
393
|
+
// Authentication audit (static analysis)
|
|
394
|
+
if (options?.checkAuthentication !== false) {
|
|
395
|
+
findings.push(...this.auditAuthentication(workflow));
|
|
396
|
+
}
|
|
397
|
+
// Runtime secret leakage detection - ENABLED BY DEFAULT
|
|
398
|
+
// This is critical for production - static analysis alone misses runtime leaks
|
|
399
|
+
// Set executeRuntimeChecks: false explicitly to skip
|
|
400
|
+
if (options?.executeRuntimeChecks !== false) {
|
|
401
|
+
try {
|
|
402
|
+
const runtimeFindings = await this.detectRuntimeSecretLeakage(workflowId, options?.testInput);
|
|
403
|
+
findings.push(...runtimeFindings);
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
// If runtime execution fails, emit warning but continue with static results
|
|
407
|
+
this.emitEvent('security.runtime.skipped', {
|
|
408
|
+
workflowId,
|
|
409
|
+
reason: error instanceof Error ? error.message : 'Runtime execution failed',
|
|
410
|
+
note: 'Static analysis completed, but runtime leakage detection was skipped',
|
|
411
|
+
}, 'low');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// OWASP compliance
|
|
415
|
+
let owaspCompliance;
|
|
416
|
+
if (options?.checkOWASP !== false) {
|
|
417
|
+
owaspCompliance = this.checkOWASPCompliance(workflow, findings);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
owaspCompliance = { score: 0, categories: {} };
|
|
421
|
+
}
|
|
422
|
+
// Calculate risk score
|
|
423
|
+
const riskScore = this.calculateRiskScore(findings);
|
|
424
|
+
// Filter by severity threshold if specified
|
|
425
|
+
const filteredFindings = options?.severityThreshold
|
|
426
|
+
? this.filterBySeverity(findings, options.severityThreshold)
|
|
427
|
+
: findings;
|
|
428
|
+
const result = {
|
|
429
|
+
workflowId,
|
|
430
|
+
workflowName: workflow.name,
|
|
431
|
+
auditDate: new Date().toISOString(),
|
|
432
|
+
riskScore,
|
|
433
|
+
findings: filteredFindings,
|
|
434
|
+
owaspCompliance,
|
|
435
|
+
summary: {
|
|
436
|
+
critical: findings.filter(f => f.severity === 'critical').length,
|
|
437
|
+
high: findings.filter(f => f.severity === 'high').length,
|
|
438
|
+
medium: findings.filter(f => f.severity === 'medium').length,
|
|
439
|
+
low: findings.filter(f => f.severity === 'low').length,
|
|
440
|
+
info: findings.filter(f => f.severity === 'info').length,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
// Store result
|
|
444
|
+
await this.storeTestResult(`security-audit:${workflowId}`, result);
|
|
445
|
+
// Emit events for critical findings
|
|
446
|
+
if (result.summary.critical > 0) {
|
|
447
|
+
this.emitEvent('security.finding.critical', {
|
|
448
|
+
workflowId,
|
|
449
|
+
count: result.summary.critical,
|
|
450
|
+
findings: findings.filter(f => f.severity === 'critical'),
|
|
451
|
+
}, 'critical');
|
|
452
|
+
}
|
|
453
|
+
// Emit completion event
|
|
454
|
+
this.emitEvent('security.audit.completed', {
|
|
455
|
+
workflowId,
|
|
456
|
+
riskScore,
|
|
457
|
+
findingsCount: findings.length,
|
|
458
|
+
});
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Scan workflow for exposed secrets
|
|
463
|
+
*/
|
|
464
|
+
scanForSecrets(workflow) {
|
|
465
|
+
const findings = [];
|
|
466
|
+
let findingId = 1;
|
|
467
|
+
for (const node of workflow.nodes) {
|
|
468
|
+
const nodeJson = JSON.stringify(node.parameters);
|
|
469
|
+
for (const secretPattern of SECRET_PATTERNS) {
|
|
470
|
+
if (secretPattern.pattern.test(nodeJson)) {
|
|
471
|
+
findings.push({
|
|
472
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
473
|
+
type: 'hardcoded_secret',
|
|
474
|
+
severity: secretPattern.severity,
|
|
475
|
+
node: node.name,
|
|
476
|
+
message: `${secretPattern.name} detected in node parameters`,
|
|
477
|
+
details: `Found pattern matching ${secretPattern.name}. Hardcoded secrets should be moved to n8n credentials.`,
|
|
478
|
+
remediation: 'Move the secret to n8n credential store and reference it via credentials configuration.',
|
|
479
|
+
owaspCategory: 'A02',
|
|
480
|
+
cwe: 'CWE-798',
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return findings;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Detect injection vulnerabilities
|
|
489
|
+
*/
|
|
490
|
+
detectInjectionRisks(workflow) {
|
|
491
|
+
const findings = [];
|
|
492
|
+
let findingId = 100;
|
|
493
|
+
for (const node of workflow.nodes) {
|
|
494
|
+
// Check SQL nodes
|
|
495
|
+
if (node.type.includes('postgres') || node.type.includes('mysql') || node.type.includes('mssql')) {
|
|
496
|
+
const query = node.parameters.query;
|
|
497
|
+
if (query) {
|
|
498
|
+
for (const pattern of INJECTION_PATTERNS.filter(p => p.type === 'sql_injection')) {
|
|
499
|
+
if (pattern.pattern.test(query)) {
|
|
500
|
+
findings.push({
|
|
501
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
502
|
+
type: 'sql_injection',
|
|
503
|
+
severity: pattern.severity,
|
|
504
|
+
node: node.name,
|
|
505
|
+
message: `SQL injection vulnerability: ${pattern.name}`,
|
|
506
|
+
details: `Query uses string interpolation with user input. Attack vector: ${this.getSQLAttackVector(query)}`,
|
|
507
|
+
remediation: 'Use parameterized queries instead of string concatenation.',
|
|
508
|
+
owaspCategory: 'A03',
|
|
509
|
+
cwe: 'CWE-89',
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Check Execute Command nodes
|
|
516
|
+
if (node.type === 'n8n-nodes-base.executeCommand') {
|
|
517
|
+
const command = node.parameters.command;
|
|
518
|
+
if (command) {
|
|
519
|
+
if (/\{\{\s*\$json\./.test(command)) {
|
|
520
|
+
findings.push({
|
|
521
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
522
|
+
type: 'command_injection',
|
|
523
|
+
severity: 'critical',
|
|
524
|
+
node: node.name,
|
|
525
|
+
message: 'Command injection vulnerability',
|
|
526
|
+
details: `Command includes user-controlled input. Attack vector: User input could contain shell metacharacters.`,
|
|
527
|
+
remediation: 'Avoid using Execute Command with user input. If necessary, sanitize and escape all input.',
|
|
528
|
+
owaspCategory: 'A03',
|
|
529
|
+
cwe: 'CWE-78',
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Check HTTP Request nodes for SSRF
|
|
535
|
+
if (node.type === 'n8n-nodes-base.httpRequest') {
|
|
536
|
+
const url = node.parameters.url;
|
|
537
|
+
if (url && /\{\{\s*\$json\./.test(url)) {
|
|
538
|
+
findings.push({
|
|
539
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
540
|
+
type: 'ssrf',
|
|
541
|
+
severity: 'high',
|
|
542
|
+
node: node.name,
|
|
543
|
+
message: 'Potential SSRF vulnerability',
|
|
544
|
+
details: 'URL is constructed from user input, allowing potential Server-Side Request Forgery.',
|
|
545
|
+
remediation: 'Validate and whitelist allowed URLs. Do not allow user input to control request destination.',
|
|
546
|
+
owaspCategory: 'A10',
|
|
547
|
+
cwe: 'CWE-918',
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
// Check for insecure HTTP
|
|
551
|
+
if (url && url.startsWith('http://') && !url.includes('localhost') && !url.includes('127.0.0.1')) {
|
|
552
|
+
findings.push({
|
|
553
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
554
|
+
type: 'insecure_http',
|
|
555
|
+
severity: 'medium',
|
|
556
|
+
node: node.name,
|
|
557
|
+
message: 'Insecure HTTP connection',
|
|
558
|
+
details: `URL uses HTTP instead of HTTPS: ${url}`,
|
|
559
|
+
remediation: 'Use HTTPS for all external API calls.',
|
|
560
|
+
owaspCategory: 'A02',
|
|
561
|
+
cwe: 'CWE-319',
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return findings;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Audit authentication configurations
|
|
570
|
+
*/
|
|
571
|
+
auditAuthentication(workflow) {
|
|
572
|
+
const findings = [];
|
|
573
|
+
let findingId = 200;
|
|
574
|
+
for (const node of workflow.nodes) {
|
|
575
|
+
// Check webhook authentication
|
|
576
|
+
if (node.type.includes('webhook')) {
|
|
577
|
+
const auth = node.parameters.authentication;
|
|
578
|
+
if (!auth || auth === 'none') {
|
|
579
|
+
findings.push({
|
|
580
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
581
|
+
type: 'unauthenticated_webhook',
|
|
582
|
+
severity: 'high',
|
|
583
|
+
node: node.name,
|
|
584
|
+
message: 'Webhook has no authentication',
|
|
585
|
+
details: 'Anyone can trigger this webhook without credentials.',
|
|
586
|
+
remediation: 'Configure header authentication, basic auth, or JWT validation.',
|
|
587
|
+
owaspCategory: 'A01',
|
|
588
|
+
cwe: 'CWE-306',
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Check for basic auth over HTTP
|
|
593
|
+
if (node.type === 'n8n-nodes-base.httpRequest') {
|
|
594
|
+
const url = node.parameters.url;
|
|
595
|
+
const auth = node.parameters.authentication;
|
|
596
|
+
if (url?.startsWith('http://') && auth === 'basicAuth') {
|
|
597
|
+
findings.push({
|
|
598
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
599
|
+
type: 'weak_auth',
|
|
600
|
+
severity: 'high',
|
|
601
|
+
node: node.name,
|
|
602
|
+
message: 'Basic auth over insecure connection',
|
|
603
|
+
details: 'Basic authentication credentials sent over unencrypted HTTP.',
|
|
604
|
+
remediation: 'Use HTTPS when using basic authentication.',
|
|
605
|
+
owaspCategory: 'A02',
|
|
606
|
+
cwe: 'CWE-523',
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return findings;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Check OWASP Top 10 compliance
|
|
615
|
+
*/
|
|
616
|
+
checkOWASPCompliance(workflow, findings) {
|
|
617
|
+
const categories = {
|
|
618
|
+
'A01_Broken_Access_Control': { status: 'pass', findings: 0 },
|
|
619
|
+
'A02_Cryptographic_Failures': { status: 'pass', findings: 0 },
|
|
620
|
+
'A03_Injection': { status: 'pass', findings: 0 },
|
|
621
|
+
'A04_Insecure_Design': { status: 'pass', findings: 0 },
|
|
622
|
+
'A05_Security_Misconfiguration': { status: 'pass', findings: 0 },
|
|
623
|
+
'A06_Vulnerable_Components': { status: 'pass', findings: 0 },
|
|
624
|
+
'A07_Auth_Failures': { status: 'pass', findings: 0 },
|
|
625
|
+
'A08_Data_Integrity_Failures': { status: 'pass', findings: 0 },
|
|
626
|
+
'A09_Logging_Monitoring_Failures': { status: 'pass', findings: 0 },
|
|
627
|
+
'A10_SSRF': { status: 'pass', findings: 0 },
|
|
628
|
+
};
|
|
629
|
+
// Map findings to OWASP categories
|
|
630
|
+
const owaspMapping = {
|
|
631
|
+
'A01': 'A01_Broken_Access_Control',
|
|
632
|
+
'A02': 'A02_Cryptographic_Failures',
|
|
633
|
+
'A03': 'A03_Injection',
|
|
634
|
+
'A04': 'A04_Insecure_Design',
|
|
635
|
+
'A05': 'A05_Security_Misconfiguration',
|
|
636
|
+
'A06': 'A06_Vulnerable_Components',
|
|
637
|
+
'A07': 'A07_Auth_Failures',
|
|
638
|
+
'A08': 'A08_Data_Integrity_Failures',
|
|
639
|
+
'A09': 'A09_Logging_Monitoring_Failures',
|
|
640
|
+
'A10': 'A10_SSRF',
|
|
641
|
+
};
|
|
642
|
+
for (const finding of findings) {
|
|
643
|
+
if (finding.owaspCategory && owaspMapping[finding.owaspCategory]) {
|
|
644
|
+
const category = owaspMapping[finding.owaspCategory];
|
|
645
|
+
categories[category].findings++;
|
|
646
|
+
if (finding.severity === 'critical' || finding.severity === 'high') {
|
|
647
|
+
categories[category].status = 'fail';
|
|
648
|
+
}
|
|
649
|
+
else if (categories[category].status !== 'fail') {
|
|
650
|
+
categories[category].status = 'warn';
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Calculate compliance score
|
|
655
|
+
const totalCategories = Object.keys(categories).length;
|
|
656
|
+
const passedCategories = Object.values(categories).filter(c => c.status === 'pass').length;
|
|
657
|
+
const score = Math.round((passedCategories / totalCategories) * 100);
|
|
658
|
+
return { score, categories };
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Calculate overall risk score
|
|
662
|
+
*/
|
|
663
|
+
calculateRiskScore(findings) {
|
|
664
|
+
const weights = { critical: 25, high: 15, medium: 8, low: 3, info: 1 };
|
|
665
|
+
let totalPenalty = 0;
|
|
666
|
+
for (const finding of findings) {
|
|
667
|
+
totalPenalty += weights[finding.severity] || 0;
|
|
668
|
+
}
|
|
669
|
+
// Cap at 100
|
|
670
|
+
return Math.max(0, 100 - Math.min(totalPenalty, 100));
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Filter findings by severity threshold
|
|
674
|
+
*/
|
|
675
|
+
filterBySeverity(findings, threshold) {
|
|
676
|
+
const severityOrder = ['info', 'low', 'medium', 'high', 'critical'];
|
|
677
|
+
const thresholdIndex = severityOrder.indexOf(threshold);
|
|
678
|
+
return findings.filter(f => {
|
|
679
|
+
const findingIndex = severityOrder.indexOf(f.severity);
|
|
680
|
+
return findingIndex >= thresholdIndex;
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Get example SQL attack vector
|
|
685
|
+
*/
|
|
686
|
+
getSQLAttackVector(query) {
|
|
687
|
+
if (query.includes("'{{")) {
|
|
688
|
+
return "Input: ' OR '1'='1' -- would bypass authentication";
|
|
689
|
+
}
|
|
690
|
+
if (query.includes('" + $json')) {
|
|
691
|
+
return "Input containing SQL metacharacters could modify query logic";
|
|
692
|
+
}
|
|
693
|
+
return "User input could manipulate SQL query structure";
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Quick security check (just critical and high issues)
|
|
697
|
+
*/
|
|
698
|
+
async quickCheck(workflowId) {
|
|
699
|
+
const result = await this.auditWorkflow(workflowId, {
|
|
700
|
+
severityThreshold: 'high',
|
|
701
|
+
});
|
|
702
|
+
return {
|
|
703
|
+
secure: result.summary.critical === 0 && result.summary.high === 0,
|
|
704
|
+
criticalIssues: result.summary.critical,
|
|
705
|
+
highIssues: result.summary.high,
|
|
706
|
+
topIssue: result.findings[0]?.message || null,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Detect secret leakage at runtime by executing workflow and scanning output
|
|
711
|
+
* This catches secrets that are exposed through execution data, logs, or responses
|
|
712
|
+
*/
|
|
713
|
+
async detectRuntimeSecretLeakage(workflowId, testInput) {
|
|
714
|
+
const findings = [];
|
|
715
|
+
let findingId = 300;
|
|
716
|
+
try {
|
|
717
|
+
// Execute workflow with test input
|
|
718
|
+
const execution = await this.executeWorkflow(workflowId, testInput || {}, {
|
|
719
|
+
waitForCompletion: true,
|
|
720
|
+
timeout: 30000,
|
|
721
|
+
});
|
|
722
|
+
// Wait for completion
|
|
723
|
+
const completedExecution = await this.waitForExecution(execution.id, 30000);
|
|
724
|
+
// Scan all execution output for secrets
|
|
725
|
+
const outputData = JSON.stringify(completedExecution.data || {});
|
|
726
|
+
for (const secretPattern of SECRET_PATTERNS) {
|
|
727
|
+
if (secretPattern.pattern.test(outputData)) {
|
|
728
|
+
// Find which node leaked the secret
|
|
729
|
+
const leakingNode = this.findLeakingNode(completedExecution, secretPattern.pattern);
|
|
730
|
+
findings.push({
|
|
731
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
732
|
+
type: 'secret_leakage',
|
|
733
|
+
severity: 'critical',
|
|
734
|
+
node: leakingNode || 'unknown',
|
|
735
|
+
message: `Runtime secret leakage detected: ${secretPattern.name}`,
|
|
736
|
+
details: `Secret of type "${secretPattern.name}" was found in workflow execution output. This may expose sensitive data to downstream systems or logs.`,
|
|
737
|
+
remediation: 'Ensure secrets are never passed through workflow data. Use n8n credentials and reference them directly in nodes.',
|
|
738
|
+
owaspCategory: 'A02',
|
|
739
|
+
cwe: 'CWE-200',
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// Check for PII in output (email, phone, SSN patterns)
|
|
744
|
+
const piiPatterns = [
|
|
745
|
+
{ pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, name: 'Email Address' },
|
|
746
|
+
{ pattern: /\b\d{3}-\d{2}-\d{4}\b/, name: 'SSN' },
|
|
747
|
+
{ pattern: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, name: 'Phone Number' },
|
|
748
|
+
{ pattern: /\b\d{16}\b/, name: 'Credit Card Number' },
|
|
749
|
+
];
|
|
750
|
+
for (const pii of piiPatterns) {
|
|
751
|
+
if (pii.pattern.test(outputData)) {
|
|
752
|
+
findings.push({
|
|
753
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
754
|
+
type: 'pii_exposure',
|
|
755
|
+
severity: 'high',
|
|
756
|
+
node: 'workflow_output',
|
|
757
|
+
message: `PII detected in execution output: ${pii.name}`,
|
|
758
|
+
details: `Sensitive PII (${pii.name}) was found in workflow execution data.`,
|
|
759
|
+
remediation: 'Mask or redact PII before passing through workflow. Use data transformation nodes.',
|
|
760
|
+
owaspCategory: 'A02',
|
|
761
|
+
cwe: 'CWE-359',
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// Check for secrets in error messages
|
|
766
|
+
const errorData = completedExecution.data?.resultData?.error;
|
|
767
|
+
if (errorData) {
|
|
768
|
+
const errorStr = JSON.stringify(errorData);
|
|
769
|
+
for (const secretPattern of SECRET_PATTERNS.slice(0, 10)) { // Check top patterns
|
|
770
|
+
if (secretPattern.pattern.test(errorStr)) {
|
|
771
|
+
findings.push({
|
|
772
|
+
id: `SEC-${String(findingId++).padStart(3, '0')}`,
|
|
773
|
+
type: 'secret_in_error',
|
|
774
|
+
severity: 'critical',
|
|
775
|
+
node: errorData.node || 'unknown',
|
|
776
|
+
message: `Secret exposed in error message: ${secretPattern.name}`,
|
|
777
|
+
details: 'Secrets should never appear in error messages as they may be logged or displayed.',
|
|
778
|
+
remediation: 'Catch errors and sanitize messages before propagation.',
|
|
779
|
+
owaspCategory: 'A02',
|
|
780
|
+
cwe: 'CWE-209',
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
// Log but don't fail the entire audit
|
|
788
|
+
console.error('Runtime security check failed:', error);
|
|
789
|
+
}
|
|
790
|
+
return findings;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Find which node is leaking the secret
|
|
794
|
+
*/
|
|
795
|
+
findLeakingNode(execution, pattern) {
|
|
796
|
+
const runData = execution.data?.resultData?.runData;
|
|
797
|
+
if (!runData)
|
|
798
|
+
return null;
|
|
799
|
+
for (const [nodeName, nodeRuns] of Object.entries(runData)) {
|
|
800
|
+
for (const run of nodeRuns) {
|
|
801
|
+
const nodeOutput = JSON.stringify(run.data || {});
|
|
802
|
+
if (pattern.test(nodeOutput)) {
|
|
803
|
+
return nodeName;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Wait for workflow execution to complete
|
|
811
|
+
*/
|
|
812
|
+
async waitForExecution(executionId, timeoutMs) {
|
|
813
|
+
const startTime = Date.now();
|
|
814
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
815
|
+
const execution = await this.getExecution(executionId);
|
|
816
|
+
if (execution.status !== 'running' && execution.status !== 'waiting') {
|
|
817
|
+
return execution;
|
|
818
|
+
}
|
|
819
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
820
|
+
}
|
|
821
|
+
throw new Error(`Execution ${executionId} timed out after ${timeoutMs}ms`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
exports.N8nSecurityAuditorAgent = N8nSecurityAuditorAgent;
|
|
825
|
+
//# sourceMappingURL=N8nSecurityAuditorAgent.js.map
|