@veraxhq/verax 0.2.1 → 0.3.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/README.md +14 -18
- package/bin/verax.js +7 -0
- package/package.json +3 -3
- package/src/cli/commands/baseline.js +104 -0
- package/src/cli/commands/default.js +79 -25
- package/src/cli/commands/ga.js +243 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +131 -2
- package/src/cli/commands/release-check.js +213 -0
- package/src/cli/commands/run.js +246 -35
- package/src/cli/commands/security-check.js +211 -0
- package/src/cli/commands/truth.js +114 -0
- package/src/cli/entry.js +304 -67
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +546 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/determinism-runner.js +123 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/env-url.js +4 -0
- package/src/cli/util/expectation-extractor.js +369 -73
- package/src/cli/util/findings-writer.js +126 -16
- package/src/cli/util/learn-writer.js +3 -1
- package/src/cli/util/observe-writer.js +3 -1
- package/src/cli/util/paths.js +3 -12
- package/src/cli/util/project-discovery.js +3 -0
- package/src/cli/util/project-writer.js +3 -1
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +1 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +147 -0
- package/src/cli/util/svelte-state-detector.js +243 -0
- package/src/cli/util/vue-navigation-detector.js +177 -0
- package/src/cli/util/vue-sfc-extractor.js +162 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/verax/cli/finding-explainer.js +56 -3
- package/src/verax/core/artifacts/registry.js +154 -0
- package/src/verax/core/artifacts/verifier.js +980 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +231 -0
- package/src/verax/core/capabilities/gates.js +499 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +137 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +79 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +484 -0
- package/src/verax/core/confidence-engine.js +486 -0
- package/src/verax/core/confidence-engine.js.backup +471 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +185 -0
- package/src/verax/core/contracts/validators.js +381 -0
- package/src/verax/core/decision-snapshot.js +30 -3
- package/src/verax/core/decisions/decision.trace.js +276 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +364 -0
- package/src/verax/core/determinism/engine.js +221 -0
- package/src/verax/core/determinism/finding-identity.js +148 -0
- package/src/verax/core/determinism/normalize.js +438 -0
- package/src/verax/core/determinism/report-writer.js +92 -0
- package/src/verax/core/determinism/run-fingerprint.js +118 -0
- package/src/verax/core/dynamic-route-intelligence.js +528 -0
- package/src/verax/core/evidence/evidence-capture-service.js +307 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +190 -0
- package/src/verax/core/failures/exit-codes.js +86 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +132 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +434 -0
- package/src/verax/core/ga/ga.enforcer.js +86 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +83 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/observe/run-timeline.js +316 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +198 -0
- package/src/verax/core/pipeline-tracker.js +238 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +271 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +159 -0
- package/src/verax/core/release/reproducibility.check.js +221 -0
- package/src/verax/core/release/sbom.builder.js +283 -0
- package/src/verax/core/report/cross-index.js +192 -0
- package/src/verax/core/report/human-summary.js +222 -0
- package/src/verax/core/route-intelligence.js +419 -0
- package/src/verax/core/security/secrets.scan.js +326 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +124 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +326 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +250 -0
- package/src/verax/core/ui-feedback-intelligence.js +515 -0
- package/src/verax/detect/confidence-engine.js +628 -40
- package/src/verax/detect/confidence-helper.js +33 -0
- package/src/verax/detect/detection-engine.js +18 -1
- package/src/verax/detect/dynamic-route-findings.js +335 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +3 -1
- package/src/verax/detect/findings-writer.js +141 -5
- package/src/verax/detect/index.js +229 -5
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/route-findings.js +218 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/verdict-engine.js +57 -3
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/index.js +413 -45
- package/src/verax/learn/action-contract-extractor.js +682 -64
- package/src/verax/learn/route-validator.js +4 -1
- package/src/verax/observe/index.js +88 -843
- package/src/verax/observe/interaction-runner.js +25 -8
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +191 -0
- package/src/verax/observe/observe-runner.js +226 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/observe/ui-signal-sensor.js +148 -2
- package/src/verax/scan-summary-writer.js +42 -8
- package/src/verax/shared/artifact-manager.js +8 -5
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.8 — Secrets Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans git history, working tree, and artifacts for secrets.
|
|
5
|
+
* Any secret detected = BLOCKING.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import { resolve, relative } from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Secret patterns to detect
|
|
15
|
+
*/
|
|
16
|
+
const SECRET_PATTERNS = [
|
|
17
|
+
// API Keys
|
|
18
|
+
{ pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'API_KEY', severity: 'CRITICAL' },
|
|
19
|
+
{ pattern: /(?:api[_-]?token|api_token)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'API_TOKEN', severity: 'CRITICAL' },
|
|
20
|
+
|
|
21
|
+
// Tokens
|
|
22
|
+
{ pattern: /(?:bearer|token)\s+([a-zA-Z0-9_\-\.]{20,})/gi, type: 'BEARER_TOKEN', severity: 'CRITICAL' },
|
|
23
|
+
{ pattern: /(?:access[_-]?token|access_token)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'ACCESS_TOKEN', severity: 'CRITICAL' },
|
|
24
|
+
{ pattern: /(?:refresh[_-]?token|refresh_token)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'REFRESH_TOKEN', severity: 'HIGH' },
|
|
25
|
+
|
|
26
|
+
// Private Keys
|
|
27
|
+
{ pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/gi, type: 'PRIVATE_KEY', severity: 'CRITICAL' },
|
|
28
|
+
{ pattern: /-----BEGIN\s+EC\s+PRIVATE\s+KEY-----/gi, type: 'EC_PRIVATE_KEY', severity: 'CRITICAL' },
|
|
29
|
+
{ pattern: /-----BEGIN\s+DSA\s+PRIVATE\s+KEY-----/gi, type: 'DSA_PRIVATE_KEY', severity: 'CRITICAL' },
|
|
30
|
+
|
|
31
|
+
// AWS
|
|
32
|
+
{ pattern: /AKIA[0-9A-Z]{16}/gi, type: 'AWS_ACCESS_KEY', severity: 'CRITICAL' },
|
|
33
|
+
{ pattern: /aws[_-]?secret[_-]?access[_-]?key\s*[:=]\s*['"]?([a-zA-Z0-9/+=]{40})['"]?/gi, type: 'AWS_SECRET_KEY', severity: 'CRITICAL' },
|
|
34
|
+
|
|
35
|
+
// GitHub
|
|
36
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/gi, type: 'GITHUB_TOKEN', severity: 'CRITICAL' },
|
|
37
|
+
{ pattern: /github[_-]?token\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi, type: 'GITHUB_TOKEN', severity: 'CRITICAL' },
|
|
38
|
+
|
|
39
|
+
// Generic secrets
|
|
40
|
+
{ pattern: /(?:secret|password|pwd|passwd)\s*[:=]\s*['"]?([a-zA-Z0-9_\-\.@]{12,})['"]?/gi, type: 'SECRET', severity: 'HIGH' },
|
|
41
|
+
|
|
42
|
+
// .env files
|
|
43
|
+
{ pattern: /\.env/, type: 'ENV_FILE', severity: 'HIGH', fileOnly: true }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Files/directories to ignore
|
|
48
|
+
*/
|
|
49
|
+
const IGNORE_PATTERNS = [
|
|
50
|
+
/node_modules/,
|
|
51
|
+
/\.git/,
|
|
52
|
+
/\.verax/,
|
|
53
|
+
/dist/,
|
|
54
|
+
/build/,
|
|
55
|
+
/coverage/,
|
|
56
|
+
/\.test-release-integrity/,
|
|
57
|
+
/\.tmp/,
|
|
58
|
+
/release\/.*\.json$/ // Allow release artifacts
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* File extensions to scan
|
|
63
|
+
*/
|
|
64
|
+
const SCANNABLE_EXTENSIONS = [
|
|
65
|
+
'.js', '.jsx', '.ts', '.tsx', '.json', '.yaml', '.yml',
|
|
66
|
+
'.env', '.env.local', '.env.production', '.env.development',
|
|
67
|
+
'.md', '.txt', '.sh', '.bat', '.ps1'
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if file should be ignored
|
|
72
|
+
*/
|
|
73
|
+
function shouldIgnore(filePath, projectDir) {
|
|
74
|
+
const relPath = relative(projectDir, filePath);
|
|
75
|
+
|
|
76
|
+
for (const pattern of IGNORE_PATTERNS) {
|
|
77
|
+
if (pattern.test(relPath)) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if file is scannable
|
|
87
|
+
*/
|
|
88
|
+
function isScannable(filePath) {
|
|
89
|
+
const ext = filePath.toLowerCase();
|
|
90
|
+
return SCANNABLE_EXTENSIONS.some(e => ext.endsWith(e)) ||
|
|
91
|
+
!ext.includes('.') || // Files without extension
|
|
92
|
+
ext.endsWith('.env'); // .env files
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Scan file content for secrets
|
|
97
|
+
*/
|
|
98
|
+
function scanFileContent(content, filePath, projectDir) {
|
|
99
|
+
const findings = [];
|
|
100
|
+
const relPath = relative(projectDir, filePath);
|
|
101
|
+
|
|
102
|
+
for (const { pattern, type, severity, fileOnly } of SECRET_PATTERNS) {
|
|
103
|
+
if (fileOnly && !filePath.includes('.env')) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const matches = [...content.matchAll(pattern)];
|
|
108
|
+
for (const match of matches) {
|
|
109
|
+
// Skip if it's a comment or documentation
|
|
110
|
+
const lineStart = content.lastIndexOf('\n', match.index) + 1;
|
|
111
|
+
const line = content.substring(lineStart, match.index + match[0].length);
|
|
112
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('#')) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
findings.push({
|
|
117
|
+
type,
|
|
118
|
+
severity,
|
|
119
|
+
file: relPath,
|
|
120
|
+
line: content.substring(0, match.index).split('\n').length,
|
|
121
|
+
match: match[0].substring(0, 50), // Truncate for safety
|
|
122
|
+
hash: createHash('sha256').update(match[0]).digest('hex').substring(0, 16)
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return findings;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Scan working tree files
|
|
132
|
+
*/
|
|
133
|
+
function scanWorkingTree(projectDir) {
|
|
134
|
+
const findings = [];
|
|
135
|
+
|
|
136
|
+
function scanDirectory(dir) {
|
|
137
|
+
try {
|
|
138
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
139
|
+
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const fullPath = resolve(dir, entry.name);
|
|
142
|
+
|
|
143
|
+
if (shouldIgnore(fullPath, projectDir)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (entry.isDirectory()) {
|
|
148
|
+
scanDirectory(fullPath);
|
|
149
|
+
} else if (entry.isFile() && isScannable(fullPath)) {
|
|
150
|
+
try {
|
|
151
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
152
|
+
const fileFindings = scanFileContent(content, fullPath, projectDir);
|
|
153
|
+
findings.push(...fileFindings);
|
|
154
|
+
} catch {
|
|
155
|
+
// Skip unreadable files
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Skip inaccessible directories
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
scanDirectory(projectDir);
|
|
165
|
+
return findings;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Scan git history (shallow, last 50 commits)
|
|
170
|
+
*/
|
|
171
|
+
function scanGitHistory(projectDir) {
|
|
172
|
+
const findings = [];
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Get list of files in last 50 commits
|
|
176
|
+
const result = execSync('git log --all --name-only --pretty=format: --last 50', {
|
|
177
|
+
cwd: projectDir,
|
|
178
|
+
encoding: 'utf-8',
|
|
179
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const files = new Set(result.split('\n').filter(f => f.trim() && isScannable(f)));
|
|
183
|
+
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
if (shouldIgnore(resolve(projectDir, file), projectDir)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// Get file content from git
|
|
191
|
+
const content = execSync(`git show HEAD:${file}`, {
|
|
192
|
+
cwd: projectDir,
|
|
193
|
+
encoding: 'utf-8',
|
|
194
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const fileFindings = scanFileContent(content, resolve(projectDir, file), projectDir);
|
|
198
|
+
for (const finding of fileFindings) {
|
|
199
|
+
finding.source = 'git_history';
|
|
200
|
+
}
|
|
201
|
+
findings.push(...fileFindings);
|
|
202
|
+
} catch {
|
|
203
|
+
// File might not exist in current HEAD
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// Not a git repo or git command failed
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return findings;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Scan artifacts directory
|
|
215
|
+
*/
|
|
216
|
+
function scanArtifacts(projectDir) {
|
|
217
|
+
const findings = [];
|
|
218
|
+
const distPath = resolve(projectDir, 'dist');
|
|
219
|
+
|
|
220
|
+
if (!existsSync(distPath)) {
|
|
221
|
+
return findings;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function scanArtifactDir(dir) {
|
|
225
|
+
try {
|
|
226
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
227
|
+
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
const fullPath = resolve(dir, entry.name);
|
|
230
|
+
|
|
231
|
+
if (entry.isDirectory()) {
|
|
232
|
+
scanArtifactDir(fullPath);
|
|
233
|
+
} else if (entry.isFile() && isScannable(fullPath)) {
|
|
234
|
+
try {
|
|
235
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
236
|
+
const fileFindings = scanFileContent(content, fullPath, projectDir);
|
|
237
|
+
for (const finding of fileFindings) {
|
|
238
|
+
finding.source = 'artifacts';
|
|
239
|
+
}
|
|
240
|
+
findings.push(...fileFindings);
|
|
241
|
+
} catch {
|
|
242
|
+
// Skip unreadable files
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// Skip inaccessible directories
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
scanArtifactDir(distPath);
|
|
252
|
+
return findings;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Scan for secrets
|
|
257
|
+
*
|
|
258
|
+
* @param {string} projectDir - Project directory
|
|
259
|
+
* @returns {Promise<Object>} Scan results
|
|
260
|
+
*/
|
|
261
|
+
export async function scanSecrets(projectDir) {
|
|
262
|
+
const findings = [];
|
|
263
|
+
|
|
264
|
+
// Scan working tree
|
|
265
|
+
const workingTreeFindings = scanWorkingTree(projectDir);
|
|
266
|
+
findings.push(...workingTreeFindings);
|
|
267
|
+
|
|
268
|
+
// Scan git history (shallow)
|
|
269
|
+
const gitFindings = scanGitHistory(projectDir);
|
|
270
|
+
findings.push(...gitFindings);
|
|
271
|
+
|
|
272
|
+
// Scan artifacts
|
|
273
|
+
const artifactFindings = scanArtifacts(projectDir);
|
|
274
|
+
findings.push(...artifactFindings);
|
|
275
|
+
|
|
276
|
+
// Deduplicate by file + hash
|
|
277
|
+
const seen = new Set();
|
|
278
|
+
const uniqueFindings = [];
|
|
279
|
+
for (const finding of findings) {
|
|
280
|
+
const key = `${finding.file}:${finding.hash}`;
|
|
281
|
+
if (!seen.has(key)) {
|
|
282
|
+
seen.add(key);
|
|
283
|
+
uniqueFindings.push(finding);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const hasSecrets = uniqueFindings.length > 0;
|
|
288
|
+
const criticalCount = uniqueFindings.filter(f => f.severity === 'CRITICAL').length;
|
|
289
|
+
const highCount = uniqueFindings.filter(f => f.severity === 'HIGH').length;
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
ok: !hasSecrets,
|
|
293
|
+
hasSecrets,
|
|
294
|
+
findings: uniqueFindings,
|
|
295
|
+
summary: {
|
|
296
|
+
total: uniqueFindings.length,
|
|
297
|
+
critical: criticalCount,
|
|
298
|
+
high: highCount,
|
|
299
|
+
byType: uniqueFindings.reduce((acc, f) => {
|
|
300
|
+
acc[f.type] = (acc[f.type] || 0) + 1;
|
|
301
|
+
return acc;
|
|
302
|
+
}, {}),
|
|
303
|
+
scannedAt: new Date().toISOString()
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Write secrets report
|
|
310
|
+
*
|
|
311
|
+
* @param {string} projectDir - Project directory
|
|
312
|
+
* @param {Object} report - Scan results
|
|
313
|
+
* @returns {string} Path to written file
|
|
314
|
+
*/
|
|
315
|
+
export function writeSecretsReport(projectDir, report) {
|
|
316
|
+
const outputDir = resolve(projectDir, 'release');
|
|
317
|
+
if (!existsSync(outputDir)) {
|
|
318
|
+
mkdirSync(outputDir, { recursive: true });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const outputPath = resolve(outputDir, 'security.secrets.report.json');
|
|
322
|
+
writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
323
|
+
|
|
324
|
+
return outputPath;
|
|
325
|
+
}
|
|
326
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ENTERPRISE READINESS — Unified Security Report
|
|
3
|
+
*
|
|
4
|
+
* Produces security.report.json with all security check results in one unified artifact.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Write unified security report
|
|
12
|
+
*
|
|
13
|
+
* @param {string} projectDir - Project directory
|
|
14
|
+
* @param {Object} report - Security check results
|
|
15
|
+
* @param {string} [outputPath] - Optional custom output path
|
|
16
|
+
* @returns {string} Path to written file
|
|
17
|
+
*/
|
|
18
|
+
export function writeSecurityReport(projectDir, report, outputPath = null) {
|
|
19
|
+
// Write to .verax/security/security.report.json (run-less artifact)
|
|
20
|
+
const securityDir = resolve(projectDir, '.verax', 'security');
|
|
21
|
+
if (!existsSync(securityDir)) {
|
|
22
|
+
mkdirSync(securityDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const unifiedPath = outputPath || resolve(securityDir, 'security.report.json');
|
|
26
|
+
|
|
27
|
+
const unifiedReport = {
|
|
28
|
+
contractVersion: 1,
|
|
29
|
+
generatedAt: new Date().toISOString(),
|
|
30
|
+
securityOk: report.securityOk,
|
|
31
|
+
status: report.status,
|
|
32
|
+
summary: report.summary,
|
|
33
|
+
findings: {
|
|
34
|
+
secrets: report.secretsReport || null,
|
|
35
|
+
vulnerabilities: report.vulnReport || null,
|
|
36
|
+
supplychain: report.supplyChainReport || null
|
|
37
|
+
},
|
|
38
|
+
toolAvailability: {
|
|
39
|
+
secrets: report.status?.secrets?.tool || 'VERAX_SECRETS_SCANNER',
|
|
40
|
+
vulnerabilities: report.status?.vulnerabilities?.tool || null,
|
|
41
|
+
osv: report.status?.vulnerabilities?.osvAvailable || false,
|
|
42
|
+
supplychain: report.status?.supplychain?.tool || 'VERAX_SUPPLYCHAIN_POLICY'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
writeFileSync(unifiedPath, JSON.stringify(unifiedReport, null, 2), 'utf-8');
|
|
47
|
+
|
|
48
|
+
return unifiedPath;
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.8 — Security Enforcer
|
|
3
|
+
*
|
|
4
|
+
* Hard lock: blocks GA/Release without SECURITY-OK.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from 'fs';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
import { createInternalFailure } from '../failures/failure.factory.js';
|
|
10
|
+
import { FAILURE_CODE } from '../failures/failure.types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check security status (prefers unified report, falls back to individual reports)
|
|
14
|
+
*
|
|
15
|
+
* @param {string} projectDir - Project directory
|
|
16
|
+
* @returns {Object} Security status
|
|
17
|
+
*/
|
|
18
|
+
export function checkSecurityStatus(projectDir) {
|
|
19
|
+
// Try unified report first
|
|
20
|
+
const unifiedPath = resolve(projectDir, '.verax', 'security', 'security.report.json');
|
|
21
|
+
|
|
22
|
+
const status = {
|
|
23
|
+
exists: false,
|
|
24
|
+
ok: false,
|
|
25
|
+
blockers: []
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (existsSync(unifiedPath)) {
|
|
29
|
+
try {
|
|
30
|
+
const unifiedReport = JSON.parse(readFileSync(unifiedPath, 'utf-8'));
|
|
31
|
+
status.exists = true;
|
|
32
|
+
status.ok = unifiedReport.securityOk || false;
|
|
33
|
+
|
|
34
|
+
if (!status.ok && unifiedReport.status) {
|
|
35
|
+
// Extract blockers from unified report
|
|
36
|
+
if (unifiedReport.status.secrets && !unifiedReport.status.secrets.ok) {
|
|
37
|
+
status.blockers.push(...(unifiedReport.status.secrets.blockers || []));
|
|
38
|
+
}
|
|
39
|
+
if (unifiedReport.status.vulnerabilities && !unifiedReport.status.vulnerabilities.ok) {
|
|
40
|
+
status.blockers.push(...(unifiedReport.status.vulnerabilities.blockers || []));
|
|
41
|
+
}
|
|
42
|
+
if (unifiedReport.status.supplychain && !unifiedReport.status.supplychain.ok) {
|
|
43
|
+
status.blockers.push(...(unifiedReport.status.supplychain.blockers || []));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return status;
|
|
48
|
+
} catch {
|
|
49
|
+
// Fall through to individual reports
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fallback to individual reports for backward compatibility
|
|
54
|
+
const secretsPath = resolve(projectDir, 'release', 'security.secrets.report.json');
|
|
55
|
+
const vulnPath = resolve(projectDir, 'release', 'security.vuln.report.json');
|
|
56
|
+
const supplyChainPath = resolve(projectDir, 'release', 'security.supplychain.report.json');
|
|
57
|
+
|
|
58
|
+
if (!existsSync(secretsPath) || !existsSync(vulnPath) || !existsSync(supplyChainPath)) {
|
|
59
|
+
return status;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
status.exists = true;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Check secrets
|
|
66
|
+
const secretsReport = JSON.parse(readFileSync(secretsPath, 'utf-8'));
|
|
67
|
+
if (secretsReport.hasSecrets) {
|
|
68
|
+
status.blockers.push(`Secrets detected: ${secretsReport.summary.total} finding(s)`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check vulnerabilities
|
|
72
|
+
const vulnReport = JSON.parse(readFileSync(vulnPath, 'utf-8'));
|
|
73
|
+
if (vulnReport.blocking) {
|
|
74
|
+
status.blockers.push(`Critical/High vulnerabilities: ${vulnReport.summary.critical + vulnReport.summary.high} total`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check supply-chain
|
|
78
|
+
const supplyChainReport = JSON.parse(readFileSync(supplyChainPath, 'utf-8'));
|
|
79
|
+
if (!supplyChainReport.ok) {
|
|
80
|
+
status.blockers.push(`Supply-chain violations: ${supplyChainReport.summary.totalViolations} violation(s)`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
status.ok = status.blockers.length === 0;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
status.blockers.push(`Security check failed: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return status;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Enforce security readiness
|
|
93
|
+
*
|
|
94
|
+
* @param {string} projectDir - Project directory
|
|
95
|
+
* @param {string} operation - Operation name (ga, release, publish)
|
|
96
|
+
* @throws {Error} If security not OK
|
|
97
|
+
*/
|
|
98
|
+
export function enforceSecurityReadiness(projectDir, operation = 'operation') {
|
|
99
|
+
const check = checkSecurityStatus(projectDir);
|
|
100
|
+
|
|
101
|
+
if (!check.exists) {
|
|
102
|
+
const failure = createInternalFailure(
|
|
103
|
+
FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
104
|
+
`Cannot ${operation}: Security reports not found. Run 'verax security:check' first.`,
|
|
105
|
+
'security.enforcer',
|
|
106
|
+
{ operation },
|
|
107
|
+
null
|
|
108
|
+
);
|
|
109
|
+
throw failure;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!check.ok) {
|
|
113
|
+
const blockerMessages = check.blockers.join('; ');
|
|
114
|
+
const failure = createInternalFailure(
|
|
115
|
+
FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
|
|
116
|
+
`Cannot ${operation}: SECURITY-BLOCKED. ${blockerMessages}`,
|
|
117
|
+
'security.enforcer',
|
|
118
|
+
{ operation, blockers: check.blockers },
|
|
119
|
+
null
|
|
120
|
+
);
|
|
121
|
+
throw failure;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"licensePolicy": {
|
|
4
|
+
"allowlist": [
|
|
5
|
+
"MIT",
|
|
6
|
+
"Apache-2.0",
|
|
7
|
+
"BSD-2-Clause",
|
|
8
|
+
"BSD-3-Clause",
|
|
9
|
+
"ISC",
|
|
10
|
+
"0BSD"
|
|
11
|
+
],
|
|
12
|
+
"denylist": [
|
|
13
|
+
"GPL-2.0",
|
|
14
|
+
"GPL-3.0",
|
|
15
|
+
"AGPL-1.0",
|
|
16
|
+
"AGPL-3.0",
|
|
17
|
+
"LGPL-2.0",
|
|
18
|
+
"LGPL-2.1",
|
|
19
|
+
"LGPL-3.0"
|
|
20
|
+
],
|
|
21
|
+
"strictMode": false
|
|
22
|
+
},
|
|
23
|
+
"integrityPolicy": {
|
|
24
|
+
"requireIntegrityHash": true,
|
|
25
|
+
"allowedMissingIntegrity": []
|
|
26
|
+
},
|
|
27
|
+
"scriptPolicy": {
|
|
28
|
+
"forbidPostinstall": true,
|
|
29
|
+
"forbidPreinstall": true,
|
|
30
|
+
"forbidInstall": true,
|
|
31
|
+
"allowlist": []
|
|
32
|
+
},
|
|
33
|
+
"sourcePolicy": {
|
|
34
|
+
"requireRegistrySource": true,
|
|
35
|
+
"allowedGitSources": []
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|