@veraxhq/verax 0.2.1 → 0.4.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 (213) hide show
  1. package/README.md +10 -6
  2. package/bin/verax.js +11 -11
  3. package/package.json +29 -8
  4. package/src/cli/commands/baseline.js +103 -0
  5. package/src/cli/commands/default.js +51 -6
  6. package/src/cli/commands/doctor.js +29 -0
  7. package/src/cli/commands/ga.js +246 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +4 -2
  10. package/src/cli/commands/release-check.js +215 -0
  11. package/src/cli/commands/run.js +45 -6
  12. package/src/cli/commands/security-check.js +212 -0
  13. package/src/cli/commands/truth.js +113 -0
  14. package/src/cli/entry.js +30 -20
  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 +544 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-promise-extractor.js +581 -0
  22. package/src/cli/util/ast-usestate-detector.js +602 -0
  23. package/src/cli/util/atomic-write.js +12 -1
  24. package/src/cli/util/bootstrap-guard.js +86 -0
  25. package/src/cli/util/console-reporter.js +72 -0
  26. package/src/cli/util/detection-engine.js +105 -41
  27. package/src/cli/util/determinism-runner.js +124 -0
  28. package/src/cli/util/determinism-writer.js +129 -0
  29. package/src/cli/util/digest-engine.js +359 -0
  30. package/src/cli/util/dom-diff.js +226 -0
  31. package/src/cli/util/evidence-engine.js +287 -0
  32. package/src/cli/util/expectation-extractor.js +151 -5
  33. package/src/cli/util/findings-writer.js +3 -0
  34. package/src/cli/util/framework-detector.js +572 -0
  35. package/src/cli/util/idgen.js +1 -1
  36. package/src/cli/util/interaction-planner.js +529 -0
  37. package/src/cli/util/learn-writer.js +2 -0
  38. package/src/cli/util/ledger-writer.js +110 -0
  39. package/src/cli/util/monorepo-resolver.js +162 -0
  40. package/src/cli/util/observation-engine.js +127 -278
  41. package/src/cli/util/observe-writer.js +2 -0
  42. package/src/cli/util/project-discovery.js +284 -0
  43. package/src/cli/util/project-writer.js +2 -0
  44. package/src/cli/util/run-id.js +23 -27
  45. package/src/cli/util/run-resolver.js +64 -0
  46. package/src/cli/util/run-result.js +778 -0
  47. package/src/cli/util/selector-resolver.js +235 -0
  48. package/src/cli/util/source-requirement.js +55 -0
  49. package/src/cli/util/summary-writer.js +2 -0
  50. package/src/cli/util/svelte-navigation-detector.js +163 -0
  51. package/src/cli/util/svelte-network-detector.js +80 -0
  52. package/src/cli/util/svelte-sfc-extractor.js +146 -0
  53. package/src/cli/util/svelte-state-detector.js +242 -0
  54. package/src/cli/util/trust-activation-integration.js +496 -0
  55. package/src/cli/util/trust-activation-wrapper.js +85 -0
  56. package/src/cli/util/trust-integration-hooks.js +164 -0
  57. package/src/cli/util/types.js +153 -0
  58. package/src/cli/util/url-validation.js +40 -0
  59. package/src/cli/util/vue-navigation-detector.js +178 -0
  60. package/src/cli/util/vue-sfc-extractor.js +161 -0
  61. package/src/cli/util/vue-state-detector.js +215 -0
  62. package/src/types/fs-augment.d.ts +23 -0
  63. package/src/types/global.d.ts +137 -0
  64. package/src/types/internal-types.d.ts +35 -0
  65. package/src/verax/cli/init.js +4 -18
  66. package/src/verax/core/action-classifier.js +4 -3
  67. package/src/verax/core/artifacts/registry.js +139 -0
  68. package/src/verax/core/artifacts/verifier.js +990 -0
  69. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  70. package/src/verax/core/baseline/baseline.snapshot.js +233 -0
  71. package/src/verax/core/capabilities/gates.js +505 -0
  72. package/src/verax/core/capabilities/registry.js +475 -0
  73. package/src/verax/core/confidence/confidence-compute.js +144 -0
  74. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  75. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  76. package/src/verax/core/confidence/confidence-weights.js +44 -0
  77. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  78. package/src/verax/core/confidence/confidence.loader.js +80 -0
  79. package/src/verax/core/confidence/confidence.schema.js +94 -0
  80. package/src/verax/core/confidence-engine-refactor.js +489 -0
  81. package/src/verax/core/confidence-engine.js +625 -0
  82. package/src/verax/core/contracts/index.js +29 -0
  83. package/src/verax/core/contracts/types.js +186 -0
  84. package/src/verax/core/contracts/validators.js +456 -0
  85. package/src/verax/core/decisions/decision.trace.js +278 -0
  86. package/src/verax/core/determinism/contract-writer.js +89 -0
  87. package/src/verax/core/determinism/contract.js +139 -0
  88. package/src/verax/core/determinism/diff.js +405 -0
  89. package/src/verax/core/determinism/engine.js +222 -0
  90. package/src/verax/core/determinism/finding-identity.js +149 -0
  91. package/src/verax/core/determinism/normalize.js +466 -0
  92. package/src/verax/core/determinism/report-writer.js +93 -0
  93. package/src/verax/core/determinism/run-fingerprint.js +123 -0
  94. package/src/verax/core/dynamic-route-intelligence.js +529 -0
  95. package/src/verax/core/evidence/evidence-capture-service.js +308 -0
  96. package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
  97. package/src/verax/core/evidence-builder.js +487 -0
  98. package/src/verax/core/execution-mode-context.js +77 -0
  99. package/src/verax/core/execution-mode-detector.js +192 -0
  100. package/src/verax/core/failures/exit-codes.js +88 -0
  101. package/src/verax/core/failures/failure-summary.js +76 -0
  102. package/src/verax/core/failures/failure.factory.js +225 -0
  103. package/src/verax/core/failures/failure.ledger.js +133 -0
  104. package/src/verax/core/failures/failure.types.js +196 -0
  105. package/src/verax/core/failures/index.js +10 -0
  106. package/src/verax/core/ga/ga-report-writer.js +43 -0
  107. package/src/verax/core/ga/ga.artifact.js +49 -0
  108. package/src/verax/core/ga/ga.contract.js +435 -0
  109. package/src/verax/core/ga/ga.enforcer.js +87 -0
  110. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  111. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  112. package/src/verax/core/guardrails/policy.loader.js +84 -0
  113. package/src/verax/core/guardrails/policy.schema.js +110 -0
  114. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  115. package/src/verax/core/guardrails-engine.js +505 -0
  116. package/src/verax/core/incremental-store.js +1 -0
  117. package/src/verax/core/integrity/budget.js +138 -0
  118. package/src/verax/core/integrity/determinism.js +342 -0
  119. package/src/verax/core/integrity/integrity.js +208 -0
  120. package/src/verax/core/integrity/poisoning.js +108 -0
  121. package/src/verax/core/integrity/transaction.js +140 -0
  122. package/src/verax/core/observe/run-timeline.js +318 -0
  123. package/src/verax/core/perf/perf.contract.js +186 -0
  124. package/src/verax/core/perf/perf.display.js +65 -0
  125. package/src/verax/core/perf/perf.enforcer.js +91 -0
  126. package/src/verax/core/perf/perf.monitor.js +209 -0
  127. package/src/verax/core/perf/perf.report.js +200 -0
  128. package/src/verax/core/pipeline-tracker.js +243 -0
  129. package/src/verax/core/product-definition.js +127 -0
  130. package/src/verax/core/release/provenance.builder.js +130 -0
  131. package/src/verax/core/release/release-report-writer.js +40 -0
  132. package/src/verax/core/release/release.enforcer.js +164 -0
  133. package/src/verax/core/release/reproducibility.check.js +222 -0
  134. package/src/verax/core/release/sbom.builder.js +292 -0
  135. package/src/verax/core/replay-validator.js +2 -0
  136. package/src/verax/core/replay.js +4 -0
  137. package/src/verax/core/report/cross-index.js +195 -0
  138. package/src/verax/core/report/human-summary.js +362 -0
  139. package/src/verax/core/route-intelligence.js +420 -0
  140. package/src/verax/core/run-id.js +6 -3
  141. package/src/verax/core/run-manifest.js +4 -3
  142. package/src/verax/core/security/secrets.scan.js +329 -0
  143. package/src/verax/core/security/security-report.js +50 -0
  144. package/src/verax/core/security/security.enforcer.js +128 -0
  145. package/src/verax/core/security/supplychain.defaults.json +38 -0
  146. package/src/verax/core/security/supplychain.policy.js +334 -0
  147. package/src/verax/core/security/vuln.scan.js +265 -0
  148. package/src/verax/core/truth/truth.certificate.js +252 -0
  149. package/src/verax/core/ui-feedback-intelligence.js +481 -0
  150. package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
  151. package/src/verax/detect/confidence-engine.js +62 -34
  152. package/src/verax/detect/confidence-helper.js +34 -0
  153. package/src/verax/detect/dynamic-route-findings.js +338 -0
  154. package/src/verax/detect/expectation-chain-detector.js +417 -0
  155. package/src/verax/detect/expectation-model.js +2 -2
  156. package/src/verax/detect/failure-cause-inference.js +293 -0
  157. package/src/verax/detect/findings-writer.js +131 -35
  158. package/src/verax/detect/flow-detector.js +2 -2
  159. package/src/verax/detect/form-silent-failure.js +98 -0
  160. package/src/verax/detect/index.js +46 -5
  161. package/src/verax/detect/invariants-enforcer.js +147 -0
  162. package/src/verax/detect/journey-stall-detector.js +558 -0
  163. package/src/verax/detect/navigation-silent-failure.js +82 -0
  164. package/src/verax/detect/problem-aggregator.js +361 -0
  165. package/src/verax/detect/route-findings.js +219 -0
  166. package/src/verax/detect/summary-writer.js +477 -0
  167. package/src/verax/detect/test-failure-cause-inference.js +314 -0
  168. package/src/verax/detect/ui-feedback-findings.js +207 -0
  169. package/src/verax/detect/view-switch-correlator.js +242 -0
  170. package/src/verax/flow/flow-engine.js +2 -1
  171. package/src/verax/flow/flow-spec.js +0 -6
  172. package/src/verax/index.js +4 -0
  173. package/src/verax/intel/ts-program.js +1 -0
  174. package/src/verax/intel/vue-navigation-extractor.js +3 -0
  175. package/src/verax/learn/action-contract-extractor.js +3 -0
  176. package/src/verax/learn/ast-contract-extractor.js +1 -1
  177. package/src/verax/learn/flow-extractor.js +1 -0
  178. package/src/verax/learn/project-detector.js +5 -0
  179. package/src/verax/learn/react-router-extractor.js +2 -0
  180. package/src/verax/learn/source-instrumenter.js +1 -0
  181. package/src/verax/learn/state-extractor.js +2 -1
  182. package/src/verax/learn/static-extractor.js +1 -0
  183. package/src/verax/observe/coverage-gaps.js +132 -0
  184. package/src/verax/observe/expectation-handler.js +126 -0
  185. package/src/verax/observe/incremental-skip.js +46 -0
  186. package/src/verax/observe/index.js +51 -155
  187. package/src/verax/observe/interaction-executor.js +192 -0
  188. package/src/verax/observe/interaction-runner.js +782 -513
  189. package/src/verax/observe/network-firewall.js +86 -0
  190. package/src/verax/observe/observation-builder.js +169 -0
  191. package/src/verax/observe/observe-context.js +205 -0
  192. package/src/verax/observe/observe-helpers.js +192 -0
  193. package/src/verax/observe/observe-runner.js +230 -0
  194. package/src/verax/observe/observers/budget-observer.js +185 -0
  195. package/src/verax/observe/observers/console-observer.js +102 -0
  196. package/src/verax/observe/observers/coverage-observer.js +107 -0
  197. package/src/verax/observe/observers/interaction-observer.js +471 -0
  198. package/src/verax/observe/observers/navigation-observer.js +132 -0
  199. package/src/verax/observe/observers/network-observer.js +87 -0
  200. package/src/verax/observe/observers/safety-observer.js +82 -0
  201. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  202. package/src/verax/observe/page-traversal.js +138 -0
  203. package/src/verax/observe/snapshot-ops.js +94 -0
  204. package/src/verax/observe/ui-feedback-detector.js +742 -0
  205. package/src/verax/scan-summary-writer.js +2 -0
  206. package/src/verax/shared/artifact-manager.js +25 -5
  207. package/src/verax/shared/caching.js +1 -0
  208. package/src/verax/shared/css-spinner-rules.js +204 -0
  209. package/src/verax/shared/expectation-tracker.js +1 -0
  210. package/src/verax/shared/view-switch-rules.js +208 -0
  211. package/src/verax/shared/zip-artifacts.js +6 -0
  212. package/src/verax/shared/config-loader.js +0 -169
  213. /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
