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,599 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: n8n-security-testing
|
|
3
|
+
description: "Credential exposure detection, OAuth flow validation, API key management testing, and data sanitization verification for n8n workflows. Use when validating n8n workflow security."
|
|
4
|
+
category: n8n-testing
|
|
5
|
+
priority: critical
|
|
6
|
+
tokenEstimate: 1100
|
|
7
|
+
agents: [n8n-integration-test]
|
|
8
|
+
implementation_status: production
|
|
9
|
+
optimization_version: 1.0
|
|
10
|
+
last_optimized: 2025-12-15
|
|
11
|
+
dependencies: []
|
|
12
|
+
quick_reference_card: true
|
|
13
|
+
tags: [n8n, security, credentials, oauth, api-keys, encryption, testing]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# n8n Security Testing
|
|
17
|
+
|
|
18
|
+
<default_to_action>
|
|
19
|
+
When testing n8n security:
|
|
20
|
+
1. SCAN for credential exposure in workflows
|
|
21
|
+
2. VERIFY encryption of sensitive data
|
|
22
|
+
3. TEST OAuth token handling
|
|
23
|
+
4. CHECK for insecure data transmission
|
|
24
|
+
5. VALIDATE input sanitization
|
|
25
|
+
|
|
26
|
+
**Quick Security Checklist:**
|
|
27
|
+
- No credentials in workflow JSON
|
|
28
|
+
- No credentials in execution logs
|
|
29
|
+
- OAuth tokens properly encrypted
|
|
30
|
+
- API keys not in version control
|
|
31
|
+
- Webhook authentication enabled
|
|
32
|
+
- Input data sanitized
|
|
33
|
+
|
|
34
|
+
**Critical Success Factors:**
|
|
35
|
+
- Scan all workflow exports
|
|
36
|
+
- Test credential rotation
|
|
37
|
+
- Verify encryption at rest
|
|
38
|
+
- Check audit logging
|
|
39
|
+
</default_to_action>
|
|
40
|
+
|
|
41
|
+
## Quick Reference Card
|
|
42
|
+
|
|
43
|
+
### Security Risk Areas
|
|
44
|
+
|
|
45
|
+
| Area | Risk Level | Testing Focus |
|
|
46
|
+
|------|------------|---------------|
|
|
47
|
+
| **Credential Storage** | Critical | Encryption, exposure |
|
|
48
|
+
| **Webhook Security** | High | Authentication, validation |
|
|
49
|
+
| **Expression Injection** | High | Input sanitization |
|
|
50
|
+
| **Data Leakage** | Medium | Logging, error messages |
|
|
51
|
+
| **OAuth Flows** | Medium | Token handling, refresh |
|
|
52
|
+
|
|
53
|
+
### Credential Types
|
|
54
|
+
|
|
55
|
+
| Type | Exposure Risk | Rotation |
|
|
56
|
+
|------|---------------|----------|
|
|
57
|
+
| **API Keys** | High if exposed | Manual |
|
|
58
|
+
| **OAuth Tokens** | Medium (short-lived) | Automatic |
|
|
59
|
+
| **Passwords** | Critical | Manual |
|
|
60
|
+
| **Webhooks** | Medium | Generate new |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Credential Security Testing
|
|
65
|
+
|
|
66
|
+
### Scan for Exposed Credentials
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Scan workflow JSON for credential exposure
|
|
70
|
+
async function scanForExposedCredentials(workflowId: string): Promise<CredentialScanResult> {
|
|
71
|
+
const workflow = await getWorkflow(workflowId);
|
|
72
|
+
const workflowJson = JSON.stringify(workflow, null, 2);
|
|
73
|
+
|
|
74
|
+
const sensitivePatterns = [
|
|
75
|
+
// API Keys
|
|
76
|
+
{ name: 'Generic API Key', pattern: /api[_-]?key["\s:=]+["']?([a-zA-Z0-9_-]{20,})["']?/gi },
|
|
77
|
+
{ name: 'AWS Access Key', pattern: /AKIA[0-9A-Z]{16}/g },
|
|
78
|
+
{ name: 'AWS Secret Key', pattern: /[a-zA-Z0-9/+=]{40}/g },
|
|
79
|
+
// Tokens
|
|
80
|
+
{ name: 'Bearer Token', pattern: /bearer\s+[a-zA-Z0-9_-]{20,}/gi },
|
|
81
|
+
{ name: 'JWT Token', pattern: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g },
|
|
82
|
+
{ name: 'Slack Token', pattern: /xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/g },
|
|
83
|
+
// Passwords
|
|
84
|
+
{ name: 'Password Field', pattern: /"password":\s*"[^"]+"/gi },
|
|
85
|
+
{ name: 'Secret Field', pattern: /"secret":\s*"[^"]+"/gi },
|
|
86
|
+
// OAuth
|
|
87
|
+
{ name: 'Client Secret', pattern: /client[_-]?secret["\s:=]+["']?([a-zA-Z0-9_-]{20,})["']?/gi },
|
|
88
|
+
{ name: 'Refresh Token', pattern: /refresh[_-]?token["\s:=]+["']?([a-zA-Z0-9_-]{20,})["']?/gi }
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const findings: CredentialFinding[] = [];
|
|
92
|
+
|
|
93
|
+
for (const pattern of sensitivePatterns) {
|
|
94
|
+
const matches = workflowJson.match(pattern.pattern);
|
|
95
|
+
if (matches) {
|
|
96
|
+
for (const match of matches) {
|
|
97
|
+
findings.push({
|
|
98
|
+
type: pattern.name,
|
|
99
|
+
location: findLocationInWorkflow(workflow, match),
|
|
100
|
+
severity: 'CRITICAL',
|
|
101
|
+
recommendation: `Remove ${pattern.name} from workflow. Use n8n credentials instead.`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
workflowId,
|
|
109
|
+
scanned: true,
|
|
110
|
+
findingsCount: findings.length,
|
|
111
|
+
findings,
|
|
112
|
+
secure: findings.length === 0
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Verify Credential Encryption
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Verify credentials are encrypted at rest
|
|
121
|
+
async function verifyCredentialEncryption(credentialId: string): Promise<EncryptionResult> {
|
|
122
|
+
// Get credential metadata (not the actual credential)
|
|
123
|
+
const credential = await getCredentialMetadata(credentialId);
|
|
124
|
+
|
|
125
|
+
// Check if credential data is encrypted
|
|
126
|
+
const encryptionChecks = {
|
|
127
|
+
// Check if stored data looks encrypted (not plain text)
|
|
128
|
+
isEncrypted: !isPlainText(credential.data),
|
|
129
|
+
// Check encryption algorithm
|
|
130
|
+
algorithm: credential.encryptionAlgorithm || 'unknown',
|
|
131
|
+
// Check key derivation
|
|
132
|
+
keyDerivation: credential.keyDerivation || 'unknown',
|
|
133
|
+
// Check if using instance encryption key
|
|
134
|
+
instanceEncryption: credential.useInstanceKey || false
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
credentialId,
|
|
139
|
+
credentialName: credential.name,
|
|
140
|
+
credentialType: credential.type,
|
|
141
|
+
encryption: encryptionChecks,
|
|
142
|
+
secure: encryptionChecks.isEncrypted && encryptionChecks.algorithm !== 'unknown',
|
|
143
|
+
recommendations: generateEncryptionRecommendations(encryptionChecks)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check if data appears to be plain text
|
|
148
|
+
function isPlainText(data: string): boolean {
|
|
149
|
+
// Plain text credentials often have recognizable patterns
|
|
150
|
+
const plainTextPatterns = [
|
|
151
|
+
/^[a-zA-Z0-9_-]+$/, // Simple alphanumeric
|
|
152
|
+
/^sk-[a-zA-Z0-9]+$/, // API key format
|
|
153
|
+
/^Bearer\s/, // Bearer token
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
return plainTextPatterns.some(p => p.test(data));
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Test Credential Rotation
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// Test credential rotation process
|
|
164
|
+
async function testCredentialRotation(credentialId: string): Promise<RotationTestResult> {
|
|
165
|
+
const credential = await getCredentialMetadata(credentialId);
|
|
166
|
+
|
|
167
|
+
const rotationTests = {
|
|
168
|
+
// Check if credential has rotation metadata
|
|
169
|
+
hasRotationSchedule: !!credential.rotationSchedule,
|
|
170
|
+
lastRotated: credential.lastRotatedAt,
|
|
171
|
+
rotationDue: isRotationDue(credential),
|
|
172
|
+
|
|
173
|
+
// Test OAuth token refresh
|
|
174
|
+
oauthRefresh: credential.type.includes('oauth')
|
|
175
|
+
? await testOAuthRefresh(credentialId)
|
|
176
|
+
: null,
|
|
177
|
+
|
|
178
|
+
// Check credential age
|
|
179
|
+
credentialAge: calculateAge(credential.createdAt),
|
|
180
|
+
isStale: calculateAge(credential.createdAt) > 90 // 90 days
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
credentialId,
|
|
185
|
+
rotationTests,
|
|
186
|
+
recommendations: generateRotationRecommendations(rotationTests)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Test OAuth token refresh
|
|
191
|
+
async function testOAuthRefresh(credentialId: string): Promise<OAuthRefreshResult> {
|
|
192
|
+
try {
|
|
193
|
+
// Trigger refresh
|
|
194
|
+
const refreshed = await refreshCredential(credentialId);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
newExpiry: refreshed.expiresAt,
|
|
199
|
+
refreshedAt: new Date()
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return {
|
|
203
|
+
success: false,
|
|
204
|
+
error: error.message,
|
|
205
|
+
recommendation: 'Re-authorize OAuth connection'
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Webhook Security Testing
|
|
214
|
+
|
|
215
|
+
### Authentication Testing
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Test webhook authentication enforcement
|
|
219
|
+
async function testWebhookAuthentication(webhookUrl: string): Promise<WebhookAuthResult> {
|
|
220
|
+
const authTests = [
|
|
221
|
+
// No authentication
|
|
222
|
+
{
|
|
223
|
+
name: 'No Auth',
|
|
224
|
+
headers: {},
|
|
225
|
+
expectedStatus: 401
|
|
226
|
+
},
|
|
227
|
+
// Invalid Basic Auth
|
|
228
|
+
{
|
|
229
|
+
name: 'Invalid Basic Auth',
|
|
230
|
+
headers: { 'Authorization': 'Basic aW52YWxpZDppbnZhbGlk' },
|
|
231
|
+
expectedStatus: 401
|
|
232
|
+
},
|
|
233
|
+
// Invalid Bearer Token
|
|
234
|
+
{
|
|
235
|
+
name: 'Invalid Bearer',
|
|
236
|
+
headers: { 'Authorization': 'Bearer invalid-token-12345' },
|
|
237
|
+
expectedStatus: 401
|
|
238
|
+
},
|
|
239
|
+
// Invalid Header Auth
|
|
240
|
+
{
|
|
241
|
+
name: 'Invalid Header Auth',
|
|
242
|
+
headers: { 'X-API-Key': 'invalid-key' },
|
|
243
|
+
expectedStatus: 401
|
|
244
|
+
}
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const results: AuthTestResult[] = [];
|
|
248
|
+
|
|
249
|
+
for (const test of authTests) {
|
|
250
|
+
const response = await fetch(webhookUrl, {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {
|
|
253
|
+
'Content-Type': 'application/json',
|
|
254
|
+
...test.headers
|
|
255
|
+
},
|
|
256
|
+
body: '{}'
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
results.push({
|
|
260
|
+
test: test.name,
|
|
261
|
+
status: response.status,
|
|
262
|
+
passed: response.status === test.expectedStatus,
|
|
263
|
+
actualStatus: response.status,
|
|
264
|
+
expectedStatus: test.expectedStatus
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check if webhook has ANY auth
|
|
269
|
+
const noAuthResponse = results.find(r => r.test === 'No Auth');
|
|
270
|
+
const webhookHasAuth = noAuthResponse?.status === 401;
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
webhookUrl,
|
|
274
|
+
hasAuthentication: webhookHasAuth,
|
|
275
|
+
testResults: results,
|
|
276
|
+
allTestsPassed: results.every(r => r.passed),
|
|
277
|
+
recommendation: !webhookHasAuth
|
|
278
|
+
? 'CRITICAL: Enable authentication on webhook'
|
|
279
|
+
: null
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Input Validation Testing
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// Test webhook input validation
|
|
288
|
+
async function testWebhookInputValidation(webhookUrl: string): Promise<InputValidationResult> {
|
|
289
|
+
const maliciousPayloads = [
|
|
290
|
+
// XSS attempts
|
|
291
|
+
{
|
|
292
|
+
name: 'XSS Script Tag',
|
|
293
|
+
payload: { text: '<script>alert("xss")</script>' },
|
|
294
|
+
check: 'sanitized'
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'XSS Event Handler',
|
|
298
|
+
payload: { text: '<img onerror="alert(1)" src="x">' },
|
|
299
|
+
check: 'sanitized'
|
|
300
|
+
},
|
|
301
|
+
// SQL Injection
|
|
302
|
+
{
|
|
303
|
+
name: 'SQL Injection',
|
|
304
|
+
payload: { id: "1; DROP TABLE users; --" },
|
|
305
|
+
check: 'escaped'
|
|
306
|
+
},
|
|
307
|
+
// Command Injection
|
|
308
|
+
{
|
|
309
|
+
name: 'Command Injection',
|
|
310
|
+
payload: { filename: '; rm -rf /' },
|
|
311
|
+
check: 'rejected'
|
|
312
|
+
},
|
|
313
|
+
// Path Traversal
|
|
314
|
+
{
|
|
315
|
+
name: 'Path Traversal',
|
|
316
|
+
payload: { path: '../../../etc/passwd' },
|
|
317
|
+
check: 'rejected'
|
|
318
|
+
},
|
|
319
|
+
// JSON Injection
|
|
320
|
+
{
|
|
321
|
+
name: 'JSON Injection',
|
|
322
|
+
payload: { data: '{"admin": true}' },
|
|
323
|
+
check: 'escaped'
|
|
324
|
+
},
|
|
325
|
+
// Oversized payload
|
|
326
|
+
{
|
|
327
|
+
name: 'Oversized Payload',
|
|
328
|
+
payload: { data: 'x'.repeat(10000000) }, // 10MB
|
|
329
|
+
check: 'rejected'
|
|
330
|
+
}
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const results: ValidationTestResult[] = [];
|
|
334
|
+
|
|
335
|
+
for (const test of maliciousPayloads) {
|
|
336
|
+
try {
|
|
337
|
+
const response = await fetch(webhookUrl, {
|
|
338
|
+
method: 'POST',
|
|
339
|
+
headers: { 'Content-Type': 'application/json' },
|
|
340
|
+
body: JSON.stringify(test.payload)
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const responseBody = await response.text();
|
|
344
|
+
|
|
345
|
+
results.push({
|
|
346
|
+
test: test.name,
|
|
347
|
+
status: response.status,
|
|
348
|
+
handled: response.status !== 500, // Not a server error
|
|
349
|
+
sanitized: !responseBody.includes(test.payload.text || test.payload.data),
|
|
350
|
+
recommendation: response.status === 500
|
|
351
|
+
? `Input not handled safely: ${test.name}`
|
|
352
|
+
: null
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
results.push({
|
|
356
|
+
test: test.name,
|
|
357
|
+
handled: false,
|
|
358
|
+
error: error.message
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
webhookUrl,
|
|
365
|
+
testsRun: maliciousPayloads.length,
|
|
366
|
+
passed: results.filter(r => r.handled).length,
|
|
367
|
+
failed: results.filter(r => !r.handled).length,
|
|
368
|
+
results,
|
|
369
|
+
secure: results.every(r => r.handled)
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Expression Security Testing
|
|
377
|
+
|
|
378
|
+
### Detect Dangerous Expressions
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// Scan expressions for security vulnerabilities
|
|
382
|
+
async function scanExpressionsForSecurity(workflowId: string): Promise<ExpressionSecurityResult> {
|
|
383
|
+
const workflow = await getWorkflow(workflowId);
|
|
384
|
+
const expressions = extractExpressions(workflow);
|
|
385
|
+
|
|
386
|
+
const dangerousPatterns = [
|
|
387
|
+
// Code execution
|
|
388
|
+
{ name: 'eval()', pattern: /eval\s*\(/g, severity: 'CRITICAL' },
|
|
389
|
+
{ name: 'Function()', pattern: /new\s+Function\s*\(/g, severity: 'CRITICAL' },
|
|
390
|
+
{ name: 'setTimeout string', pattern: /setTimeout\s*\(\s*["'`]/g, severity: 'HIGH' },
|
|
391
|
+
{ name: 'setInterval string', pattern: /setInterval\s*\(\s*["'`]/g, severity: 'HIGH' },
|
|
392
|
+
|
|
393
|
+
// File system access
|
|
394
|
+
{ name: 'require()', pattern: /require\s*\(/g, severity: 'HIGH' },
|
|
395
|
+
{ name: 'import()', pattern: /import\s*\(/g, severity: 'HIGH' },
|
|
396
|
+
{ name: 'fs access', pattern: /\bfs\./g, severity: 'HIGH' },
|
|
397
|
+
|
|
398
|
+
// Process/child execution
|
|
399
|
+
{ name: 'child_process', pattern: /child_process/g, severity: 'CRITICAL' },
|
|
400
|
+
{ name: 'process.', pattern: /process\./g, severity: 'MEDIUM' },
|
|
401
|
+
{ name: 'exec()', pattern: /exec\s*\(/g, severity: 'CRITICAL' },
|
|
402
|
+
{ name: 'spawn()', pattern: /spawn\s*\(/g, severity: 'CRITICAL' },
|
|
403
|
+
|
|
404
|
+
// Network access
|
|
405
|
+
{ name: 'fetch()', pattern: /fetch\s*\(/g, severity: 'MEDIUM' },
|
|
406
|
+
{ name: 'XMLHttpRequest', pattern: /XMLHttpRequest/g, severity: 'MEDIUM' },
|
|
407
|
+
|
|
408
|
+
// Prototype pollution
|
|
409
|
+
{ name: '__proto__', pattern: /__proto__/g, severity: 'HIGH' },
|
|
410
|
+
{ name: 'constructor.prototype', pattern: /constructor\.prototype/g, severity: 'HIGH' }
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
const findings: SecurityFinding[] = [];
|
|
414
|
+
|
|
415
|
+
for (const expr of expressions) {
|
|
416
|
+
for (const pattern of dangerousPatterns) {
|
|
417
|
+
if (pattern.pattern.test(expr.expression)) {
|
|
418
|
+
findings.push({
|
|
419
|
+
node: expr.nodeName,
|
|
420
|
+
parameter: expr.parameter,
|
|
421
|
+
expression: expr.expression,
|
|
422
|
+
pattern: pattern.name,
|
|
423
|
+
severity: pattern.severity,
|
|
424
|
+
recommendation: `Remove ${pattern.name} from expression. Use safer alternatives.`
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
workflowId,
|
|
432
|
+
expressionsScanned: expressions.length,
|
|
433
|
+
findings,
|
|
434
|
+
secure: findings.length === 0,
|
|
435
|
+
criticalIssues: findings.filter(f => f.severity === 'CRITICAL').length,
|
|
436
|
+
highIssues: findings.filter(f => f.severity === 'HIGH').length
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Data Leakage Testing
|
|
444
|
+
|
|
445
|
+
### Scan Execution Logs
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// Scan execution logs for credential leakage
|
|
449
|
+
async function scanExecutionLogs(workflowId: string, executionCount: number = 10): Promise<LogScanResult> {
|
|
450
|
+
const executions = await getRecentExecutions(workflowId, executionCount);
|
|
451
|
+
const findings: LogFinding[] = [];
|
|
452
|
+
|
|
453
|
+
const sensitivePatterns = [
|
|
454
|
+
{ name: 'Password', pattern: /password["\s:=]+["']?[^"'\s]+["']?/gi },
|
|
455
|
+
{ name: 'API Key', pattern: /api[_-]?key["\s:=]+["']?[^"'\s]{20,}["']?/gi },
|
|
456
|
+
{ name: 'Token', pattern: /token["\s:=]+["']?[a-zA-Z0-9_-]{20,}["']?/gi },
|
|
457
|
+
{ name: 'Secret', pattern: /secret["\s:=]+["']?[^"'\s]+["']?/gi },
|
|
458
|
+
{ name: 'Authorization Header', pattern: /authorization["\s:]+["']?(bearer|basic)\s+[^"'\s]+["']?/gi }
|
|
459
|
+
];
|
|
460
|
+
|
|
461
|
+
for (const execution of executions) {
|
|
462
|
+
const logString = JSON.stringify(execution.data, null, 2);
|
|
463
|
+
|
|
464
|
+
for (const pattern of sensitivePatterns) {
|
|
465
|
+
const matches = logString.match(pattern.pattern);
|
|
466
|
+
if (matches) {
|
|
467
|
+
findings.push({
|
|
468
|
+
executionId: execution.id,
|
|
469
|
+
type: pattern.name,
|
|
470
|
+
matchCount: matches.length,
|
|
471
|
+
severity: 'HIGH',
|
|
472
|
+
recommendation: `Mask ${pattern.name} in logs`
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
workflowId,
|
|
480
|
+
executionsScanned: executions.length,
|
|
481
|
+
findings,
|
|
482
|
+
secure: findings.length === 0,
|
|
483
|
+
recommendation: findings.length > 0
|
|
484
|
+
? 'Enable credential masking in n8n settings'
|
|
485
|
+
: null
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Check Error Message Exposure
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// Check if error messages expose sensitive information
|
|
494
|
+
async function checkErrorMessageSecurity(workflowId: string): Promise<ErrorMessageResult> {
|
|
495
|
+
// Trigger intentional errors
|
|
496
|
+
const errorScenarios = [
|
|
497
|
+
{ name: 'Invalid credentials', inject: { credentials: null } },
|
|
498
|
+
{ name: 'Invalid endpoint', inject: { url: 'https://invalid' } },
|
|
499
|
+
{ name: 'Database error', inject: { query: 'INVALID SQL' } }
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
const findings: ErrorFinding[] = [];
|
|
503
|
+
|
|
504
|
+
for (const scenario of errorScenarios) {
|
|
505
|
+
try {
|
|
506
|
+
await executeWithError(workflowId, scenario.inject);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
const errorMessage = error.message;
|
|
509
|
+
|
|
510
|
+
// Check for sensitive data in error
|
|
511
|
+
const sensitiveData = [
|
|
512
|
+
{ name: 'Connection string', pattern: /mongodb:\/\/[^@]+@/i },
|
|
513
|
+
{ name: 'Password in URL', pattern: /:\/\/[^:]+:[^@]+@/i },
|
|
514
|
+
{ name: 'Full file path', pattern: /\/(?:home|Users|var)\/[^\s]+/i },
|
|
515
|
+
{ name: 'Stack trace', pattern: /at\s+\w+\s+\([^)]+\)/i },
|
|
516
|
+
{ name: 'Internal IP', pattern: /\b(?:10|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\.\d+\.\d+\b/i }
|
|
517
|
+
];
|
|
518
|
+
|
|
519
|
+
for (const check of sensitiveData) {
|
|
520
|
+
if (check.pattern.test(errorMessage)) {
|
|
521
|
+
findings.push({
|
|
522
|
+
scenario: scenario.name,
|
|
523
|
+
exposedData: check.name,
|
|
524
|
+
severity: 'MEDIUM',
|
|
525
|
+
recommendation: `Sanitize ${check.name} from error messages`
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
workflowId,
|
|
534
|
+
scenariosTested: errorScenarios.length,
|
|
535
|
+
findings,
|
|
536
|
+
secure: findings.length === 0
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## Security Report Template
|
|
544
|
+
|
|
545
|
+
```markdown
|
|
546
|
+
# n8n Security Audit Report
|
|
547
|
+
|
|
548
|
+
## Summary
|
|
549
|
+
| Category | Status | Findings |
|
|
550
|
+
|----------|--------|----------|
|
|
551
|
+
| Credential Security | PASS/FAIL | X issues |
|
|
552
|
+
| Webhook Security | PASS/FAIL | X issues |
|
|
553
|
+
| Expression Security | PASS/FAIL | X issues |
|
|
554
|
+
| Data Leakage | PASS/FAIL | X issues |
|
|
555
|
+
|
|
556
|
+
## Critical Findings
|
|
557
|
+
|
|
558
|
+
### CRIT-001: API Key Exposed in Workflow
|
|
559
|
+
- **Location:** HTTP Request node, URL parameter
|
|
560
|
+
- **Impact:** Credential theft, unauthorized access
|
|
561
|
+
- **Fix:** Move to n8n credentials store
|
|
562
|
+
|
|
563
|
+
### CRIT-002: eval() in Expression
|
|
564
|
+
- **Location:** Set node, custom field
|
|
565
|
+
- **Impact:** Remote code execution
|
|
566
|
+
- **Fix:** Remove eval, use explicit logic
|
|
567
|
+
|
|
568
|
+
## Recommendations
|
|
569
|
+
|
|
570
|
+
1. **Enable webhook authentication** - All public webhooks
|
|
571
|
+
2. **Rotate exposed credentials** - Immediately
|
|
572
|
+
3. **Enable log masking** - For all credentials
|
|
573
|
+
4. **Regular security scans** - Weekly automated scans
|
|
574
|
+
|
|
575
|
+
## Compliance Status
|
|
576
|
+
- OWASP Top 10: X/10 addressed
|
|
577
|
+
- SOC 2: Partially compliant
|
|
578
|
+
- GDPR: Review data handling
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## Related Skills
|
|
584
|
+
- [n8n-workflow-testing-fundamentals](../n8n-workflow-testing-fundamentals/)
|
|
585
|
+
- [n8n-integration-testing-patterns](../n8n-integration-testing-patterns/)
|
|
586
|
+
- [compliance-testing](../compliance-testing/)
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Remember
|
|
591
|
+
|
|
592
|
+
**n8n handles sensitive credentials** for 400+ integrations. Security testing requires:
|
|
593
|
+
- Credential exposure scanning
|
|
594
|
+
- Encryption verification
|
|
595
|
+
- Webhook authentication testing
|
|
596
|
+
- Expression security analysis
|
|
597
|
+
- Data leakage detection
|
|
598
|
+
|
|
599
|
+
**Critical practices:** Never expose credentials in workflow JSON. Enable webhook authentication. Mask sensitive data in logs. Rotate credentials regularly. Scan expressions for dangerous functions.
|