qa360 1.0.3 → 1.1.0
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/dist/commands/history.js +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/commands/verify.js +1 -1
- package/dist/core/adapters/gitleaks-secrets.d.ts +115 -0
- package/dist/core/adapters/gitleaks-secrets.d.ts.map +1 -0
- package/dist/core/adapters/gitleaks-secrets.js +410 -0
- package/dist/core/adapters/k6-perf.d.ts +86 -0
- package/dist/core/adapters/k6-perf.d.ts.map +1 -0
- package/dist/core/adapters/k6-perf.js +398 -0
- package/dist/core/adapters/osv-deps.d.ts +124 -0
- package/dist/core/adapters/osv-deps.d.ts.map +1 -0
- package/dist/core/adapters/osv-deps.js +372 -0
- package/dist/core/adapters/playwright-api.d.ts +82 -0
- package/dist/core/adapters/playwright-api.d.ts.map +1 -0
- package/dist/core/adapters/playwright-api.js +252 -0
- package/dist/core/adapters/playwright-ui.d.ts +115 -0
- package/dist/core/adapters/playwright-ui.d.ts.map +1 -0
- package/dist/core/adapters/playwright-ui.js +346 -0
- package/dist/core/adapters/semgrep-sast.d.ts +100 -0
- package/dist/core/adapters/semgrep-sast.d.ts.map +1 -0
- package/dist/core/adapters/semgrep-sast.js +322 -0
- package/dist/core/adapters/zap-dast.d.ts +134 -0
- package/dist/core/adapters/zap-dast.d.ts.map +1 -0
- package/dist/core/adapters/zap-dast.js +424 -0
- package/dist/core/hooks/compose.d.ts +62 -0
- package/dist/core/hooks/compose.d.ts.map +1 -0
- package/dist/core/hooks/compose.js +225 -0
- package/dist/core/hooks/runner.d.ts +69 -0
- package/dist/core/hooks/runner.d.ts.map +1 -0
- package/dist/core/hooks/runner.js +303 -0
- package/dist/core/index.d.ts +74 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +39 -0
- package/dist/core/pack/migrator.d.ts +52 -0
- package/dist/core/pack/migrator.d.ts.map +1 -0
- package/dist/core/pack/migrator.js +304 -0
- package/dist/core/pack/validator.d.ts +43 -0
- package/dist/core/pack/validator.d.ts.map +1 -0
- package/dist/core/pack/validator.js +292 -0
- package/dist/core/proof/bundle.d.ts +138 -0
- package/dist/core/proof/bundle.d.ts.map +1 -0
- package/dist/core/proof/bundle.js +160 -0
- package/dist/core/proof/canonicalize.d.ts +48 -0
- package/dist/core/proof/canonicalize.d.ts.map +1 -0
- package/dist/core/proof/canonicalize.js +105 -0
- package/dist/core/proof/index.d.ts +14 -0
- package/dist/core/proof/index.d.ts.map +1 -0
- package/dist/core/proof/index.js +18 -0
- package/dist/core/proof/schema.d.ts +218 -0
- package/dist/core/proof/schema.d.ts.map +1 -0
- package/dist/core/proof/schema.js +263 -0
- package/dist/core/proof/signer.d.ts +112 -0
- package/dist/core/proof/signer.d.ts.map +1 -0
- package/dist/core/proof/signer.js +226 -0
- package/dist/core/proof/verifier.d.ts +98 -0
- package/dist/core/proof/verifier.d.ts.map +1 -0
- package/dist/core/proof/verifier.js +302 -0
- package/dist/core/runner/phase3-runner.d.ts +102 -0
- package/dist/core/runner/phase3-runner.d.ts.map +1 -0
- package/dist/core/runner/phase3-runner.js +471 -0
- package/dist/core/secrets/crypto.d.ts +76 -0
- package/dist/core/secrets/crypto.d.ts.map +1 -0
- package/dist/core/secrets/crypto.js +225 -0
- package/dist/core/secrets/manager.d.ts +77 -0
- package/dist/core/secrets/manager.d.ts.map +1 -0
- package/dist/core/secrets/manager.js +219 -0
- package/dist/core/security/redaction-patterns-extended.d.ts +28 -0
- package/dist/core/security/redaction-patterns-extended.d.ts.map +1 -0
- package/dist/core/security/redaction-patterns-extended.js +247 -0
- package/dist/core/security/redactor.d.ts +72 -0
- package/dist/core/security/redactor.d.ts.map +1 -0
- package/dist/core/security/redactor.js +279 -0
- package/dist/core/serve/diagnostics-collector.d.ts +33 -0
- package/dist/core/serve/diagnostics-collector.d.ts.map +1 -0
- package/dist/core/serve/diagnostics-collector.js +149 -0
- package/dist/core/serve/health-checker.d.ts +45 -0
- package/dist/core/serve/health-checker.d.ts.map +1 -0
- package/dist/core/serve/health-checker.js +219 -0
- package/dist/core/serve/index.d.ts +9 -0
- package/dist/core/serve/index.d.ts.map +1 -0
- package/dist/core/serve/index.js +8 -0
- package/dist/core/serve/metrics-collector.d.ts +25 -0
- package/dist/core/serve/metrics-collector.d.ts.map +1 -0
- package/dist/core/serve/metrics-collector.js +322 -0
- package/dist/core/serve/process-manager.d.ts +37 -0
- package/dist/core/serve/process-manager.d.ts.map +1 -0
- package/dist/core/serve/process-manager.js +213 -0
- package/dist/core/serve/server.d.ts +37 -0
- package/dist/core/serve/server.d.ts.map +1 -0
- package/dist/core/serve/server.js +191 -0
- package/dist/core/types/pack-v1.d.ts +162 -0
- package/dist/core/types/pack-v1.d.ts.map +1 -0
- package/dist/core/types/pack-v1.js +5 -0
- package/dist/core/types/trust-score.d.ts +70 -0
- package/dist/core/types/trust-score.d.ts.map +1 -0
- package/dist/core/types/trust-score.js +191 -0
- package/dist/core/vault/cas.d.ts +87 -0
- package/dist/core/vault/cas.d.ts.map +1 -0
- package/dist/core/vault/cas.js +255 -0
- package/dist/core/vault/index.d.ts +205 -0
- package/dist/core/vault/index.d.ts.map +1 -0
- package/dist/core/vault/index.js +631 -0
- package/package.json +12 -6
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 ZAP DAST Adapter (Sécurité Réelle)
|
|
3
|
+
* OWASP ZAP baseline scan for dynamic application security testing
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { existsSync, unlinkSync, readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { SecurityRedactor } from '../security/redactor.js';
|
|
9
|
+
export class ZapDastAdapter {
|
|
10
|
+
redactor;
|
|
11
|
+
workingDir;
|
|
12
|
+
constructor(workingDir = process.cwd()) {
|
|
13
|
+
this.redactor = SecurityRedactor.forLogs();
|
|
14
|
+
this.workingDir = workingDir;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Execute ZAP DAST scan
|
|
18
|
+
*/
|
|
19
|
+
async runDastScan(config) {
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
try {
|
|
22
|
+
console.log(`🔍 Running ZAP ${config.scanType || 'baseline'} scan on ${config.targetUrl}`);
|
|
23
|
+
// Validate target URL
|
|
24
|
+
if (!this.isValidUrl(config.targetUrl)) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
alerts: [],
|
|
28
|
+
summary: this.getEmptySummary(),
|
|
29
|
+
budgetCheck: this.getDefaultBudgetCheck(),
|
|
30
|
+
targetUrl: config.targetUrl,
|
|
31
|
+
scanDuration: 0,
|
|
32
|
+
error: 'Invalid target URL provided',
|
|
33
|
+
errorCode: 'ZAP001'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Execute ZAP scan
|
|
37
|
+
const result = await this.executeZapScan(config);
|
|
38
|
+
result.scanDuration = Date.now() - startTime;
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
alerts: [],
|
|
45
|
+
summary: this.getEmptySummary(),
|
|
46
|
+
budgetCheck: this.getDefaultBudgetCheck(),
|
|
47
|
+
targetUrl: config.targetUrl,
|
|
48
|
+
scanDuration: Date.now() - startTime,
|
|
49
|
+
error: this.redactor.redact(error instanceof Error ? error.message : 'Unknown error'),
|
|
50
|
+
errorCode: 'ZAP099'
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate URL format
|
|
56
|
+
*/
|
|
57
|
+
isValidUrl(url) {
|
|
58
|
+
try {
|
|
59
|
+
new URL(url);
|
|
60
|
+
return url.startsWith('http://') || url.startsWith('https://');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Execute ZAP scanner
|
|
68
|
+
*/
|
|
69
|
+
async executeZapScan(config) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
let output = '';
|
|
72
|
+
let error = '';
|
|
73
|
+
const reportPath = join(this.workingDir, `zap-report-${Date.now()}.json`);
|
|
74
|
+
const htmlReportPath = join(this.workingDir, `zap-report-${Date.now()}.html`);
|
|
75
|
+
// Try Docker first, then local installation
|
|
76
|
+
const useDocker = this.shouldUseDocker();
|
|
77
|
+
const args = this.buildZapArgs(config, reportPath, htmlReportPath, useDocker);
|
|
78
|
+
const command = useDocker ? 'docker' : 'zap-baseline.py';
|
|
79
|
+
console.log(`🐳 Using ${useDocker ? 'Docker' : 'local'} ZAP installation`);
|
|
80
|
+
const child = spawn(command, args, {
|
|
81
|
+
cwd: this.workingDir,
|
|
82
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
83
|
+
});
|
|
84
|
+
const timeout = setTimeout(() => {
|
|
85
|
+
child.kill('SIGKILL');
|
|
86
|
+
resolve({
|
|
87
|
+
success: false,
|
|
88
|
+
alerts: [],
|
|
89
|
+
summary: this.getEmptySummary(),
|
|
90
|
+
budgetCheck: this.getDefaultBudgetCheck(),
|
|
91
|
+
targetUrl: config.targetUrl,
|
|
92
|
+
scanDuration: 0,
|
|
93
|
+
error: `ZAP scan timed out after ${config.security?.dast?.timeout_ms || 300000}ms`,
|
|
94
|
+
errorCode: 'ZAP002'
|
|
95
|
+
});
|
|
96
|
+
}, config.security?.dast?.timeout_ms || 300000); // 5 minutes default
|
|
97
|
+
if (child.stdout) {
|
|
98
|
+
child.stdout.on('data', (data) => {
|
|
99
|
+
output += data.toString();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (child.stderr) {
|
|
103
|
+
child.stderr.on('data', (data) => {
|
|
104
|
+
error += data.toString();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
child.on('close', (code) => {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
// ZAP returns different codes: 0=no alerts, 1=low, 2=medium, 3=high
|
|
110
|
+
if (code !== null && code <= 3) {
|
|
111
|
+
const result = this.parseZapResults(reportPath, config, output);
|
|
112
|
+
resolve(result);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
resolve({
|
|
116
|
+
success: false,
|
|
117
|
+
alerts: [],
|
|
118
|
+
summary: this.getEmptySummary(),
|
|
119
|
+
budgetCheck: this.getDefaultBudgetCheck(),
|
|
120
|
+
targetUrl: config.targetUrl,
|
|
121
|
+
scanDuration: 0,
|
|
122
|
+
error: this.redactor.redact(error || `ZAP exited with code ${code}`),
|
|
123
|
+
rawOutput: this.redactor.redact(output),
|
|
124
|
+
errorCode: 'ZAP003'
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
child.on('error', (err) => {
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
// Fallback to mock scan
|
|
131
|
+
resolve(this.fallbackMockScan(config));
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if Docker should be used for ZAP
|
|
137
|
+
*/
|
|
138
|
+
shouldUseDocker() {
|
|
139
|
+
// Check if Docker is available and ZAP image exists
|
|
140
|
+
try {
|
|
141
|
+
const { execSync } = require('child_process');
|
|
142
|
+
execSync('docker --version', { stdio: 'ignore' });
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build ZAP command arguments
|
|
151
|
+
*/
|
|
152
|
+
buildZapArgs(config, reportPath, htmlReportPath, useDocker) {
|
|
153
|
+
const args = [];
|
|
154
|
+
if (useDocker) {
|
|
155
|
+
args.push('run', '--rm', '-v', `${this.workingDir}:/zap/wrk/:rw`, 'owasp/zap2docker-stable', 'zap-baseline.py');
|
|
156
|
+
}
|
|
157
|
+
// Target URL
|
|
158
|
+
args.push('-t', config.targetUrl);
|
|
159
|
+
// Report formats
|
|
160
|
+
args.push('-J', reportPath.split('/').pop()); // JSON report
|
|
161
|
+
args.push('-r', htmlReportPath.split('/').pop()); // HTML report
|
|
162
|
+
// Scan configuration
|
|
163
|
+
if (config.scanType === 'full') {
|
|
164
|
+
args.push('-a'); // Full scan
|
|
165
|
+
}
|
|
166
|
+
// Exclude URLs
|
|
167
|
+
if (config.excludeUrls && config.excludeUrls.length > 0) {
|
|
168
|
+
for (const url of config.excludeUrls) {
|
|
169
|
+
args.push('-x', url);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Include URLs
|
|
173
|
+
if (config.includeUrls && config.includeUrls.length > 0) {
|
|
174
|
+
for (const url of config.includeUrls) {
|
|
175
|
+
args.push('-i', url);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Context file
|
|
179
|
+
if (config.contextFile && existsSync(config.contextFile)) {
|
|
180
|
+
args.push('-n', config.contextFile);
|
|
181
|
+
}
|
|
182
|
+
// Config file
|
|
183
|
+
if (config.configFile && existsSync(config.configFile)) {
|
|
184
|
+
args.push('-c', config.configFile);
|
|
185
|
+
}
|
|
186
|
+
// Additional options
|
|
187
|
+
args.push('-I'); // Ignore warnings
|
|
188
|
+
args.push('-d'); // Show debug messages
|
|
189
|
+
return args;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Parse ZAP scan results
|
|
193
|
+
*/
|
|
194
|
+
parseZapResults(reportPath, config, output) {
|
|
195
|
+
try {
|
|
196
|
+
let alerts = [];
|
|
197
|
+
if (existsSync(reportPath)) {
|
|
198
|
+
const reportContent = readFileSync(reportPath, 'utf8');
|
|
199
|
+
const data = JSON.parse(reportContent);
|
|
200
|
+
if (data.site && data.site[0] && data.site[0].alerts) {
|
|
201
|
+
alerts = data.site[0].alerts;
|
|
202
|
+
}
|
|
203
|
+
// Cleanup report file
|
|
204
|
+
try {
|
|
205
|
+
unlinkSync(reportPath);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Ignore cleanup errors
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Redact sensitive information from alerts
|
|
212
|
+
const redactedAlerts = alerts.map(alert => ({
|
|
213
|
+
...alert,
|
|
214
|
+
instances: alert.instances?.map(instance => ({
|
|
215
|
+
...instance,
|
|
216
|
+
uri: this.redactor.redact(instance.uri || ''),
|
|
217
|
+
attack: this.redactor.redact(instance.attack || ''),
|
|
218
|
+
evidence: this.redactor.redact(instance.evidence || ''),
|
|
219
|
+
otherinfo: this.redactor.redact(instance.otherinfo || '')
|
|
220
|
+
})) || []
|
|
221
|
+
}));
|
|
222
|
+
const summary = this.calculateSummary(redactedAlerts);
|
|
223
|
+
const budgetCheck = this.generateBudgetCheck(summary, config);
|
|
224
|
+
const success = budgetCheck.high_passed && budgetCheck.critical_passed;
|
|
225
|
+
const junit = this.generateJUnit(summary, success, config);
|
|
226
|
+
return {
|
|
227
|
+
success,
|
|
228
|
+
alerts: redactedAlerts,
|
|
229
|
+
summary,
|
|
230
|
+
budgetCheck,
|
|
231
|
+
targetUrl: config.targetUrl,
|
|
232
|
+
scanDuration: 0, // Will be set by caller
|
|
233
|
+
rawOutput: this.redactor.redact(output),
|
|
234
|
+
reportPath,
|
|
235
|
+
junit
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
alerts: [],
|
|
242
|
+
summary: this.getEmptySummary(),
|
|
243
|
+
budgetCheck: this.getDefaultBudgetCheck(),
|
|
244
|
+
targetUrl: config.targetUrl,
|
|
245
|
+
scanDuration: 0,
|
|
246
|
+
error: `Failed to parse ZAP results: ${error}`,
|
|
247
|
+
rawOutput: this.redactor.redact(output),
|
|
248
|
+
errorCode: 'ZAP004'
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Fallback mock scan when ZAP not available
|
|
254
|
+
*/
|
|
255
|
+
fallbackMockScan(config) {
|
|
256
|
+
console.log('⚠️ ZAP not found, using mock scan (install OWASP ZAP for real scanning)');
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
alerts: [],
|
|
260
|
+
summary: this.getEmptySummary(),
|
|
261
|
+
budgetCheck: this.getDefaultBudgetCheck(),
|
|
262
|
+
targetUrl: config.targetUrl,
|
|
263
|
+
scanDuration: 1000,
|
|
264
|
+
error: 'ZAP not available - install from https://www.zaproxy.org/ or use Docker',
|
|
265
|
+
junit: this.generateJUnit(this.getEmptySummary(), true, config),
|
|
266
|
+
errorCode: 'ZAP005'
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Calculate alerts summary
|
|
271
|
+
*/
|
|
272
|
+
calculateSummary(alerts) {
|
|
273
|
+
const summary = {
|
|
274
|
+
total: alerts.length,
|
|
275
|
+
high: 0,
|
|
276
|
+
medium: 0,
|
|
277
|
+
low: 0,
|
|
278
|
+
informational: 0,
|
|
279
|
+
by_category: {}
|
|
280
|
+
};
|
|
281
|
+
for (const alert of alerts) {
|
|
282
|
+
const risk = alert.riskdesc?.toLowerCase() || '';
|
|
283
|
+
if (risk.includes('high')) {
|
|
284
|
+
summary.high++;
|
|
285
|
+
}
|
|
286
|
+
else if (risk.includes('medium')) {
|
|
287
|
+
summary.medium++;
|
|
288
|
+
}
|
|
289
|
+
else if (risk.includes('low')) {
|
|
290
|
+
summary.low++;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
summary.informational++;
|
|
294
|
+
}
|
|
295
|
+
// Count by category/plugin
|
|
296
|
+
const category = alert.alert || 'Unknown';
|
|
297
|
+
summary.by_category[category] = (summary.by_category[category] || 0) + 1;
|
|
298
|
+
}
|
|
299
|
+
return summary;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Generate budget check based on security config
|
|
303
|
+
*/
|
|
304
|
+
generateBudgetCheck(summary, config) {
|
|
305
|
+
const dast = config.security?.dast;
|
|
306
|
+
return {
|
|
307
|
+
critical_passed: !dast?.max_critical || 0 <= (dast.max_critical || 0), // No critical in ZAP baseline
|
|
308
|
+
high_passed: !dast?.max_high || summary.high <= dast.max_high,
|
|
309
|
+
medium_passed: !dast?.max_medium || summary.medium <= dast.max_medium
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get empty summary structure
|
|
314
|
+
*/
|
|
315
|
+
getEmptySummary() {
|
|
316
|
+
return {
|
|
317
|
+
total: 0,
|
|
318
|
+
high: 0,
|
|
319
|
+
medium: 0,
|
|
320
|
+
low: 0,
|
|
321
|
+
informational: 0,
|
|
322
|
+
by_category: {}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get default budget check structure
|
|
327
|
+
*/
|
|
328
|
+
getDefaultBudgetCheck() {
|
|
329
|
+
return {
|
|
330
|
+
critical_passed: true,
|
|
331
|
+
high_passed: true,
|
|
332
|
+
medium_passed: true
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Generate JUnit XML report
|
|
337
|
+
*/
|
|
338
|
+
generateJUnit(summary, success, config) {
|
|
339
|
+
const timestamp = new Date().toISOString();
|
|
340
|
+
const duration = '30.0'; // Typical ZAP scan duration
|
|
341
|
+
let junit = `<?xml version="1.0" encoding="UTF-8"?>
|
|
342
|
+
<testsuite name="ZAP DAST Scan" tests="1" failures="${success ? 0 : 1}" time="${duration}" timestamp="${timestamp}">
|
|
343
|
+
<testcase name="DAST Security Scan: ${config.targetUrl}" time="${duration}">
|
|
344
|
+
`;
|
|
345
|
+
if (!success) {
|
|
346
|
+
junit += ` <failure message="Security vulnerabilities found">
|
|
347
|
+
High: ${summary.high}
|
|
348
|
+
Medium: ${summary.medium}
|
|
349
|
+
Low: ${summary.low}
|
|
350
|
+
Informational: ${summary.informational}
|
|
351
|
+
Total: ${summary.total}
|
|
352
|
+
</failure>
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
junit += ` </testcase>
|
|
356
|
+
</testsuite>`;
|
|
357
|
+
return junit;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Validate ZAP scan configuration
|
|
361
|
+
*/
|
|
362
|
+
static validateConfig(config) {
|
|
363
|
+
const errors = [];
|
|
364
|
+
if (!config.targetUrl) {
|
|
365
|
+
errors.push('Target URL is required');
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
new URL(config.targetUrl);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
errors.push('Invalid target URL format');
|
|
372
|
+
}
|
|
373
|
+
if (config.contextFile && !existsSync(config.contextFile)) {
|
|
374
|
+
errors.push(`Context file does not exist: ${config.contextFile}`);
|
|
375
|
+
}
|
|
376
|
+
if (config.configFile && !existsSync(config.configFile)) {
|
|
377
|
+
errors.push(`Config file does not exist: ${config.configFile}`);
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
valid: errors.length === 0,
|
|
381
|
+
errors
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Check if ZAP is available (Docker or local)
|
|
386
|
+
*/
|
|
387
|
+
static async isAvailable() {
|
|
388
|
+
// Check Docker first
|
|
389
|
+
try {
|
|
390
|
+
const { execSync } = require('child_process');
|
|
391
|
+
execSync('docker run --rm owasp/zap2docker-stable zap-baseline.py --version', {
|
|
392
|
+
stdio: 'ignore',
|
|
393
|
+
timeout: 10000
|
|
394
|
+
});
|
|
395
|
+
return { available: true, method: 'docker' };
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// Check local installation
|
|
399
|
+
return new Promise((resolve) => {
|
|
400
|
+
const child = spawn('zap-baseline.py', ['--version'], { stdio: 'pipe' });
|
|
401
|
+
child.on('close', (code) => {
|
|
402
|
+
resolve({
|
|
403
|
+
available: code === 0,
|
|
404
|
+
method: 'local',
|
|
405
|
+
error: code !== 0 ? 'ZAP not found. Install from https://www.zaproxy.org/ or use Docker' : undefined
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
child.on('error', () => {
|
|
409
|
+
resolve({
|
|
410
|
+
available: false,
|
|
411
|
+
error: 'ZAP not found. Install from https://www.zaproxy.org/ or use Docker'
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
child.kill();
|
|
416
|
+
resolve({
|
|
417
|
+
available: false,
|
|
418
|
+
error: 'ZAP availability check timed out'
|
|
419
|
+
});
|
|
420
|
+
}, 5000);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Docker Compose Helper
|
|
3
|
+
* Safe Docker Compose operations with health checks and error handling
|
|
4
|
+
*/
|
|
5
|
+
export interface ComposeOptions {
|
|
6
|
+
workingDir: string;
|
|
7
|
+
composeFile?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ComposeError extends Error {
|
|
10
|
+
code: string;
|
|
11
|
+
suggestion: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class ComposeHelper {
|
|
14
|
+
private workingDir;
|
|
15
|
+
private composeFile;
|
|
16
|
+
private isUp;
|
|
17
|
+
constructor(options: ComposeOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Start services with Docker Compose
|
|
20
|
+
*/
|
|
21
|
+
up(timeout?: number): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Stop and remove services
|
|
24
|
+
*/
|
|
25
|
+
down(timeout?: number): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Get service status
|
|
28
|
+
*/
|
|
29
|
+
getStatus(): Promise<{
|
|
30
|
+
service: string;
|
|
31
|
+
status: string;
|
|
32
|
+
ports: string[];
|
|
33
|
+
}[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if Docker is available
|
|
36
|
+
*/
|
|
37
|
+
private checkDockerAvailable;
|
|
38
|
+
/**
|
|
39
|
+
* Execute Docker Compose command
|
|
40
|
+
*/
|
|
41
|
+
private executeCompose;
|
|
42
|
+
/**
|
|
43
|
+
* Execute shell command with timeout
|
|
44
|
+
*/
|
|
45
|
+
private executeCommand;
|
|
46
|
+
/**
|
|
47
|
+
* Validate compose file syntax
|
|
48
|
+
*/
|
|
49
|
+
validateComposeFile(): Promise<{
|
|
50
|
+
valid: boolean;
|
|
51
|
+
errors: string[];
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Get compose file path
|
|
55
|
+
*/
|
|
56
|
+
getComposeFilePath(): string;
|
|
57
|
+
/**
|
|
58
|
+
* Check if services are currently up
|
|
59
|
+
*/
|
|
60
|
+
isServicesUp(): boolean;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/compose.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAa,SAAQ,KAAK;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,IAAI,CAAkB;gBAElB,OAAO,EAAE,cAAc;IAKnC;;OAEG;IACG,EAAE,CAAC,OAAO,GAAE,MAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA6CjD;;OAEG;IACG,IAAI,CAAC,OAAO,GAAE,MAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlD;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IA+BlF;;OAEG;YACW,oBAAoB;IAiBlC;;OAEG;YACW,cAAc;IAW5B;;OAEG;YACW,cAAc;IAwD5B;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAyB1E;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;OAEG;IACH,YAAY,IAAI,OAAO;CAGxB"}
|