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,986 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* N8nComplianceValidatorAgent
|
|
4
|
+
*
|
|
5
|
+
* Regulatory and policy compliance validation:
|
|
6
|
+
* - GDPR compliance checking
|
|
7
|
+
* - Data retention policy validation
|
|
8
|
+
* - Audit trail verification
|
|
9
|
+
* - Privacy controls validation
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.N8nComplianceValidatorAgent = void 0;
|
|
13
|
+
const N8nBaseAgent_1 = require("./N8nBaseAgent");
|
|
14
|
+
// GDPR-specific patterns
|
|
15
|
+
const GDPR_CONTROLS = [
|
|
16
|
+
{ id: 'GDPR-5.1', name: 'Lawfulness, fairness and transparency', category: 'principles' },
|
|
17
|
+
{ id: 'GDPR-5.2', name: 'Purpose limitation', category: 'principles' },
|
|
18
|
+
{ id: 'GDPR-5.3', name: 'Data minimization', category: 'principles' },
|
|
19
|
+
{ id: 'GDPR-5.4', name: 'Accuracy', category: 'principles' },
|
|
20
|
+
{ id: 'GDPR-5.5', name: 'Storage limitation', category: 'principles' },
|
|
21
|
+
{ id: 'GDPR-5.6', name: 'Integrity and confidentiality', category: 'security' },
|
|
22
|
+
{ id: 'GDPR-32', name: 'Security of processing', category: 'security' },
|
|
23
|
+
{ id: 'GDPR-33', name: 'Data breach notification', category: 'incident' },
|
|
24
|
+
{ id: 'GDPR-35', name: 'Data protection impact assessment', category: 'dpia' },
|
|
25
|
+
];
|
|
26
|
+
// PII detection patterns
|
|
27
|
+
const PII_PATTERNS = {
|
|
28
|
+
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/,
|
|
29
|
+
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/,
|
|
30
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/,
|
|
31
|
+
creditCard: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/,
|
|
32
|
+
ipAddress: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/,
|
|
33
|
+
name: /\b(first_?name|last_?name|full_?name|name)\b/i,
|
|
34
|
+
address: /\b(address|street|city|state|zip|postal)\b/i,
|
|
35
|
+
dob: /\b(dob|date_?of_?birth|birth_?date|birthday)\b/i,
|
|
36
|
+
};
|
|
37
|
+
class N8nComplianceValidatorAgent extends N8nBaseAgent_1.N8nBaseAgent {
|
|
38
|
+
constructor(config) {
|
|
39
|
+
const capabilities = [
|
|
40
|
+
{
|
|
41
|
+
name: 'gdpr-compliance',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
description: 'GDPR compliance validation',
|
|
44
|
+
parameters: {},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'data-classification',
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
description: 'Classify and detect sensitive data',
|
|
50
|
+
parameters: {},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'audit-validation',
|
|
54
|
+
version: '1.0.0',
|
|
55
|
+
description: 'Validate audit trail configuration',
|
|
56
|
+
parameters: {},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'policy-enforcement',
|
|
60
|
+
version: '1.0.0',
|
|
61
|
+
description: 'Enforce data handling policies',
|
|
62
|
+
parameters: {},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
super({
|
|
66
|
+
...config,
|
|
67
|
+
type: 'n8n-compliance-validator',
|
|
68
|
+
capabilities: [...capabilities, ...(config.capabilities || [])],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async performTask(task) {
|
|
72
|
+
const complianceTask = task;
|
|
73
|
+
if (complianceTask.type !== 'compliance-validation') {
|
|
74
|
+
throw new Error(`Unsupported task type: ${complianceTask.type}`);
|
|
75
|
+
}
|
|
76
|
+
return this.validateCompliance(complianceTask.target, complianceTask.options);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate compliance for workflow
|
|
80
|
+
*
|
|
81
|
+
* PRODUCTION DEFAULT: Runtime PII tracing is ENABLED by default.
|
|
82
|
+
* This ensures actual data flow is analyzed, not just static configuration.
|
|
83
|
+
* Set executeRuntimeTracing: false to disable if workflow cannot be executed.
|
|
84
|
+
*/
|
|
85
|
+
async validateCompliance(workflowId, options) {
|
|
86
|
+
const workflow = await this.getWorkflow(workflowId);
|
|
87
|
+
const frameworks = options?.frameworks || ['GDPR'];
|
|
88
|
+
// Validate each framework (static analysis)
|
|
89
|
+
const frameworkResults = [];
|
|
90
|
+
for (const framework of frameworks) {
|
|
91
|
+
const result = this.validateFramework(workflow, framework);
|
|
92
|
+
frameworkResults.push(result);
|
|
93
|
+
}
|
|
94
|
+
// Check data handling (static analysis)
|
|
95
|
+
const dataHandling = options?.checkDataHandling !== false
|
|
96
|
+
? this.validateDataHandling(workflow)
|
|
97
|
+
: this.getDefaultDataHandling();
|
|
98
|
+
// Check audit trail (static analysis)
|
|
99
|
+
const auditTrail = options?.checkAuditTrail !== false
|
|
100
|
+
? this.validateAuditTrail(workflow)
|
|
101
|
+
: this.getDefaultAuditTrail();
|
|
102
|
+
// Check retention policy (static analysis)
|
|
103
|
+
const retentionPolicy = options?.checkRetention !== false
|
|
104
|
+
? this.validateRetentionPolicy(workflow)
|
|
105
|
+
: this.getDefaultRetentionPolicy();
|
|
106
|
+
// Runtime PII flow tracing - ENABLED BY DEFAULT
|
|
107
|
+
// This is critical for production - static analysis alone misses runtime data flows
|
|
108
|
+
// Set executeRuntimeTracing: false explicitly to skip
|
|
109
|
+
let runtimeFindings = [];
|
|
110
|
+
if (options?.executeRuntimeTracing !== false) {
|
|
111
|
+
try {
|
|
112
|
+
runtimeFindings = await this.executeRuntimePIITracing(workflowId, options?.testInput);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
// If runtime execution fails, emit warning but continue with static results
|
|
116
|
+
this.emitEvent('compliance.runtime.skipped', {
|
|
117
|
+
workflowId,
|
|
118
|
+
reason: error instanceof Error ? error.message : 'Runtime PII tracing failed',
|
|
119
|
+
note: 'Static compliance analysis completed, but runtime PII flow tracing was skipped',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Collect all findings
|
|
124
|
+
const findings = [
|
|
125
|
+
...frameworkResults.flatMap(f => f.findings),
|
|
126
|
+
...this.dataHandlingFindings(dataHandling),
|
|
127
|
+
...this.auditTrailFindings(auditTrail),
|
|
128
|
+
...runtimeFindings,
|
|
129
|
+
...this.retentionFindings(retentionPolicy),
|
|
130
|
+
];
|
|
131
|
+
// Calculate overall compliance
|
|
132
|
+
const overallCompliance = this.calculateOverallCompliance(frameworkResults, findings);
|
|
133
|
+
// Generate remediation plan
|
|
134
|
+
const remediationPlan = this.generateRemediationPlan(findings);
|
|
135
|
+
const result = {
|
|
136
|
+
workflowId,
|
|
137
|
+
timestamp: new Date(),
|
|
138
|
+
frameworks: frameworkResults,
|
|
139
|
+
dataHandling,
|
|
140
|
+
auditTrail,
|
|
141
|
+
retentionPolicy,
|
|
142
|
+
overallCompliance,
|
|
143
|
+
findings,
|
|
144
|
+
remediationPlan,
|
|
145
|
+
};
|
|
146
|
+
// Store result
|
|
147
|
+
await this.storeTestResult(`compliance:${workflowId}`, result);
|
|
148
|
+
// Emit event
|
|
149
|
+
this.emitEvent('compliance.validation.completed', {
|
|
150
|
+
workflowId,
|
|
151
|
+
isCompliant: overallCompliance.isCompliant,
|
|
152
|
+
score: overallCompliance.score,
|
|
153
|
+
findingCount: findings.length,
|
|
154
|
+
});
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Validate compliance for a specific framework
|
|
159
|
+
*/
|
|
160
|
+
validateFramework(workflow, framework) {
|
|
161
|
+
switch (framework) {
|
|
162
|
+
case 'GDPR':
|
|
163
|
+
return this.validateGDPR(workflow);
|
|
164
|
+
case 'HIPAA':
|
|
165
|
+
return this.validateHIPAA(workflow);
|
|
166
|
+
case 'SOC2':
|
|
167
|
+
return this.validateSOC2(workflow);
|
|
168
|
+
case 'PCI-DSS':
|
|
169
|
+
return this.validatePCIDSS(workflow);
|
|
170
|
+
default:
|
|
171
|
+
return this.getDefaultFrameworkResult(framework);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Validate GDPR compliance
|
|
176
|
+
*/
|
|
177
|
+
validateGDPR(workflow) {
|
|
178
|
+
const controls = [];
|
|
179
|
+
const findings = [];
|
|
180
|
+
// Check each GDPR control
|
|
181
|
+
for (const control of GDPR_CONTROLS) {
|
|
182
|
+
const result = this.checkGDPRControl(workflow, control);
|
|
183
|
+
controls.push(result);
|
|
184
|
+
if (result.status !== 'compliant' && result.status !== 'not-applicable') {
|
|
185
|
+
findings.push({
|
|
186
|
+
id: `GDPR-${control.id}-${Date.now()}`,
|
|
187
|
+
framework: 'GDPR',
|
|
188
|
+
severity: result.status === 'non-compliant' ? 'error' : 'warning',
|
|
189
|
+
category: control.category,
|
|
190
|
+
title: `${control.name} - ${result.status}`,
|
|
191
|
+
description: result.gaps.join('; '),
|
|
192
|
+
location: 'workflow',
|
|
193
|
+
evidence: result.evidence.join(', '),
|
|
194
|
+
remediation: this.getGDPRRemediation(control.id),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Check for international data transfers
|
|
199
|
+
const transfers = this.detectDataTransfers(workflow);
|
|
200
|
+
if (transfers.length > 0) {
|
|
201
|
+
findings.push({
|
|
202
|
+
id: `GDPR-transfer-${Date.now()}`,
|
|
203
|
+
framework: 'GDPR',
|
|
204
|
+
severity: 'warning',
|
|
205
|
+
category: 'transfer',
|
|
206
|
+
title: 'International data transfer detected',
|
|
207
|
+
description: `Data may be transferred to: ${transfers.join(', ')}`,
|
|
208
|
+
location: 'workflow',
|
|
209
|
+
evidence: 'HTTP requests to external services',
|
|
210
|
+
remediation: 'Ensure appropriate safeguards for international transfers',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const compliantCount = controls.filter(c => c.status === 'compliant').length;
|
|
214
|
+
const score = (compliantCount / controls.length) * 100;
|
|
215
|
+
return {
|
|
216
|
+
framework: 'GDPR',
|
|
217
|
+
isCompliant: findings.filter(f => f.severity === 'error' || f.severity === 'critical').length === 0,
|
|
218
|
+
score,
|
|
219
|
+
controls,
|
|
220
|
+
findings,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check specific GDPR control
|
|
225
|
+
*/
|
|
226
|
+
checkGDPRControl(workflow, control) {
|
|
227
|
+
const evidence = [];
|
|
228
|
+
const gaps = [];
|
|
229
|
+
switch (control.id) {
|
|
230
|
+
case 'GDPR-5.1': // Lawfulness
|
|
231
|
+
if (this.hasLawfulBasisDocumentation(workflow)) {
|
|
232
|
+
evidence.push('Processing purposes documented');
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
gaps.push('No documented lawful basis for processing');
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
case 'GDPR-5.3': // Data minimization
|
|
239
|
+
const unnecessaryData = this.detectUnnecessaryData(workflow);
|
|
240
|
+
if (unnecessaryData.length === 0) {
|
|
241
|
+
evidence.push('No unnecessary data collection detected');
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
gaps.push(`Potentially unnecessary data: ${unnecessaryData.join(', ')}`);
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
case 'GDPR-5.5': // Storage limitation
|
|
248
|
+
if (workflow.settings?.saveDataSuccessExecution === 'none') {
|
|
249
|
+
evidence.push('Data not retained after execution');
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
gaps.push('Data may be retained indefinitely');
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
case 'GDPR-5.6': // Security
|
|
256
|
+
const securityIssues = this.checkSecurityControls(workflow);
|
|
257
|
+
if (securityIssues.length === 0) {
|
|
258
|
+
evidence.push('Security controls in place');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
gaps.push(...securityIssues);
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
case 'GDPR-32': // Security of processing
|
|
265
|
+
if (this.hasEncryption(workflow)) {
|
|
266
|
+
evidence.push('Encryption configured for sensitive data');
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
gaps.push('No encryption detected for data in transit');
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
default:
|
|
273
|
+
evidence.push('Control not specifically validated');
|
|
274
|
+
}
|
|
275
|
+
let status;
|
|
276
|
+
if (gaps.length === 0 && evidence.length > 0) {
|
|
277
|
+
status = 'compliant';
|
|
278
|
+
}
|
|
279
|
+
else if (gaps.length > 0 && evidence.length > 0) {
|
|
280
|
+
status = 'partial';
|
|
281
|
+
}
|
|
282
|
+
else if (gaps.length > 0) {
|
|
283
|
+
status = 'non-compliant';
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
status = 'not-applicable';
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
controlId: control.id,
|
|
290
|
+
controlName: control.name,
|
|
291
|
+
status,
|
|
292
|
+
evidence,
|
|
293
|
+
gaps,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get GDPR remediation guidance
|
|
298
|
+
*/
|
|
299
|
+
getGDPRRemediation(controlId) {
|
|
300
|
+
const remediations = {
|
|
301
|
+
'GDPR-5.1': 'Document the lawful basis for processing in workflow metadata',
|
|
302
|
+
'GDPR-5.2': 'Ensure data is only used for documented purposes',
|
|
303
|
+
'GDPR-5.3': 'Remove unnecessary data fields from processing',
|
|
304
|
+
'GDPR-5.4': 'Implement data validation and accuracy checks',
|
|
305
|
+
'GDPR-5.5': 'Configure data retention policies and automatic deletion',
|
|
306
|
+
'GDPR-5.6': 'Enable encryption and access controls',
|
|
307
|
+
'GDPR-32': 'Implement appropriate technical security measures',
|
|
308
|
+
'GDPR-33': 'Configure breach notification workflow',
|
|
309
|
+
'GDPR-35': 'Complete DPIA for high-risk processing',
|
|
310
|
+
};
|
|
311
|
+
return remediations[controlId] || 'Review and address the identified gap';
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Validate HIPAA compliance
|
|
315
|
+
*/
|
|
316
|
+
validateHIPAA(workflow) {
|
|
317
|
+
const findings = [];
|
|
318
|
+
const controls = [];
|
|
319
|
+
// Check for PHI handling
|
|
320
|
+
const phiDetected = this.detectPHI(workflow);
|
|
321
|
+
if (phiDetected.length > 0) {
|
|
322
|
+
// Check encryption
|
|
323
|
+
if (!this.hasEncryption(workflow)) {
|
|
324
|
+
findings.push({
|
|
325
|
+
id: `HIPAA-encryption-${Date.now()}`,
|
|
326
|
+
framework: 'HIPAA',
|
|
327
|
+
severity: 'critical',
|
|
328
|
+
category: 'security',
|
|
329
|
+
title: 'PHI transmitted without encryption',
|
|
330
|
+
description: 'Protected Health Information detected without encryption',
|
|
331
|
+
location: phiDetected.join(', '),
|
|
332
|
+
evidence: 'PHI fields detected in workflow',
|
|
333
|
+
remediation: 'Enable encryption for all PHI data',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// Check access controls
|
|
337
|
+
if (!this.hasAccessControls(workflow)) {
|
|
338
|
+
findings.push({
|
|
339
|
+
id: `HIPAA-access-${Date.now()}`,
|
|
340
|
+
framework: 'HIPAA',
|
|
341
|
+
severity: 'error',
|
|
342
|
+
category: 'access',
|
|
343
|
+
title: 'Inadequate access controls for PHI',
|
|
344
|
+
description: 'No access controls detected for PHI handling',
|
|
345
|
+
location: 'workflow',
|
|
346
|
+
evidence: 'Workflow has no credential restrictions',
|
|
347
|
+
remediation: 'Implement role-based access controls',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
controls.push({
|
|
352
|
+
controlId: 'HIPAA-164.312(a)',
|
|
353
|
+
controlName: 'Access Control',
|
|
354
|
+
status: this.hasAccessControls(workflow) ? 'compliant' : 'non-compliant',
|
|
355
|
+
evidence: [],
|
|
356
|
+
gaps: [],
|
|
357
|
+
});
|
|
358
|
+
controls.push({
|
|
359
|
+
controlId: 'HIPAA-164.312(e)',
|
|
360
|
+
controlName: 'Transmission Security',
|
|
361
|
+
status: this.hasEncryption(workflow) ? 'compliant' : 'non-compliant',
|
|
362
|
+
evidence: [],
|
|
363
|
+
gaps: [],
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
framework: 'HIPAA',
|
|
367
|
+
isCompliant: findings.filter(f => f.severity === 'critical').length === 0,
|
|
368
|
+
score: controls.filter(c => c.status === 'compliant').length / controls.length * 100,
|
|
369
|
+
controls,
|
|
370
|
+
findings,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Validate SOC2 compliance
|
|
375
|
+
*/
|
|
376
|
+
validateSOC2(workflow) {
|
|
377
|
+
const findings = [];
|
|
378
|
+
const controls = [];
|
|
379
|
+
// Trust Service Criteria checks
|
|
380
|
+
const criteria = [
|
|
381
|
+
{ id: 'CC6.1', name: 'Logical and Physical Access', check: () => this.hasAccessControls(workflow) },
|
|
382
|
+
{ id: 'CC6.6', name: 'System Boundaries', check: () => this.hasNetworkControls(workflow) },
|
|
383
|
+
{ id: 'CC7.2', name: 'Change Management', check: () => this.hasChangeControls(workflow) },
|
|
384
|
+
{ id: 'CC8.1', name: 'Availability', check: () => this.hasAvailabilityControls(workflow) },
|
|
385
|
+
];
|
|
386
|
+
for (const criterion of criteria) {
|
|
387
|
+
const passed = criterion.check();
|
|
388
|
+
controls.push({
|
|
389
|
+
controlId: criterion.id,
|
|
390
|
+
controlName: criterion.name,
|
|
391
|
+
status: passed ? 'compliant' : 'partial',
|
|
392
|
+
evidence: [],
|
|
393
|
+
gaps: passed ? [] : [`${criterion.name} controls not fully implemented`],
|
|
394
|
+
});
|
|
395
|
+
if (!passed) {
|
|
396
|
+
findings.push({
|
|
397
|
+
id: `SOC2-${criterion.id}-${Date.now()}`,
|
|
398
|
+
framework: 'SOC2',
|
|
399
|
+
severity: 'warning',
|
|
400
|
+
category: 'trust-criteria',
|
|
401
|
+
title: `${criterion.name} - Partial compliance`,
|
|
402
|
+
description: `${criterion.name} controls may need enhancement`,
|
|
403
|
+
location: 'workflow',
|
|
404
|
+
evidence: 'Automated check',
|
|
405
|
+
remediation: `Review and enhance ${criterion.name.toLowerCase()} controls`,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
framework: 'SOC2',
|
|
411
|
+
isCompliant: findings.filter(f => f.severity === 'error' || f.severity === 'critical').length === 0,
|
|
412
|
+
score: controls.filter(c => c.status === 'compliant').length / controls.length * 100,
|
|
413
|
+
controls,
|
|
414
|
+
findings,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Validate PCI-DSS compliance
|
|
419
|
+
*/
|
|
420
|
+
validatePCIDSS(workflow) {
|
|
421
|
+
const findings = [];
|
|
422
|
+
const controls = [];
|
|
423
|
+
// Check for payment card data
|
|
424
|
+
const cardDataNodes = this.detectCardData(workflow);
|
|
425
|
+
if (cardDataNodes.length > 0) {
|
|
426
|
+
// Requirement 3: Protect stored cardholder data
|
|
427
|
+
if (!this.hasCardDataProtection(workflow)) {
|
|
428
|
+
findings.push({
|
|
429
|
+
id: `PCI-req3-${Date.now()}`,
|
|
430
|
+
framework: 'PCI-DSS',
|
|
431
|
+
severity: 'critical',
|
|
432
|
+
category: 'data-protection',
|
|
433
|
+
title: 'Cardholder data not protected',
|
|
434
|
+
description: 'Payment card data detected without adequate protection',
|
|
435
|
+
location: cardDataNodes.join(', '),
|
|
436
|
+
evidence: 'Card data patterns detected',
|
|
437
|
+
remediation: 'Encrypt or mask cardholder data',
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Requirement 4: Encrypt transmission
|
|
441
|
+
if (!this.hasEncryption(workflow)) {
|
|
442
|
+
findings.push({
|
|
443
|
+
id: `PCI-req4-${Date.now()}`,
|
|
444
|
+
framework: 'PCI-DSS',
|
|
445
|
+
severity: 'critical',
|
|
446
|
+
category: 'encryption',
|
|
447
|
+
title: 'Card data transmitted without encryption',
|
|
448
|
+
description: 'Payment data may be transmitted unencrypted',
|
|
449
|
+
location: 'workflow',
|
|
450
|
+
evidence: 'No TLS/encryption detected',
|
|
451
|
+
remediation: 'Enable TLS for all cardholder data transmission',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
controls.push({
|
|
456
|
+
controlId: 'PCI-3',
|
|
457
|
+
controlName: 'Protect Stored Data',
|
|
458
|
+
status: cardDataNodes.length === 0 || this.hasCardDataProtection(workflow) ? 'compliant' : 'non-compliant',
|
|
459
|
+
evidence: [],
|
|
460
|
+
gaps: [],
|
|
461
|
+
});
|
|
462
|
+
controls.push({
|
|
463
|
+
controlId: 'PCI-4',
|
|
464
|
+
controlName: 'Encrypt Transmission',
|
|
465
|
+
status: this.hasEncryption(workflow) ? 'compliant' : 'non-compliant',
|
|
466
|
+
evidence: [],
|
|
467
|
+
gaps: [],
|
|
468
|
+
});
|
|
469
|
+
return {
|
|
470
|
+
framework: 'PCI-DSS',
|
|
471
|
+
isCompliant: findings.filter(f => f.severity === 'critical').length === 0,
|
|
472
|
+
score: controls.filter(c => c.status === 'compliant').length / controls.length * 100,
|
|
473
|
+
controls,
|
|
474
|
+
findings,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Validate data handling practices
|
|
479
|
+
*/
|
|
480
|
+
validateDataHandling(workflow) {
|
|
481
|
+
const categories = [];
|
|
482
|
+
const risks = [];
|
|
483
|
+
// Detect PII
|
|
484
|
+
const piiFields = this.detectPII(workflow);
|
|
485
|
+
if (piiFields.length > 0) {
|
|
486
|
+
categories.push({
|
|
487
|
+
type: 'PII',
|
|
488
|
+
fields: piiFields,
|
|
489
|
+
nodes: this.findNodesWithFields(workflow, piiFields),
|
|
490
|
+
protection: this.hasEncryption(workflow) ? 'encrypted' : 'none',
|
|
491
|
+
});
|
|
492
|
+
if (!this.hasEncryption(workflow)) {
|
|
493
|
+
risks.push({
|
|
494
|
+
type: 'unprotected-pii',
|
|
495
|
+
severity: 'high',
|
|
496
|
+
description: 'PII data may be processed without encryption',
|
|
497
|
+
mitigation: 'Enable encryption for PII fields',
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// Detect financial data
|
|
502
|
+
const financialFields = this.detectFinancialData(workflow);
|
|
503
|
+
if (financialFields.length > 0) {
|
|
504
|
+
categories.push({
|
|
505
|
+
type: 'financial',
|
|
506
|
+
fields: financialFields,
|
|
507
|
+
nodes: this.findNodesWithFields(workflow, financialFields),
|
|
508
|
+
protection: this.hasEncryption(workflow) ? 'encrypted' : 'none',
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
// Processing activities
|
|
512
|
+
const processingActivities = [{
|
|
513
|
+
purpose: workflow.settings?.description || 'Automated data processing',
|
|
514
|
+
dataSubjects: 'Unknown',
|
|
515
|
+
transfers: this.detectDataTransfers(workflow),
|
|
516
|
+
}];
|
|
517
|
+
return {
|
|
518
|
+
personalDataDetected: piiFields.length > 0,
|
|
519
|
+
sensitiveDataDetected: financialFields.length > 0,
|
|
520
|
+
dataCategories: categories,
|
|
521
|
+
processingActivities,
|
|
522
|
+
risks,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Validate audit trail
|
|
527
|
+
*/
|
|
528
|
+
validateAuditTrail(workflow) {
|
|
529
|
+
const gaps = [];
|
|
530
|
+
const recommendations = [];
|
|
531
|
+
// Check execution logging
|
|
532
|
+
const hasExecutionLogs = workflow.settings?.saveExecutionProgress === true;
|
|
533
|
+
if (!hasExecutionLogs) {
|
|
534
|
+
gaps.push('Execution progress not logged');
|
|
535
|
+
recommendations.push('Enable saveExecutionProgress for audit trail');
|
|
536
|
+
}
|
|
537
|
+
// Check error logging
|
|
538
|
+
const hasErrorLogs = workflow.settings?.saveDataErrorExecution !== 'none';
|
|
539
|
+
if (!hasErrorLogs) {
|
|
540
|
+
gaps.push('Error execution data not saved');
|
|
541
|
+
recommendations.push('Enable error execution data saving');
|
|
542
|
+
}
|
|
543
|
+
// Calculate coverage
|
|
544
|
+
const coverage = ((hasExecutionLogs ? 50 : 0) + (hasErrorLogs ? 50 : 0));
|
|
545
|
+
return {
|
|
546
|
+
isEnabled: hasExecutionLogs || hasErrorLogs,
|
|
547
|
+
coverage,
|
|
548
|
+
gaps,
|
|
549
|
+
recommendations,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Validate retention policy
|
|
554
|
+
*/
|
|
555
|
+
validateRetentionPolicy(workflow) {
|
|
556
|
+
const policies = [];
|
|
557
|
+
const violations = [];
|
|
558
|
+
// Check workflow-level retention
|
|
559
|
+
const saveDataSuccessExecution = workflow.settings?.saveDataSuccessExecution;
|
|
560
|
+
const saveDataErrorExecution = workflow.settings?.saveDataErrorExecution;
|
|
561
|
+
if (saveDataSuccessExecution === 'all') {
|
|
562
|
+
violations.push('All successful execution data retained indefinitely');
|
|
563
|
+
}
|
|
564
|
+
policies.push({
|
|
565
|
+
dataType: 'successful-executions',
|
|
566
|
+
retentionPeriod: saveDataSuccessExecution === 'none' ? '0 days' :
|
|
567
|
+
saveDataSuccessExecution === 'lastSuccess' ? 'Last execution only' : 'Indefinite',
|
|
568
|
+
deletionMethod: 'automatic',
|
|
569
|
+
isEnforced: saveDataSuccessExecution !== 'all',
|
|
570
|
+
});
|
|
571
|
+
policies.push({
|
|
572
|
+
dataType: 'error-executions',
|
|
573
|
+
retentionPeriod: saveDataErrorExecution === 'none' ? '0 days' : 'Indefinite',
|
|
574
|
+
deletionMethod: 'manual',
|
|
575
|
+
isEnforced: saveDataErrorExecution === 'none',
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
hasPolicy: policies.some(p => p.isEnforced),
|
|
579
|
+
policies,
|
|
580
|
+
violations,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
// Helper methods
|
|
584
|
+
detectPII(workflow) {
|
|
585
|
+
const piiFields = [];
|
|
586
|
+
const workflowStr = JSON.stringify(workflow).toLowerCase();
|
|
587
|
+
for (const [name, pattern] of Object.entries(PII_PATTERNS)) {
|
|
588
|
+
if (pattern.test(workflowStr)) {
|
|
589
|
+
piiFields.push(name);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return piiFields;
|
|
593
|
+
}
|
|
594
|
+
detectPHI(workflow) {
|
|
595
|
+
const phiPatterns = [
|
|
596
|
+
/medical|health|diagnosis|treatment|prescription/i,
|
|
597
|
+
/patient|doctor|hospital|clinic/i,
|
|
598
|
+
/insurance|claim|coverage/i,
|
|
599
|
+
];
|
|
600
|
+
const workflowStr = JSON.stringify(workflow);
|
|
601
|
+
const detected = [];
|
|
602
|
+
for (const pattern of phiPatterns) {
|
|
603
|
+
if (pattern.test(workflowStr)) {
|
|
604
|
+
detected.push(pattern.source);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return detected;
|
|
608
|
+
}
|
|
609
|
+
detectFinancialData(workflow) {
|
|
610
|
+
const patterns = [
|
|
611
|
+
/account.*number|routing.*number/i,
|
|
612
|
+
/credit.*card|debit.*card/i,
|
|
613
|
+
/bank|payment|transaction/i,
|
|
614
|
+
];
|
|
615
|
+
const workflowStr = JSON.stringify(workflow);
|
|
616
|
+
const detected = [];
|
|
617
|
+
for (const pattern of patterns) {
|
|
618
|
+
if (pattern.test(workflowStr)) {
|
|
619
|
+
detected.push(pattern.source);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return detected;
|
|
623
|
+
}
|
|
624
|
+
detectCardData(workflow) {
|
|
625
|
+
const nodes = [];
|
|
626
|
+
for (const node of workflow.nodes) {
|
|
627
|
+
const nodeStr = JSON.stringify(node.parameters);
|
|
628
|
+
if (PII_PATTERNS.creditCard.test(nodeStr) || /card_?number|cvv|expir/i.test(nodeStr)) {
|
|
629
|
+
nodes.push(node.name);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return nodes;
|
|
633
|
+
}
|
|
634
|
+
detectDataTransfers(workflow) {
|
|
635
|
+
const transfers = [];
|
|
636
|
+
for (const node of workflow.nodes) {
|
|
637
|
+
if (node.type.includes('httpRequest')) {
|
|
638
|
+
const url = node.parameters.url;
|
|
639
|
+
if (url) {
|
|
640
|
+
try {
|
|
641
|
+
const hostname = new URL(url).hostname;
|
|
642
|
+
if (!hostname.includes('localhost') && !hostname.includes('127.0.0.1')) {
|
|
643
|
+
transfers.push(hostname);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
// Invalid URL
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return [...new Set(transfers)];
|
|
653
|
+
}
|
|
654
|
+
hasLawfulBasisDocumentation(workflow) {
|
|
655
|
+
return !!(workflow.settings?.description || workflow.tags?.length);
|
|
656
|
+
}
|
|
657
|
+
detectUnnecessaryData(workflow) {
|
|
658
|
+
// Check for overly broad data selection
|
|
659
|
+
const unnecessary = [];
|
|
660
|
+
for (const node of workflow.nodes) {
|
|
661
|
+
if (node.type.includes('postgres') || node.type.includes('mysql')) {
|
|
662
|
+
const query = node.parameters.query;
|
|
663
|
+
if (query && query.includes('SELECT *')) {
|
|
664
|
+
unnecessary.push(`${node.name}: SELECT * may retrieve unnecessary data`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return unnecessary;
|
|
669
|
+
}
|
|
670
|
+
checkSecurityControls(workflow) {
|
|
671
|
+
const issues = [];
|
|
672
|
+
// Check for hardcoded credentials
|
|
673
|
+
for (const node of workflow.nodes) {
|
|
674
|
+
const paramsStr = JSON.stringify(node.parameters);
|
|
675
|
+
if (/password\s*[=:]\s*["'][^"']+["']/i.test(paramsStr)) {
|
|
676
|
+
issues.push(`Hardcoded password in ${node.name}`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return issues;
|
|
680
|
+
}
|
|
681
|
+
hasEncryption(workflow) {
|
|
682
|
+
// Check if HTTPS is used for HTTP requests
|
|
683
|
+
for (const node of workflow.nodes) {
|
|
684
|
+
if (node.type.includes('httpRequest')) {
|
|
685
|
+
const url = node.parameters.url;
|
|
686
|
+
if (url && url.startsWith('http://')) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
hasAccessControls(workflow) {
|
|
694
|
+
// Check for credential usage (indicates access control)
|
|
695
|
+
return workflow.nodes.some(n => n.credentials && Object.keys(n.credentials).length > 0);
|
|
696
|
+
}
|
|
697
|
+
hasNetworkControls(workflow) {
|
|
698
|
+
return true; // Would need n8n instance configuration
|
|
699
|
+
}
|
|
700
|
+
hasChangeControls(workflow) {
|
|
701
|
+
return !!(workflow.versionId);
|
|
702
|
+
}
|
|
703
|
+
hasAvailabilityControls(workflow) {
|
|
704
|
+
return !!(workflow.settings?.errorWorkflow);
|
|
705
|
+
}
|
|
706
|
+
hasCardDataProtection(workflow) {
|
|
707
|
+
return this.hasEncryption(workflow);
|
|
708
|
+
}
|
|
709
|
+
findNodesWithFields(workflow, fields) {
|
|
710
|
+
const nodes = [];
|
|
711
|
+
const fieldsRegex = new RegExp(fields.join('|'), 'i');
|
|
712
|
+
for (const node of workflow.nodes) {
|
|
713
|
+
if (fieldsRegex.test(JSON.stringify(node.parameters))) {
|
|
714
|
+
nodes.push(node.name);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return nodes;
|
|
718
|
+
}
|
|
719
|
+
dataHandlingFindings(data) {
|
|
720
|
+
return data.risks.map((risk, i) => ({
|
|
721
|
+
id: `data-risk-${i}`,
|
|
722
|
+
severity: risk.severity,
|
|
723
|
+
category: 'data-handling',
|
|
724
|
+
title: risk.type,
|
|
725
|
+
description: risk.description,
|
|
726
|
+
location: 'workflow',
|
|
727
|
+
evidence: 'Data analysis',
|
|
728
|
+
remediation: risk.mitigation,
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
731
|
+
auditTrailFindings(audit) {
|
|
732
|
+
return audit.gaps.map((gap, i) => ({
|
|
733
|
+
id: `audit-gap-${i}`,
|
|
734
|
+
severity: 'warning',
|
|
735
|
+
category: 'audit',
|
|
736
|
+
title: 'Audit trail gap',
|
|
737
|
+
description: gap,
|
|
738
|
+
location: 'workflow',
|
|
739
|
+
evidence: 'Configuration check',
|
|
740
|
+
remediation: audit.recommendations[i] || 'Enable audit logging',
|
|
741
|
+
}));
|
|
742
|
+
}
|
|
743
|
+
retentionFindings(retention) {
|
|
744
|
+
return retention.violations.map((v, i) => ({
|
|
745
|
+
id: `retention-${i}`,
|
|
746
|
+
severity: 'warning',
|
|
747
|
+
category: 'retention',
|
|
748
|
+
title: 'Retention policy issue',
|
|
749
|
+
description: v,
|
|
750
|
+
location: 'workflow',
|
|
751
|
+
evidence: 'Policy check',
|
|
752
|
+
remediation: 'Configure appropriate retention policy',
|
|
753
|
+
}));
|
|
754
|
+
}
|
|
755
|
+
calculateOverallCompliance(frameworks, findings) {
|
|
756
|
+
const avgScore = frameworks.reduce((sum, f) => sum + f.score, 0) / frameworks.length;
|
|
757
|
+
const criticalCount = findings.filter(f => f.severity === 'critical').length;
|
|
758
|
+
const errorCount = findings.filter(f => f.severity === 'error').length;
|
|
759
|
+
let riskLevel;
|
|
760
|
+
if (criticalCount > 0)
|
|
761
|
+
riskLevel = 'critical';
|
|
762
|
+
else if (errorCount > 2)
|
|
763
|
+
riskLevel = 'high';
|
|
764
|
+
else if (errorCount > 0 || avgScore < 70)
|
|
765
|
+
riskLevel = 'medium';
|
|
766
|
+
else
|
|
767
|
+
riskLevel = 'low';
|
|
768
|
+
return {
|
|
769
|
+
isCompliant: criticalCount === 0 && errorCount === 0,
|
|
770
|
+
score: avgScore,
|
|
771
|
+
riskLevel,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
generateRemediationPlan(findings) {
|
|
775
|
+
const priorityMap = {
|
|
776
|
+
critical: 1,
|
|
777
|
+
error: 2,
|
|
778
|
+
warning: 3,
|
|
779
|
+
info: 4,
|
|
780
|
+
};
|
|
781
|
+
return findings
|
|
782
|
+
.sort((a, b) => priorityMap[a.severity] - priorityMap[b.severity])
|
|
783
|
+
.map((finding, i) => ({
|
|
784
|
+
findingId: finding.id,
|
|
785
|
+
priority: i + 1,
|
|
786
|
+
effort: finding.severity === 'critical' ? 'high' : finding.severity === 'error' ? 'medium' : 'low',
|
|
787
|
+
action: finding.remediation,
|
|
788
|
+
}));
|
|
789
|
+
}
|
|
790
|
+
getDefaultFrameworkResult(framework) {
|
|
791
|
+
return {
|
|
792
|
+
framework,
|
|
793
|
+
isCompliant: true,
|
|
794
|
+
score: 100,
|
|
795
|
+
controls: [],
|
|
796
|
+
findings: [],
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
getDefaultDataHandling() {
|
|
800
|
+
return {
|
|
801
|
+
personalDataDetected: false,
|
|
802
|
+
sensitiveDataDetected: false,
|
|
803
|
+
dataCategories: [],
|
|
804
|
+
processingActivities: [],
|
|
805
|
+
risks: [],
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
getDefaultAuditTrail() {
|
|
809
|
+
return {
|
|
810
|
+
isEnabled: false,
|
|
811
|
+
coverage: 0,
|
|
812
|
+
gaps: [],
|
|
813
|
+
recommendations: [],
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
getDefaultRetentionPolicy() {
|
|
817
|
+
return {
|
|
818
|
+
hasPolicy: false,
|
|
819
|
+
policies: [],
|
|
820
|
+
violations: [],
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Execute workflow and trace PII data flow through nodes
|
|
825
|
+
* This catches runtime compliance issues like PII being logged,
|
|
826
|
+
* sent to unauthorized destinations, or retained improperly
|
|
827
|
+
*/
|
|
828
|
+
async executeRuntimePIITracing(workflowId, testInput) {
|
|
829
|
+
const findings = [];
|
|
830
|
+
// Generate test data with identifiable PII for tracing
|
|
831
|
+
const piiTestData = {
|
|
832
|
+
email: 'test.trace@pii-detection.test',
|
|
833
|
+
phone: '555-123-4567',
|
|
834
|
+
ssn: '123-45-6789',
|
|
835
|
+
name: 'PII_Trace_Name',
|
|
836
|
+
address: '123 PII Trace Street',
|
|
837
|
+
creditCard: '4111111111111111',
|
|
838
|
+
...testInput,
|
|
839
|
+
};
|
|
840
|
+
try {
|
|
841
|
+
// Execute workflow with PII test data
|
|
842
|
+
const execution = await this.executeWorkflow(workflowId, piiTestData, {
|
|
843
|
+
waitForCompletion: true,
|
|
844
|
+
timeout: 30000,
|
|
845
|
+
});
|
|
846
|
+
// Wait for completion
|
|
847
|
+
const completedExecution = await this.waitForExecution(execution.id, 30000);
|
|
848
|
+
// Trace PII through each node's output
|
|
849
|
+
const piiTrace = this.tracePIIFlow(completedExecution, piiTestData);
|
|
850
|
+
// Analyze the trace for compliance issues
|
|
851
|
+
for (const [nodeName, piiFound] of Object.entries(piiTrace)) {
|
|
852
|
+
if (piiFound.length > 0) {
|
|
853
|
+
// Check if this node should have PII
|
|
854
|
+
const isExpectedPIINode = this.isAuthorizedPIINode(nodeName, completedExecution);
|
|
855
|
+
if (!isExpectedPIINode) {
|
|
856
|
+
findings.push({
|
|
857
|
+
id: `runtime-pii-${nodeName}-${Date.now()}`,
|
|
858
|
+
severity: 'error',
|
|
859
|
+
category: 'data-flow',
|
|
860
|
+
title: `Unauthorized PII flow detected in ${nodeName}`,
|
|
861
|
+
description: `PII types found: ${piiFound.join(', ')}. This node may be processing PII without authorization.`,
|
|
862
|
+
location: nodeName,
|
|
863
|
+
evidence: `Traced PII: ${piiFound.join(', ')}`,
|
|
864
|
+
remediation: 'Review data flow and ensure PII is only processed in authorized nodes with proper safeguards.',
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
// Check if PII is being sent to external services
|
|
868
|
+
if (this.isExternalServiceNode(nodeName, completedExecution)) {
|
|
869
|
+
findings.push({
|
|
870
|
+
id: `runtime-pii-external-${nodeName}-${Date.now()}`,
|
|
871
|
+
framework: 'GDPR',
|
|
872
|
+
severity: 'critical',
|
|
873
|
+
category: 'data-transfer',
|
|
874
|
+
title: `PII potentially transferred externally via ${nodeName}`,
|
|
875
|
+
description: `PII (${piiFound.join(', ')}) detected in data sent to external service.`,
|
|
876
|
+
location: nodeName,
|
|
877
|
+
evidence: `External node with PII: ${piiFound.join(', ')}`,
|
|
878
|
+
remediation: 'Ensure data processing agreement exists with third party. Consider data minimization.',
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
// Check if PII was stored in execution logs
|
|
884
|
+
if (completedExecution.data?.resultData?.runData) {
|
|
885
|
+
const executionDataStr = JSON.stringify(completedExecution.data);
|
|
886
|
+
for (const [piiType, piiValue] of Object.entries(piiTestData)) {
|
|
887
|
+
if (typeof piiValue === 'string' && executionDataStr.includes(piiValue)) {
|
|
888
|
+
// PII found in execution data - check retention settings
|
|
889
|
+
const workflow = await this.getWorkflow(workflowId);
|
|
890
|
+
if (workflow.settings?.saveDataSuccessExecution === 'all') {
|
|
891
|
+
findings.push({
|
|
892
|
+
id: `runtime-pii-retention-${piiType}-${Date.now()}`,
|
|
893
|
+
framework: 'GDPR',
|
|
894
|
+
severity: 'error', // Use 'error' instead of 'high' per type definition
|
|
895
|
+
category: 'retention',
|
|
896
|
+
title: `PII (${piiType}) may be retained in execution logs`,
|
|
897
|
+
description: `PII data found in execution results which are configured to be retained.`,
|
|
898
|
+
location: 'execution-logs',
|
|
899
|
+
evidence: `PII type "${piiType}" found in saved execution data`,
|
|
900
|
+
remediation: 'Configure data retention policies or mask PII before storing execution results.',
|
|
901
|
+
});
|
|
902
|
+
break; // Only report once for retention
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
// Log but don't fail the entire compliance check
|
|
910
|
+
console.error('Runtime PII tracing failed:', error);
|
|
911
|
+
}
|
|
912
|
+
return findings;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Trace PII test values through execution data
|
|
916
|
+
*/
|
|
917
|
+
tracePIIFlow(execution, piiTestData) {
|
|
918
|
+
const trace = {};
|
|
919
|
+
const runData = execution.data?.resultData?.runData;
|
|
920
|
+
if (!runData)
|
|
921
|
+
return trace;
|
|
922
|
+
for (const [nodeName, nodeRuns] of Object.entries(runData)) {
|
|
923
|
+
const piiFound = [];
|
|
924
|
+
for (const run of nodeRuns) {
|
|
925
|
+
const nodeDataStr = JSON.stringify(run.data || {});
|
|
926
|
+
for (const [piiType, piiValue] of Object.entries(piiTestData)) {
|
|
927
|
+
if (typeof piiValue === 'string' && nodeDataStr.includes(piiValue)) {
|
|
928
|
+
piiFound.push(piiType);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (piiFound.length > 0) {
|
|
933
|
+
trace[nodeName] = [...new Set(piiFound)];
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return trace;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Check if node is authorized to process PII
|
|
940
|
+
*/
|
|
941
|
+
isAuthorizedPIINode(nodeName, execution) {
|
|
942
|
+
// First few nodes in execution are typically authorized (input/trigger)
|
|
943
|
+
const runData = execution.data?.resultData?.runData;
|
|
944
|
+
if (!runData)
|
|
945
|
+
return true;
|
|
946
|
+
const nodeNames = Object.keys(runData);
|
|
947
|
+
const nodeIndex = nodeNames.indexOf(nodeName);
|
|
948
|
+
// First 2 nodes are typically authorized (trigger + first processor)
|
|
949
|
+
return nodeIndex < 2;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Check if node sends data to external services
|
|
953
|
+
*/
|
|
954
|
+
isExternalServiceNode(nodeName, execution) {
|
|
955
|
+
const runData = execution.data?.resultData?.runData;
|
|
956
|
+
if (!runData)
|
|
957
|
+
return false;
|
|
958
|
+
const nodeRuns = runData[nodeName];
|
|
959
|
+
if (!nodeRuns || nodeRuns.length === 0)
|
|
960
|
+
return false;
|
|
961
|
+
// Check source info for node type
|
|
962
|
+
const nodeSource = nodeRuns[0].source?.[0];
|
|
963
|
+
if (!nodeSource)
|
|
964
|
+
return false;
|
|
965
|
+
// HTTP, webhook, and API nodes are external
|
|
966
|
+
const externalPatterns = ['http', 'api', 'webhook', 'slack', 'email', 'sendgrid'];
|
|
967
|
+
const nodeType = nodeSource.previousNode || '';
|
|
968
|
+
return externalPatterns.some(p => nodeType.toLowerCase().includes(p) || nodeName.toLowerCase().includes(p));
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Wait for workflow execution to complete
|
|
972
|
+
*/
|
|
973
|
+
async waitForExecution(executionId, timeoutMs) {
|
|
974
|
+
const startTime = Date.now();
|
|
975
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
976
|
+
const execution = await this.getExecution(executionId);
|
|
977
|
+
if (execution.status !== 'running' && execution.status !== 'waiting') {
|
|
978
|
+
return execution;
|
|
979
|
+
}
|
|
980
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
981
|
+
}
|
|
982
|
+
throw new Error(`Execution ${executionId} timed out after ${timeoutMs}ms`);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
exports.N8nComplianceValidatorAgent = N8nComplianceValidatorAgent;
|
|
986
|
+
//# sourceMappingURL=N8nComplianceValidatorAgent.js.map
|