@@ -0,0 +1,329 @@
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
+ // @ts-expect-error - digest returns string
123
+ hash: createHash('sha256').update(match[0]).digest('hex').substring(0, 16)
124
+ });
125
+ }
126
+ }
127
+
128
+ return findings;
129
+ }
130
+
131
+ /**
132
+ * Scan working tree files
133
+ */
134
+ function scanWorkingTree(projectDir) {
135
+ const findings = [];
136
+
137
+ function scanDirectory(dir) {
138
+ try {
139
+ const entries = readdirSync(dir, { withFileTypes: true });
140
+
141
+ for (const entry of entries) {
142
+ const fullPath = resolve(dir, entry.name);
143
+
144
+ if (shouldIgnore(fullPath, projectDir)) {
145
+ continue;
146
+ }
147
+
148
+ if (entry.isDirectory()) {
149
+ scanDirectory(fullPath);
150
+ } else if (entry.isFile() && isScannable(fullPath)) {
151
+ try {
152
+ const content = readFileSync(fullPath, 'utf-8');
153
+ const fileFindings = scanFileContent(content, fullPath, projectDir);
154
+ findings.push(...fileFindings);
155
+ } catch {
156
+ // Skip unreadable files
157
+ }
158
+ }
159
+ }
160
+ } catch {
161
+ // Skip inaccessible directories
162
+ }
163
+ }
164
+
165
+ scanDirectory(projectDir);
166
+ return findings;
167
+ }
168
+
169
+ /**
170
+ * Scan git history (shallow, last 50 commits)
171
+ */
172
+ function scanGitHistory(projectDir) {
173
+ const findings = [];
174
+
175
+ try {
176
+ // Get list of files in last 50 commits
177
+ const result = execSync('git log --all --name-only --pretty=format: --last 50', {
178
+ cwd: projectDir,
179
+ encoding: 'utf-8',
180
+ stdio: ['ignore', 'pipe', 'ignore']
181
+ });
182
+
183
+ const files = new Set(result.split('\n').filter(f => f.trim() && isScannable(f)));
184
+
185
+ for (const file of files) {
186
+ if (shouldIgnore(resolve(projectDir, file), projectDir)) {
187
+ continue;
188
+ }
189
+
190
+ try {
191
+ // Get file content from git
192
+ const content = execSync(`git show HEAD:${file}`, {
193
+ cwd: projectDir,
194
+ encoding: 'utf-8',
195
+ stdio: ['ignore', 'pipe', 'ignore']
196
+ });
197
+
198
+ const fileFindings = scanFileContent(content, resolve(projectDir, file), projectDir);
199
+ for (const finding of fileFindings) {
200
+ // @ts-expect-error - Dynamic finding property
201
+ finding.source = 'git_history';
202
+ }
203
+ findings.push(...fileFindings);
204
+ } catch {
205
+ // File might not exist in current HEAD
206
+ }
207
+ }
208
+ } catch {
209
+ // Not a git repo or git command failed
210
+ }
211
+
212
+ return findings;
213
+ }
214
+
215
+ /**
216
+ * Scan artifacts directory
217
+ */
218
+ function scanArtifacts(projectDir) {
219
+ const findings = [];
220
+ const distPath = resolve(projectDir, 'dist');
221
+
222
+ if (!existsSync(distPath)) {
223
+ return findings;
224
+ }
225
+
226
+ function scanArtifactDir(dir) {
227
+ try {
228
+ const entries = readdirSync(dir, { withFileTypes: true });
229
+
230
+ for (const entry of entries) {
231
+ const fullPath = resolve(dir, entry.name);
232
+
233
+ if (entry.isDirectory()) {
234
+ scanArtifactDir(fullPath);
235
+ } else if (entry.isFile() && isScannable(fullPath)) {
236
+ try {
237
+ const content = readFileSync(fullPath, 'utf-8');
238
+ const fileFindings = scanFileContent(content, fullPath, projectDir);
239
+ for (const finding of fileFindings) {
240
+ // @ts-expect-error - Dynamic finding property
241
+ finding.source = 'artifacts';
242
+ }
243
+ findings.push(...fileFindings);
244
+ } catch {
245
+ // Skip unreadable files
246
+ }
247
+ }
248
+ }
249
+ } catch {
250
+ // Skip inaccessible directories
251
+ }
252
+ }
253
+
254
+ scanArtifactDir(distPath);
255
+ return findings;
256
+ }
257
+
258
+ /**
259
+ * Scan for secrets
260
+ *
261
+ * @param {string} projectDir - Project directory
262
+ * @returns {Promise<Object>} Scan results
263
+ */
264
+ export async function scanSecrets(projectDir) {
265
+ const findings = [];
266
+
267
+ // Scan working tree
268
+ const workingTreeFindings = scanWorkingTree(projectDir);
269
+ findings.push(...workingTreeFindings);
270
+
271
+ // Scan git history (shallow)
272
+ const gitFindings = scanGitHistory(projectDir);
273
+ findings.push(...gitFindings);
274
+
275
+ // Scan artifacts
276
+ const artifactFindings = scanArtifacts(projectDir);
277
+ findings.push(...artifactFindings);
278
+
279
+ // Deduplicate by file + hash
280
+ const seen = new Set();
281
+ const uniqueFindings = [];
282
+ for (const finding of findings) {
283
+ const key = `${finding.file}:${finding.hash}`;
284
+ if (!seen.has(key)) {
285
+ seen.add(key);
286
+ uniqueFindings.push(finding);
287
+ }
288
+ }
289
+
290
+ const hasSecrets = uniqueFindings.length > 0;
291
+ const criticalCount = uniqueFindings.filter(f => f.severity === 'CRITICAL').length;
292
+ const highCount = uniqueFindings.filter(f => f.severity === 'HIGH').length;
293
+
294
+ return {
295
+ ok: !hasSecrets,
296
+ hasSecrets,
297
+ findings: uniqueFindings,
298
+ summary: {
299
+ total: uniqueFindings.length,
300
+ critical: criticalCount,
301
+ high: highCount,
302
+ byType: uniqueFindings.reduce((acc, f) => {
303
+ acc[f.type] = (acc[f.type] || 0) + 1;
304
+ return acc;
305
+ }, {}),
306
+ scannedAt: new Date().toISOString()
307
+ }
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Write secrets report
313
+ *
314
+ * @param {string} projectDir - Project directory
315
+ * @param {Object} report - Scan results
316
+ * @returns {string} Path to written file
317
+ */
318
+ export function writeSecretsReport(projectDir, report) {
319
+ const outputDir = resolve(projectDir, 'release');
320
+ if (!existsSync(outputDir)) {
321
+ mkdirSync(outputDir, { recursive: true });
322
+ }
323
+
324
+ const outputPath = resolve(outputDir, 'security.secrets.report.json');
325
+ writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
326
+
327
+ return outputPath;
328
+ }
329
+
@@ -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,128 @@
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
+ // @ts-expect-error - readFileSync with encoding returns string
31
+ const unifiedReport = JSON.parse(readFileSync(unifiedPath, 'utf-8'));
32
+ status.exists = true;
33
+ status.ok = unifiedReport.securityOk || false;
34
+
35
+ if (!status.ok && unifiedReport.status) {
36
+ // Extract blockers from unified report
37
+ if (unifiedReport.status.secrets && !unifiedReport.status.secrets.ok) {
38
+ status.blockers.push(...(unifiedReport.status.secrets.blockers || []));
39
+ }
40
+ if (unifiedReport.status.vulnerabilities && !unifiedReport.status.vulnerabilities.ok) {
41
+ status.blockers.push(...(unifiedReport.status.vulnerabilities.blockers || []));
42
+ }
43
+ if (unifiedReport.status.supplychain && !unifiedReport.status.supplychain.ok) {
44
+ status.blockers.push(...(unifiedReport.status.supplychain.blockers || []));
45
+ }
46
+ }
47
+
48
+ return status;
49
+ } catch {
50
+ // Fall through to individual reports
51
+ }
52
+ }
53
+
54
+ // Fallback to individual reports for backward compatibility
55
+ const secretsPath = resolve(projectDir, 'release', 'security.secrets.report.json');
56
+ const vulnPath = resolve(projectDir, 'release', 'security.vuln.report.json');
57
+ const supplyChainPath = resolve(projectDir, 'release', 'security.supplychain.report.json');
58
+
59
+ if (!existsSync(secretsPath) || !existsSync(vulnPath) || !existsSync(supplyChainPath)) {
60
+ return status;
61
+ }
62
+
63
+ status.exists = true;
64
+
65
+ try {
66
+ // Check secrets
67
+ // @ts-expect-error - readFileSync with encoding returns string
68
+ const secretsReport = JSON.parse(readFileSync(secretsPath, 'utf-8'));
69
+ if (secretsReport.hasSecrets) {
70
+ status.blockers.push(`Secrets detected: ${secretsReport.summary.total} finding(s)`);
71
+ }
72
+
73
+ // Check vulnerabilities
74
+ // @ts-expect-error - readFileSync with encoding returns string
75
+ const vulnReport = JSON.parse(readFileSync(vulnPath, 'utf-8'));
76
+ if (vulnReport.blocking) {
77
+ status.blockers.push(`Critical/High vulnerabilities: ${vulnReport.summary.critical + vulnReport.summary.high} total`);
78
+ }
79
+
80
+ // Check supply-chain
81
+ // @ts-expect-error - readFileSync with encoding returns string
82
+ const supplyChainReport = JSON.parse(readFileSync(supplyChainPath, 'utf-8'));
83
+ if (!supplyChainReport.ok) {
84
+ status.blockers.push(`Supply-chain violations: ${supplyChainReport.summary.totalViolations} violation(s)`);
85
+ }
86
+
87
+ status.ok = status.blockers.length === 0;
88
+ } catch (error) {
89
+ status.blockers.push(`Security check failed: ${error.message}`);
90
+ }
91
+
92
+ return status;
93
+ }
94
+
95
+ /**
96
+ * Enforce security readiness
97
+ *
98
+ * @param {string} projectDir - Project directory
99
+ * @param {string} operation - Operation name (ga, release, publish)
100
+ * @throws {Error} If security not OK
101
+ */
102
+ export function enforceSecurityReadiness(projectDir, operation = 'operation') {
103
+ const check = checkSecurityStatus(projectDir);
104
+
105
+ if (!check.exists) {
106
+ const failure = createInternalFailure(
107
+ FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
108
+ `Cannot ${operation}: Security reports not found. Run 'verax security:check' first.`,
109
+ 'security.enforcer',
110
+ { operation },
111
+ null
112
+ );
113
+ throw failure;
114
+ }
115
+
116
+ if (!check.ok) {
117
+ const blockerMessages = check.blockers.join('; ');
118
+ const failure = createInternalFailure(
119
+ FAILURE_CODE.INTERNAL_UNEXPECTED_ERROR,
120
+ `Cannot ${operation}: SECURITY-BLOCKED. ${blockerMessages}`,
121
+ 'security.enforcer',
122
+ { operation, blockers: check.blockers },
123
+ null
124
+ );
125
+ throw failure;
126
+ }
127
+ }
128
+
@@ -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
+