@veraxhq/verax 0.2.0 → 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.
Files changed (217) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +15 -5
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +323 -111
  6. package/src/cli/commands/doctor.js +36 -4
  7. package/src/cli/commands/ga.js +243 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +131 -2
  10. package/src/cli/commands/release-check.js +213 -0
  11. package/src/cli/commands/run.js +498 -103
  12. package/src/cli/commands/security-check.js +211 -0
  13. package/src/cli/commands/truth.js +114 -0
  14. package/src/cli/entry.js +305 -68
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +546 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-usestate-detector.js +602 -0
  22. package/src/cli/util/bootstrap-guard.js +86 -0
  23. package/src/cli/util/detection-engine.js +4 -3
  24. package/src/cli/util/determinism-runner.js +123 -0
  25. package/src/cli/util/determinism-writer.js +129 -0
  26. package/src/cli/util/env-url.js +4 -0
  27. package/src/cli/util/events.js +76 -0
  28. package/src/cli/util/expectation-extractor.js +380 -74
  29. package/src/cli/util/findings-writer.js +126 -15
  30. package/src/cli/util/learn-writer.js +3 -1
  31. package/src/cli/util/observation-engine.js +69 -23
  32. package/src/cli/util/observe-writer.js +3 -1
  33. package/src/cli/util/paths.js +6 -14
  34. package/src/cli/util/project-discovery.js +23 -0
  35. package/src/cli/util/project-writer.js +3 -1
  36. package/src/cli/util/redact.js +2 -2
  37. package/src/cli/util/run-resolver.js +64 -0
  38. package/src/cli/util/runtime-budget.js +147 -0
  39. package/src/cli/util/source-requirement.js +55 -0
  40. package/src/cli/util/summary-writer.js +13 -1
  41. package/src/cli/util/svelte-navigation-detector.js +163 -0
  42. package/src/cli/util/svelte-network-detector.js +80 -0
  43. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  44. package/src/cli/util/svelte-state-detector.js +243 -0
  45. package/src/cli/util/vue-navigation-detector.js +177 -0
  46. package/src/cli/util/vue-sfc-extractor.js +162 -0
  47. package/src/cli/util/vue-state-detector.js +215 -0
  48. package/src/types/global.d.ts +28 -0
  49. package/src/types/ts-ast.d.ts +24 -0
  50. package/src/verax/cli/doctor.js +2 -2
  51. package/src/verax/cli/finding-explainer.js +56 -3
  52. package/src/verax/cli/init.js +1 -1
  53. package/src/verax/cli/url-safety.js +12 -2
  54. package/src/verax/cli/wizard.js +13 -2
  55. package/src/verax/core/artifacts/registry.js +154 -0
  56. package/src/verax/core/artifacts/verifier.js +980 -0
  57. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  58. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  59. package/src/verax/core/budget-engine.js +1 -1
  60. package/src/verax/core/capabilities/gates.js +499 -0
  61. package/src/verax/core/capabilities/registry.js +475 -0
  62. package/src/verax/core/confidence/confidence-compute.js +137 -0
  63. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  64. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  65. package/src/verax/core/confidence/confidence-weights.js +44 -0
  66. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  67. package/src/verax/core/confidence/confidence.loader.js +79 -0
  68. package/src/verax/core/confidence/confidence.schema.js +94 -0
  69. package/src/verax/core/confidence-engine-refactor.js +484 -0
  70. package/src/verax/core/confidence-engine.js +486 -0
  71. package/src/verax/core/confidence-engine.js.backup +471 -0
  72. package/src/verax/core/contracts/index.js +29 -0
  73. package/src/verax/core/contracts/types.js +185 -0
  74. package/src/verax/core/contracts/validators.js +381 -0
  75. package/src/verax/core/decision-snapshot.js +31 -4
  76. package/src/verax/core/decisions/decision.trace.js +276 -0
  77. package/src/verax/core/determinism/contract-writer.js +89 -0
  78. package/src/verax/core/determinism/contract.js +139 -0
  79. package/src/verax/core/determinism/diff.js +364 -0
  80. package/src/verax/core/determinism/engine.js +221 -0
  81. package/src/verax/core/determinism/finding-identity.js +148 -0
  82. package/src/verax/core/determinism/normalize.js +438 -0
  83. package/src/verax/core/determinism/report-writer.js +92 -0
  84. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  85. package/src/verax/core/determinism-model.js +35 -6
  86. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  87. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  88. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  89. package/src/verax/core/evidence-builder.js +487 -0
  90. package/src/verax/core/execution-mode-context.js +77 -0
  91. package/src/verax/core/execution-mode-detector.js +190 -0
  92. package/src/verax/core/failures/exit-codes.js +86 -0
  93. package/src/verax/core/failures/failure-summary.js +76 -0
  94. package/src/verax/core/failures/failure.factory.js +225 -0
  95. package/src/verax/core/failures/failure.ledger.js +132 -0
  96. package/src/verax/core/failures/failure.types.js +196 -0
  97. package/src/verax/core/failures/index.js +10 -0
  98. package/src/verax/core/ga/ga-report-writer.js +43 -0
  99. package/src/verax/core/ga/ga.artifact.js +49 -0
  100. package/src/verax/core/ga/ga.contract.js +434 -0
  101. package/src/verax/core/ga/ga.enforcer.js +86 -0
  102. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  103. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  104. package/src/verax/core/guardrails/policy.loader.js +83 -0
  105. package/src/verax/core/guardrails/policy.schema.js +110 -0
  106. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  107. package/src/verax/core/guardrails-engine.js +505 -0
  108. package/src/verax/core/incremental-store.js +15 -7
  109. package/src/verax/core/observe/run-timeline.js +316 -0
  110. package/src/verax/core/perf/perf.contract.js +186 -0
  111. package/src/verax/core/perf/perf.display.js +65 -0
  112. package/src/verax/core/perf/perf.enforcer.js +91 -0
  113. package/src/verax/core/perf/perf.monitor.js +209 -0
  114. package/src/verax/core/perf/perf.report.js +198 -0
  115. package/src/verax/core/pipeline-tracker.js +238 -0
  116. package/src/verax/core/product-definition.js +127 -0
  117. package/src/verax/core/release/provenance.builder.js +271 -0
  118. package/src/verax/core/release/release-report-writer.js +40 -0
  119. package/src/verax/core/release/release.enforcer.js +159 -0
  120. package/src/verax/core/release/reproducibility.check.js +221 -0
  121. package/src/verax/core/release/sbom.builder.js +283 -0
  122. package/src/verax/core/replay-validator.js +4 -4
  123. package/src/verax/core/replay.js +1 -1
  124. package/src/verax/core/report/cross-index.js +192 -0
  125. package/src/verax/core/report/human-summary.js +222 -0
  126. package/src/verax/core/route-intelligence.js +419 -0
  127. package/src/verax/core/security/secrets.scan.js +326 -0
  128. package/src/verax/core/security/security-report.js +50 -0
  129. package/src/verax/core/security/security.enforcer.js +124 -0
  130. package/src/verax/core/security/supplychain.defaults.json +38 -0
  131. package/src/verax/core/security/supplychain.policy.js +326 -0
  132. package/src/verax/core/security/vuln.scan.js +265 -0
  133. package/src/verax/core/silence-impact.js +1 -1
  134. package/src/verax/core/silence-model.js +9 -7
  135. package/src/verax/core/truth/truth.certificate.js +250 -0
  136. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  137. package/src/verax/detect/comparison.js +8 -3
  138. package/src/verax/detect/confidence-engine.js +645 -57
  139. package/src/verax/detect/confidence-helper.js +33 -0
  140. package/src/verax/detect/detection-engine.js +19 -2
  141. package/src/verax/detect/dynamic-route-findings.js +335 -0
  142. package/src/verax/detect/evidence-index.js +15 -65
  143. package/src/verax/detect/expectation-chain-detector.js +417 -0
  144. package/src/verax/detect/expectation-model.js +56 -3
  145. package/src/verax/detect/explanation-helpers.js +1 -1
  146. package/src/verax/detect/finding-detector.js +2 -2
  147. package/src/verax/detect/findings-writer.js +149 -20
  148. package/src/verax/detect/flow-detector.js +4 -4
  149. package/src/verax/detect/index.js +265 -15
  150. package/src/verax/detect/interactive-findings.js +3 -4
  151. package/src/verax/detect/journey-stall-detector.js +558 -0
  152. package/src/verax/detect/route-findings.js +218 -0
  153. package/src/verax/detect/signal-mapper.js +2 -2
  154. package/src/verax/detect/skip-classifier.js +4 -4
  155. package/src/verax/detect/ui-feedback-findings.js +207 -0
  156. package/src/verax/detect/verdict-engine.js +61 -9
  157. package/src/verax/detect/view-switch-correlator.js +242 -0
  158. package/src/verax/flow/flow-engine.js +3 -2
  159. package/src/verax/flow/flow-spec.js +1 -2
  160. package/src/verax/index.js +413 -33
  161. package/src/verax/intel/effect-detector.js +1 -1
  162. package/src/verax/intel/index.js +2 -2
  163. package/src/verax/intel/route-extractor.js +3 -3
  164. package/src/verax/intel/vue-navigation-extractor.js +81 -18
  165. package/src/verax/intel/vue-router-extractor.js +4 -2
  166. package/src/verax/learn/action-contract-extractor.js +684 -66
  167. package/src/verax/learn/ast-contract-extractor.js +53 -1
  168. package/src/verax/learn/index.js +36 -2
  169. package/src/verax/learn/manifest-writer.js +28 -14
  170. package/src/verax/learn/route-extractor.js +1 -1
  171. package/src/verax/learn/route-validator.js +12 -8
  172. package/src/verax/learn/state-extractor.js +1 -1
  173. package/src/verax/learn/static-extractor-navigation.js +1 -1
  174. package/src/verax/learn/static-extractor-validation.js +2 -2
  175. package/src/verax/learn/static-extractor.js +8 -7
  176. package/src/verax/learn/ts-contract-resolver.js +14 -12
  177. package/src/verax/observe/browser.js +22 -3
  178. package/src/verax/observe/console-sensor.js +2 -2
  179. package/src/verax/observe/expectation-executor.js +2 -1
  180. package/src/verax/observe/focus-sensor.js +1 -1
  181. package/src/verax/observe/human-driver.js +29 -10
  182. package/src/verax/observe/index.js +92 -844
  183. package/src/verax/observe/interaction-discovery.js +27 -15
  184. package/src/verax/observe/interaction-runner.js +31 -14
  185. package/src/verax/observe/loading-sensor.js +6 -0
  186. package/src/verax/observe/navigation-sensor.js +1 -1
  187. package/src/verax/observe/observe-context.js +205 -0
  188. package/src/verax/observe/observe-helpers.js +191 -0
  189. package/src/verax/observe/observe-runner.js +226 -0
  190. package/src/verax/observe/observers/budget-observer.js +185 -0
  191. package/src/verax/observe/observers/console-observer.js +102 -0
  192. package/src/verax/observe/observers/coverage-observer.js +107 -0
  193. package/src/verax/observe/observers/interaction-observer.js +471 -0
  194. package/src/verax/observe/observers/navigation-observer.js +132 -0
  195. package/src/verax/observe/observers/network-observer.js +87 -0
  196. package/src/verax/observe/observers/safety-observer.js +82 -0
  197. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  198. package/src/verax/observe/settle.js +1 -0
  199. package/src/verax/observe/state-sensor.js +8 -4
  200. package/src/verax/observe/state-ui-sensor.js +7 -1
  201. package/src/verax/observe/traces-writer.js +27 -16
  202. package/src/verax/observe/ui-feedback-detector.js +742 -0
  203. package/src/verax/observe/ui-signal-sensor.js +155 -2
  204. package/src/verax/scan-summary-writer.js +46 -9
  205. package/src/verax/shared/artifact-manager.js +9 -6
  206. package/src/verax/shared/budget-profiles.js +2 -2
  207. package/src/verax/shared/caching.js +1 -1
  208. package/src/verax/shared/config-loader.js +1 -2
  209. package/src/verax/shared/css-spinner-rules.js +204 -0
  210. package/src/verax/shared/dynamic-route-utils.js +12 -6
  211. package/src/verax/shared/retry-policy.js +1 -6
  212. package/src/verax/shared/root-artifacts.js +1 -1
  213. package/src/verax/shared/view-switch-rules.js +208 -0
  214. package/src/verax/shared/zip-artifacts.js +1 -0
  215. package/src/verax/validate/context-validator.js +1 -1
  216. package/src/verax/observe/index.js.backup +0 -1
  217. package/src/verax/validate/context-validator.js.bak +0 -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
+