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,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Phase 3 Runner
|
|
3
|
+
* Orchestrates hooks, adapters, and proof generation
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { HooksRunner } from '../hooks/runner.js';
|
|
10
|
+
import { PlaywrightApiAdapter } from '../adapters/playwright-api.js';
|
|
11
|
+
import { PlaywrightUiAdapter } from '../adapters/playwright-ui.js';
|
|
12
|
+
import { K6PerfAdapter } from '../adapters/k6-perf.js';
|
|
13
|
+
import { SemgrepSastAdapter } from '../adapters/semgrep-sast.js';
|
|
14
|
+
import { SecurityRedactor } from '../security/redactor.js';
|
|
15
|
+
import { initializeKeys, sign } from '../proof/signer.js';
|
|
16
|
+
import { canonicalize } from '../proof/canonicalize.js';
|
|
17
|
+
import { EvidenceVault } from '../vault/index.js';
|
|
18
|
+
export class Phase3Runner {
|
|
19
|
+
workingDir;
|
|
20
|
+
pack;
|
|
21
|
+
outputDir;
|
|
22
|
+
redactor;
|
|
23
|
+
hooksRunner;
|
|
24
|
+
keyPair;
|
|
25
|
+
vault;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.workingDir = options.workingDir;
|
|
28
|
+
this.pack = options.pack;
|
|
29
|
+
this.outputDir = options.outputDir || join(this.workingDir, '.qa360', 'runs');
|
|
30
|
+
this.redactor = SecurityRedactor.forLogs();
|
|
31
|
+
this.hooksRunner = new HooksRunner({
|
|
32
|
+
workingDir: this.workingDir,
|
|
33
|
+
hooks: this.pack.hooks || {},
|
|
34
|
+
execution: this.pack.execution,
|
|
35
|
+
redactor: this.redactor
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute complete Phase 3 workflow
|
|
40
|
+
*/
|
|
41
|
+
async run() {
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
console.log(chalk.bold.blue(`\n🚀 QA360 Phase 3 Runner - ${this.pack.name}`));
|
|
44
|
+
console.log(chalk.gray(`Gates: ${this.pack.gates.join(', ')}`));
|
|
45
|
+
try {
|
|
46
|
+
// Ensure output directory exists
|
|
47
|
+
this.ensureOutputDir();
|
|
48
|
+
// Initialize cryptographic keys
|
|
49
|
+
console.log(chalk.blue('\n🔑 Initializing Ed25519 keys...'));
|
|
50
|
+
this.keyPair = await initializeKeys();
|
|
51
|
+
console.log(chalk.green(' ✅ Keys ready'));
|
|
52
|
+
// Initialize Evidence Vault
|
|
53
|
+
console.log(chalk.blue('\n🗄️ Initializing Evidence Vault...'));
|
|
54
|
+
const vaultDir = join(this.workingDir, '.qa360');
|
|
55
|
+
this.vault = await EvidenceVault.open(vaultDir);
|
|
56
|
+
console.log(chalk.green(' ✅ Vault ready'));
|
|
57
|
+
// Execute beforeAll hooks
|
|
58
|
+
console.log(chalk.blue('\n🔗 Phase 1: Setup Hooks'));
|
|
59
|
+
const beforeAllResults = await this.hooksRunner.executeHooks('beforeAll');
|
|
60
|
+
// Check if setup failed and should stop
|
|
61
|
+
const setupFailed = beforeAllResults.some(r => !r.success);
|
|
62
|
+
if (setupFailed && this.pack.execution?.on_failure === 'stop') {
|
|
63
|
+
throw new Error('Setup hooks failed, stopping execution');
|
|
64
|
+
}
|
|
65
|
+
// Execute gates
|
|
66
|
+
console.log(chalk.blue('\n🎯 Phase 2: Quality Gates'));
|
|
67
|
+
const gateResults = [];
|
|
68
|
+
for (const gate of this.pack.gates) {
|
|
69
|
+
const gateResult = await this.executeGate(gate);
|
|
70
|
+
gateResults.push(gateResult);
|
|
71
|
+
// Check if gate failed and should stop
|
|
72
|
+
if (!gateResult.success && this.pack.execution?.on_failure === 'stop') {
|
|
73
|
+
console.log(chalk.yellow(`🛑 Gate ${gate} failed, stopping execution`));
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Execute afterAll hooks
|
|
78
|
+
console.log(chalk.blue('\n🔗 Phase 3: Cleanup Hooks'));
|
|
79
|
+
const afterAllResults = await this.hooksRunner.executeHooks('afterAll');
|
|
80
|
+
// Calculate results
|
|
81
|
+
const duration = Date.now() - startTime;
|
|
82
|
+
const summary = this.calculateSummary(gateResults);
|
|
83
|
+
const success = summary.failed === 0;
|
|
84
|
+
// Generate proof
|
|
85
|
+
console.log(chalk.blue('\n📋 Phase 4: Proof Generation'));
|
|
86
|
+
const proofPath = await this.generateProof({
|
|
87
|
+
success,
|
|
88
|
+
pack: this.pack,
|
|
89
|
+
duration,
|
|
90
|
+
gates: gateResults,
|
|
91
|
+
hooks: {
|
|
92
|
+
beforeAll: beforeAllResults,
|
|
93
|
+
beforeEach: [],
|
|
94
|
+
afterEach: [],
|
|
95
|
+
afterAll: afterAllResults
|
|
96
|
+
},
|
|
97
|
+
summary
|
|
98
|
+
});
|
|
99
|
+
// Save to Evidence Vault
|
|
100
|
+
console.log(chalk.blue('\n💾 Phase 5: Evidence Storage'));
|
|
101
|
+
const runId = proofPath.split('/').pop()?.replace('-proof.json', '') || 'unknown';
|
|
102
|
+
await this.saveToVault(runId, {
|
|
103
|
+
success,
|
|
104
|
+
pack: this.pack,
|
|
105
|
+
duration,
|
|
106
|
+
gates: gateResults,
|
|
107
|
+
hooks: {
|
|
108
|
+
beforeAll: beforeAllResults,
|
|
109
|
+
beforeEach: [],
|
|
110
|
+
afterEach: [],
|
|
111
|
+
afterAll: afterAllResults
|
|
112
|
+
},
|
|
113
|
+
summary,
|
|
114
|
+
proofPath
|
|
115
|
+
}, proofPath);
|
|
116
|
+
// Final summary
|
|
117
|
+
console.log(chalk.blue('\n📊 Execution Summary'));
|
|
118
|
+
console.log(` Duration: ${duration}ms`);
|
|
119
|
+
console.log(` Gates: ${summary.passed}/${summary.total} passed`);
|
|
120
|
+
console.log(` Trust Score: ${summary.trustScore}%`);
|
|
121
|
+
console.log(` Proof: ${proofPath}`);
|
|
122
|
+
if (success) {
|
|
123
|
+
console.log(chalk.green('\n✅ All quality gates passed!'));
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.log(chalk.red(`\n❌ ${summary.failed} quality gate(s) failed`));
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
success,
|
|
130
|
+
pack: this.pack,
|
|
131
|
+
duration,
|
|
132
|
+
gates: gateResults,
|
|
133
|
+
hooks: {
|
|
134
|
+
beforeAll: beforeAllResults,
|
|
135
|
+
beforeEach: [],
|
|
136
|
+
afterEach: [],
|
|
137
|
+
afterAll: afterAllResults
|
|
138
|
+
},
|
|
139
|
+
summary,
|
|
140
|
+
proofPath
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const duration = Date.now() - startTime;
|
|
145
|
+
// Ensure cleanup even on error
|
|
146
|
+
try {
|
|
147
|
+
await this.hooksRunner.executeHooks('afterAll');
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Ignore cleanup errors
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
pack: this.pack,
|
|
155
|
+
duration,
|
|
156
|
+
gates: [],
|
|
157
|
+
hooks: { beforeAll: [], beforeEach: [], afterEach: [], afterAll: [] },
|
|
158
|
+
summary: { total: 0, passed: 0, failed: 0, trustScore: 0 },
|
|
159
|
+
error: this.redactor.redact(error instanceof Error ? error.message : 'Unknown error')
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Execute a single quality gate
|
|
165
|
+
*/
|
|
166
|
+
async executeGate(gate) {
|
|
167
|
+
const startTime = Date.now();
|
|
168
|
+
console.log(chalk.cyan(`\n 🎯 Gate: ${gate}`));
|
|
169
|
+
try {
|
|
170
|
+
let result;
|
|
171
|
+
let adapter;
|
|
172
|
+
switch (gate) {
|
|
173
|
+
case 'api_smoke':
|
|
174
|
+
adapter = 'playwright-api';
|
|
175
|
+
result = await this.runApiSmokeGate();
|
|
176
|
+
break;
|
|
177
|
+
case 'ui':
|
|
178
|
+
adapter = 'playwright-ui';
|
|
179
|
+
result = await this.runUiGate();
|
|
180
|
+
break;
|
|
181
|
+
case 'a11y':
|
|
182
|
+
adapter = 'playwright-ui';
|
|
183
|
+
result = await this.runA11yGate();
|
|
184
|
+
break;
|
|
185
|
+
case 'perf':
|
|
186
|
+
adapter = 'k6';
|
|
187
|
+
result = await this.runPerfGate();
|
|
188
|
+
break;
|
|
189
|
+
case 'sast':
|
|
190
|
+
adapter = 'semgrep';
|
|
191
|
+
result = await this.runSastGate();
|
|
192
|
+
break;
|
|
193
|
+
case 'dast':
|
|
194
|
+
adapter = 'zap';
|
|
195
|
+
result = await this.runDastGate();
|
|
196
|
+
break;
|
|
197
|
+
default:
|
|
198
|
+
throw new Error(`Unknown gate: ${gate}`);
|
|
199
|
+
}
|
|
200
|
+
const duration = Date.now() - startTime;
|
|
201
|
+
if (result.success) {
|
|
202
|
+
console.log(chalk.green(` ✅ ${gate} passed (${duration}ms)`));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
console.log(chalk.red(` ❌ ${gate} failed (${duration}ms)`));
|
|
206
|
+
if (result.error) {
|
|
207
|
+
console.log(chalk.red(` 🔍 ${result.error}`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
gate,
|
|
212
|
+
success: result.success,
|
|
213
|
+
duration,
|
|
214
|
+
adapter,
|
|
215
|
+
results: result,
|
|
216
|
+
junit: result.junit,
|
|
217
|
+
error: result.error
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
const duration = Date.now() - startTime;
|
|
222
|
+
const errorMessage = this.redactor.redact(error instanceof Error ? error.message : 'Unknown error');
|
|
223
|
+
console.log(chalk.red(` 💥 ${gate} crashed (${duration}ms): ${errorMessage}`));
|
|
224
|
+
return {
|
|
225
|
+
gate,
|
|
226
|
+
success: false,
|
|
227
|
+
duration,
|
|
228
|
+
adapter: 'unknown',
|
|
229
|
+
results: null,
|
|
230
|
+
error: errorMessage
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Run API smoke gate
|
|
236
|
+
*/
|
|
237
|
+
async runApiSmokeGate() {
|
|
238
|
+
if (!this.pack.targets?.api) {
|
|
239
|
+
throw new Error('API smoke gate requires targets.api configuration');
|
|
240
|
+
}
|
|
241
|
+
const adapter = new PlaywrightApiAdapter();
|
|
242
|
+
return await adapter.runSmokeTests({
|
|
243
|
+
target: this.pack.targets.api,
|
|
244
|
+
budgets: this.pack.budgets,
|
|
245
|
+
timeout: this.pack.execution?.timeout || 10000,
|
|
246
|
+
retries: this.pack.execution?.max_retries || 1
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Run UI gate (includes basic accessibility)
|
|
251
|
+
*/
|
|
252
|
+
async runUiGate() {
|
|
253
|
+
if (!this.pack.targets?.web) {
|
|
254
|
+
throw new Error('UI gate requires targets.web configuration');
|
|
255
|
+
}
|
|
256
|
+
const adapter = new PlaywrightUiAdapter();
|
|
257
|
+
return await adapter.runSmokeTests({
|
|
258
|
+
target: this.pack.targets.web,
|
|
259
|
+
budgets: this.pack.budgets,
|
|
260
|
+
timeout: this.pack.execution?.timeout || 30000
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Run A11y gate (focused accessibility testing)
|
|
265
|
+
*/
|
|
266
|
+
async runA11yGate() {
|
|
267
|
+
// Same as UI gate but focused on accessibility
|
|
268
|
+
return await this.runUiGate();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Run performance gate
|
|
272
|
+
*/
|
|
273
|
+
async runPerfGate() {
|
|
274
|
+
const baseUrl = this.pack.targets?.web?.baseUrl || this.pack.targets?.api?.baseUrl;
|
|
275
|
+
if (!baseUrl) {
|
|
276
|
+
throw new Error('Performance gate requires web or api target');
|
|
277
|
+
}
|
|
278
|
+
const adapter = new K6PerfAdapter(this.workingDir);
|
|
279
|
+
return await adapter.runPerfTest({
|
|
280
|
+
baseUrl,
|
|
281
|
+
budgets: this.pack.budgets,
|
|
282
|
+
duration: '30s',
|
|
283
|
+
vus: 5,
|
|
284
|
+
timeout: 120000
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Run SAST gate
|
|
289
|
+
*/
|
|
290
|
+
async runSastGate() {
|
|
291
|
+
const adapter = new SemgrepSastAdapter();
|
|
292
|
+
return await adapter.runSastScan({
|
|
293
|
+
workingDir: this.workingDir,
|
|
294
|
+
security: this.pack.security,
|
|
295
|
+
rules: ['auto'],
|
|
296
|
+
paths: ['src/', 'lib/', '.'],
|
|
297
|
+
timeout: 120000
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Run DAST gate (mock implementation)
|
|
302
|
+
*/
|
|
303
|
+
async runDastGate() {
|
|
304
|
+
// Mock DAST implementation for Phase 3
|
|
305
|
+
console.log(chalk.yellow(' ⚠️ DAST gate: Mock implementation (ZAP integration in Phase 4)'));
|
|
306
|
+
return {
|
|
307
|
+
success: true,
|
|
308
|
+
findings: [],
|
|
309
|
+
summary: { total: 0, high: 0, medium: 0, low: 0 },
|
|
310
|
+
junit: `<?xml version="1.0" encoding="UTF-8"?>
|
|
311
|
+
<testsuite name="DAST Mock" tests="1" failures="0" time="0">
|
|
312
|
+
<testcase name="Mock DAST Scan" time="0"></testcase>
|
|
313
|
+
</testsuite>`
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Calculate execution summary
|
|
318
|
+
*/
|
|
319
|
+
calculateSummary(gateResults) {
|
|
320
|
+
const total = gateResults.length;
|
|
321
|
+
const passed = gateResults.filter(g => g.success).length;
|
|
322
|
+
const failed = total - passed;
|
|
323
|
+
// Calculate trust score (weighted by gate importance)
|
|
324
|
+
const gateWeights = {
|
|
325
|
+
'api_smoke': 20,
|
|
326
|
+
'ui': 15,
|
|
327
|
+
'perf': 15,
|
|
328
|
+
'sast': 20,
|
|
329
|
+
'dast': 20,
|
|
330
|
+
'a11y': 10
|
|
331
|
+
};
|
|
332
|
+
let totalWeight = 0;
|
|
333
|
+
let passedWeight = 0;
|
|
334
|
+
for (const gate of gateResults) {
|
|
335
|
+
const weight = gateWeights[gate.gate] || 10;
|
|
336
|
+
totalWeight += weight;
|
|
337
|
+
if (gate.success) {
|
|
338
|
+
passedWeight += weight;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const trustScore = totalWeight > 0 ? Math.round((passedWeight / totalWeight) * 100) : 0;
|
|
342
|
+
return { total, passed, failed, trustScore };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Generate cryptographically signed proof document
|
|
346
|
+
*/
|
|
347
|
+
async generateProof(result) {
|
|
348
|
+
const timestamp = new Date().toISOString();
|
|
349
|
+
const runId = `run-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
350
|
+
// Build proof payload (without signature)
|
|
351
|
+
const proofPayload = {
|
|
352
|
+
version: '3.0.0',
|
|
353
|
+
runId,
|
|
354
|
+
timestamp,
|
|
355
|
+
pack: {
|
|
356
|
+
name: result.pack.name,
|
|
357
|
+
version: result.pack.version,
|
|
358
|
+
gates: result.pack.gates
|
|
359
|
+
},
|
|
360
|
+
execution: {
|
|
361
|
+
duration: result.duration,
|
|
362
|
+
success: result.success,
|
|
363
|
+
trustScore: result.summary.trustScore
|
|
364
|
+
},
|
|
365
|
+
gates: result.gates.map(g => ({
|
|
366
|
+
gate: g.gate,
|
|
367
|
+
adapter: g.adapter,
|
|
368
|
+
success: g.success,
|
|
369
|
+
duration: g.duration,
|
|
370
|
+
error: g.error
|
|
371
|
+
})),
|
|
372
|
+
hooks: {
|
|
373
|
+
beforeAll: result.hooks.beforeAll.length,
|
|
374
|
+
afterAll: result.hooks.afterAll.length
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
// Canonicalize the payload for signing
|
|
378
|
+
const canonicalPayload = canonicalize(proofPayload);
|
|
379
|
+
// Sign with Ed25519
|
|
380
|
+
let signatureValue;
|
|
381
|
+
let algorithm;
|
|
382
|
+
let publicKeyB64;
|
|
383
|
+
if (this.keyPair) {
|
|
384
|
+
signatureValue = sign(canonicalPayload, this.keyPair.secretKey);
|
|
385
|
+
algorithm = 'ed25519';
|
|
386
|
+
publicKeyB64 = Buffer.from(this.keyPair.publicKey).toString('base64');
|
|
387
|
+
console.log(chalk.green(' 🔏 Proof signed with Ed25519'));
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
signatureValue = `unsigned-${runId}`;
|
|
391
|
+
algorithm = 'none';
|
|
392
|
+
publicKeyB64 = '';
|
|
393
|
+
console.log(chalk.yellow(' ⚠️ Proof unsigned (no keys available)'));
|
|
394
|
+
}
|
|
395
|
+
// Build complete proof with signature
|
|
396
|
+
const proof = {
|
|
397
|
+
...proofPayload,
|
|
398
|
+
signature: {
|
|
399
|
+
algorithm,
|
|
400
|
+
publicKey: publicKeyB64,
|
|
401
|
+
value: signatureValue,
|
|
402
|
+
timestamp
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
// Save proof
|
|
406
|
+
const proofPath = join(this.outputDir, `${runId}-proof.json`);
|
|
407
|
+
writeFileSync(proofPath, JSON.stringify(proof, null, 2));
|
|
408
|
+
return proofPath;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Save run results to Evidence Vault
|
|
412
|
+
*/
|
|
413
|
+
async saveToVault(runId, result, proofPath) {
|
|
414
|
+
if (!this.vault) {
|
|
415
|
+
console.log(chalk.yellow(' ⚠️ Vault not initialized, skipping storage'));
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
// Begin run in vault (returns actual runId used)
|
|
420
|
+
const { runId: vaultRunId } = await this.vault.beginRun({
|
|
421
|
+
pack_path: 'test-pack.yaml',
|
|
422
|
+
pack_hash: this.hashPack(result.pack)
|
|
423
|
+
});
|
|
424
|
+
// Finish run with final status
|
|
425
|
+
await this.vault.finishRun(vaultRunId, {
|
|
426
|
+
status: result.success ? 'passed' : 'failed',
|
|
427
|
+
trust_score: result.summary.trustScore,
|
|
428
|
+
signature: this.keyPair ? 'ed25519-signed' : undefined
|
|
429
|
+
});
|
|
430
|
+
// Record gate executions
|
|
431
|
+
for (const gate of result.gates) {
|
|
432
|
+
await this.vault.recordGate(runId, {
|
|
433
|
+
name: gate.gate,
|
|
434
|
+
status: gate.success ? 'passed' : 'failed',
|
|
435
|
+
duration_ms: gate.duration,
|
|
436
|
+
metrics_json: JSON.stringify(gate.results?.summary || {})
|
|
437
|
+
});
|
|
438
|
+
// Record finding if gate has error
|
|
439
|
+
if (gate.error) {
|
|
440
|
+
await this.vault.recordFinding(runId, {
|
|
441
|
+
gate: gate.gate,
|
|
442
|
+
severity: 'high',
|
|
443
|
+
rule: 'gate-failure',
|
|
444
|
+
message: gate.error
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
console.log(chalk.green(' 💾 Run saved to Evidence Vault'));
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
console.log(chalk.yellow(` ⚠️ Failed to save to vault: ${error}`));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Generate hash of pack configuration
|
|
456
|
+
*/
|
|
457
|
+
hashPack(pack) {
|
|
458
|
+
return createHash('sha256')
|
|
459
|
+
.update(JSON.stringify(pack))
|
|
460
|
+
.digest('hex')
|
|
461
|
+
.substring(0, 16);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Ensure output directory exists
|
|
465
|
+
*/
|
|
466
|
+
ensureOutputDir() {
|
|
467
|
+
if (!existsSync(this.outputDir)) {
|
|
468
|
+
mkdirSync(this.outputDir, { recursive: true });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Secrets Cryptography
|
|
3
|
+
* AES-256-GCM encryption with PBKDF2 key derivation
|
|
4
|
+
*/
|
|
5
|
+
export interface EncryptedData {
|
|
6
|
+
data: string;
|
|
7
|
+
iv: string;
|
|
8
|
+
salt: string;
|
|
9
|
+
tag: string;
|
|
10
|
+
algorithm: 'aes-256-gcm';
|
|
11
|
+
iterations: number;
|
|
12
|
+
}
|
|
13
|
+
export interface SecretEntry {
|
|
14
|
+
name: string;
|
|
15
|
+
value: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SecretsStore {
|
|
20
|
+
version: string;
|
|
21
|
+
encrypted: EncryptedData;
|
|
22
|
+
checksum: string;
|
|
23
|
+
}
|
|
24
|
+
export declare class SecretsCrypto {
|
|
25
|
+
private static readonly ALGORITHM;
|
|
26
|
+
private static readonly KEY_LENGTH;
|
|
27
|
+
private static readonly IV_LENGTH;
|
|
28
|
+
private static readonly SALT_LENGTH;
|
|
29
|
+
private static readonly TAG_LENGTH;
|
|
30
|
+
private static readonly ITERATIONS;
|
|
31
|
+
/**
|
|
32
|
+
* Encrypt secrets with password-derived key
|
|
33
|
+
*/
|
|
34
|
+
static encrypt(secrets: Record<string, SecretEntry>, password: string): SecretsStore;
|
|
35
|
+
/**
|
|
36
|
+
* Decrypt secrets with password
|
|
37
|
+
*/
|
|
38
|
+
static decrypt(store: SecretsStore, password: string): Record<string, SecretEntry>;
|
|
39
|
+
/**
|
|
40
|
+
* Generate a secure random password
|
|
41
|
+
*/
|
|
42
|
+
static generatePassword(length?: number): string;
|
|
43
|
+
/**
|
|
44
|
+
* Derive password from system keychain or environment
|
|
45
|
+
*/
|
|
46
|
+
static deriveSystemPassword(): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Get password from macOS Keychain
|
|
49
|
+
*/
|
|
50
|
+
private static getMacOSKeychainPassword;
|
|
51
|
+
/**
|
|
52
|
+
* Get password from Linux keyring (using secret-tool)
|
|
53
|
+
*/
|
|
54
|
+
private static getLinuxKeychainPassword;
|
|
55
|
+
/**
|
|
56
|
+
* Get password from Windows Credential Manager
|
|
57
|
+
*/
|
|
58
|
+
private static getWindowsCredentialPassword;
|
|
59
|
+
/**
|
|
60
|
+
* Generate machine-specific password as fallback
|
|
61
|
+
*/
|
|
62
|
+
private static generateMachinePassword;
|
|
63
|
+
/**
|
|
64
|
+
* Calculate checksum for integrity verification
|
|
65
|
+
*/
|
|
66
|
+
private static calculateChecksum;
|
|
67
|
+
/**
|
|
68
|
+
* Redact secret value for logging
|
|
69
|
+
*/
|
|
70
|
+
static redactSecret(value: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Check if a string looks like a secret
|
|
73
|
+
*/
|
|
74
|
+
static looksLikeSecret(value: string): boolean;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/core/secrets/crypto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,aAAa,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,aAAa,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAM;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAM;IACvC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAM;IACzC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAM;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAU;IAE5C;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY;IA6CpF;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IAqClF;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAYpD;;OAEG;WACU,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAyBpD;;OAEG;mBACkB,wBAAwB;IAqB7C;;OAEG;mBACkB,wBAAwB;IAqB7C;;OAEG;mBACkB,4BAA4B;IAqBjD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAmBtC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAKhC;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAO1C;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAW/C"}
|