@quantracode/vibecheck 0.0.1 → 0.0.2

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 (208) hide show
  1. package/README.md +6 -6
  2. package/dist/index.d.ts +0 -2
  3. package/dist/index.js +7902 -8
  4. package/package.json +13 -7
  5. package/dist/__tests__/cli.test.d.ts +0 -2
  6. package/dist/__tests__/cli.test.d.ts.map +0 -1
  7. package/dist/__tests__/cli.test.js +0 -243
  8. package/dist/__tests__/fixtures/safe-app/app/api/users/route.js +0 -36
  9. package/dist/__tests__/fixtures/vulnerable-app/app/api/users/route.js +0 -28
  10. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts +0 -4
  11. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts.map +0 -1
  12. package/dist/__tests__/fixtures/vulnerable-app/lib/config.js +0 -6
  13. package/dist/__tests__/scanners/env-config.test.d.ts +0 -2
  14. package/dist/__tests__/scanners/env-config.test.d.ts.map +0 -1
  15. package/dist/__tests__/scanners/env-config.test.js +0 -142
  16. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts +0 -2
  17. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts.map +0 -1
  18. package/dist/__tests__/scanners/nextjs-middleware.test.js +0 -193
  19. package/dist/__tests__/scanners/scanner-packs.test.d.ts +0 -2
  20. package/dist/__tests__/scanners/scanner-packs.test.d.ts.map +0 -1
  21. package/dist/__tests__/scanners/scanner-packs.test.js +0 -126
  22. package/dist/__tests__/scanners/unused-security-imports.test.d.ts +0 -2
  23. package/dist/__tests__/scanners/unused-security-imports.test.d.ts.map +0 -1
  24. package/dist/__tests__/scanners/unused-security-imports.test.js +0 -145
  25. package/dist/commands/demo-artifact.d.ts +0 -7
  26. package/dist/commands/demo-artifact.d.ts.map +0 -1
  27. package/dist/commands/demo-artifact.js +0 -322
  28. package/dist/commands/evaluate.d.ts +0 -30
  29. package/dist/commands/evaluate.d.ts.map +0 -1
  30. package/dist/commands/evaluate.js +0 -258
  31. package/dist/commands/explain.d.ts +0 -12
  32. package/dist/commands/explain.d.ts.map +0 -1
  33. package/dist/commands/explain.js +0 -214
  34. package/dist/commands/index.d.ts +0 -7
  35. package/dist/commands/index.d.ts.map +0 -1
  36. package/dist/commands/index.js +0 -6
  37. package/dist/commands/intent.d.ts +0 -21
  38. package/dist/commands/intent.d.ts.map +0 -1
  39. package/dist/commands/intent.js +0 -192
  40. package/dist/commands/scan.d.ts +0 -44
  41. package/dist/commands/scan.d.ts.map +0 -1
  42. package/dist/commands/scan.js +0 -497
  43. package/dist/commands/waivers.d.ts +0 -30
  44. package/dist/commands/waivers.d.ts.map +0 -1
  45. package/dist/commands/waivers.js +0 -249
  46. package/dist/index.d.ts.map +0 -1
  47. package/dist/phase3/index.d.ts +0 -11
  48. package/dist/phase3/index.d.ts.map +0 -1
  49. package/dist/phase3/index.js +0 -12
  50. package/dist/phase3/intent-miner.d.ts +0 -32
  51. package/dist/phase3/intent-miner.d.ts.map +0 -1
  52. package/dist/phase3/intent-miner.js +0 -323
  53. package/dist/phase3/proof-trace-builder.d.ts +0 -42
  54. package/dist/phase3/proof-trace-builder.d.ts.map +0 -1
  55. package/dist/phase3/proof-trace-builder.js +0 -441
  56. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts +0 -15
  57. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts.map +0 -1
  58. package/dist/phase3/scanners/auth-by-ui-server-gap.js +0 -237
  59. package/dist/phase3/scanners/comment-claim-unproven.d.ts +0 -14
  60. package/dist/phase3/scanners/comment-claim-unproven.d.ts.map +0 -1
  61. package/dist/phase3/scanners/comment-claim-unproven.js +0 -161
  62. package/dist/phase3/scanners/index.d.ts +0 -31
  63. package/dist/phase3/scanners/index.d.ts.map +0 -1
  64. package/dist/phase3/scanners/index.js +0 -40
  65. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts +0 -14
  66. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts.map +0 -1
  67. package/dist/phase3/scanners/middleware-assumed-not-matching.js +0 -172
  68. package/dist/phase3/scanners/validation-claimed-missing.d.ts +0 -15
  69. package/dist/phase3/scanners/validation-claimed-missing.d.ts.map +0 -1
  70. package/dist/phase3/scanners/validation-claimed-missing.js +0 -204
  71. package/dist/scanners/abuse/compute-abuse.d.ts +0 -20
  72. package/dist/scanners/abuse/compute-abuse.d.ts.map +0 -1
  73. package/dist/scanners/abuse/compute-abuse.js +0 -509
  74. package/dist/scanners/abuse/index.d.ts +0 -12
  75. package/dist/scanners/abuse/index.d.ts.map +0 -1
  76. package/dist/scanners/abuse/index.js +0 -15
  77. package/dist/scanners/auth/index.d.ts +0 -5
  78. package/dist/scanners/auth/index.d.ts.map +0 -1
  79. package/dist/scanners/auth/index.js +0 -10
  80. package/dist/scanners/auth/middleware-gap.d.ts +0 -22
  81. package/dist/scanners/auth/middleware-gap.d.ts.map +0 -1
  82. package/dist/scanners/auth/middleware-gap.js +0 -203
  83. package/dist/scanners/auth/unprotected-api-route.d.ts +0 -12
  84. package/dist/scanners/auth/unprotected-api-route.d.ts.map +0 -1
  85. package/dist/scanners/auth/unprotected-api-route.js +0 -126
  86. package/dist/scanners/config/index.d.ts +0 -5
  87. package/dist/scanners/config/index.d.ts.map +0 -1
  88. package/dist/scanners/config/index.js +0 -10
  89. package/dist/scanners/config/insecure-defaults.d.ts +0 -12
  90. package/dist/scanners/config/insecure-defaults.d.ts.map +0 -1
  91. package/dist/scanners/config/insecure-defaults.js +0 -77
  92. package/dist/scanners/config/undocumented-env.d.ts +0 -24
  93. package/dist/scanners/config/undocumented-env.d.ts.map +0 -1
  94. package/dist/scanners/config/undocumented-env.js +0 -159
  95. package/dist/scanners/crypto/index.d.ts +0 -6
  96. package/dist/scanners/crypto/index.d.ts.map +0 -1
  97. package/dist/scanners/crypto/index.js +0 -11
  98. package/dist/scanners/crypto/jwt-decode-unverified.d.ts +0 -14
  99. package/dist/scanners/crypto/jwt-decode-unverified.d.ts.map +0 -1
  100. package/dist/scanners/crypto/jwt-decode-unverified.js +0 -87
  101. package/dist/scanners/crypto/math-random-tokens.d.ts +0 -13
  102. package/dist/scanners/crypto/math-random-tokens.d.ts.map +0 -1
  103. package/dist/scanners/crypto/math-random-tokens.js +0 -80
  104. package/dist/scanners/crypto/weak-hashing.d.ts +0 -11
  105. package/dist/scanners/crypto/weak-hashing.d.ts.map +0 -1
  106. package/dist/scanners/crypto/weak-hashing.js +0 -95
  107. package/dist/scanners/env-config.d.ts +0 -24
  108. package/dist/scanners/env-config.d.ts.map +0 -1
  109. package/dist/scanners/env-config.js +0 -164
  110. package/dist/scanners/hallucinations/index.d.ts +0 -4
  111. package/dist/scanners/hallucinations/index.d.ts.map +0 -1
  112. package/dist/scanners/hallucinations/index.js +0 -8
  113. package/dist/scanners/hallucinations/unused-security-imports.d.ts +0 -36
  114. package/dist/scanners/hallucinations/unused-security-imports.d.ts.map +0 -1
  115. package/dist/scanners/hallucinations/unused-security-imports.js +0 -309
  116. package/dist/scanners/helpers/ast-helpers.d.ts +0 -6
  117. package/dist/scanners/helpers/ast-helpers.d.ts.map +0 -1
  118. package/dist/scanners/helpers/ast-helpers.js +0 -945
  119. package/dist/scanners/helpers/context-builder.d.ts +0 -17
  120. package/dist/scanners/helpers/context-builder.d.ts.map +0 -1
  121. package/dist/scanners/helpers/context-builder.js +0 -148
  122. package/dist/scanners/helpers/index.d.ts +0 -3
  123. package/dist/scanners/helpers/index.d.ts.map +0 -1
  124. package/dist/scanners/helpers/index.js +0 -2
  125. package/dist/scanners/index.d.ts +0 -30
  126. package/dist/scanners/index.d.ts.map +0 -1
  127. package/dist/scanners/index.js +0 -102
  128. package/dist/scanners/middleware/index.d.ts +0 -4
  129. package/dist/scanners/middleware/index.d.ts.map +0 -1
  130. package/dist/scanners/middleware/index.js +0 -7
  131. package/dist/scanners/middleware/missing-rate-limit.d.ts +0 -13
  132. package/dist/scanners/middleware/missing-rate-limit.d.ts.map +0 -1
  133. package/dist/scanners/middleware/missing-rate-limit.js +0 -140
  134. package/dist/scanners/network/cors-misconfiguration.d.ts +0 -14
  135. package/dist/scanners/network/cors-misconfiguration.d.ts.map +0 -1
  136. package/dist/scanners/network/cors-misconfiguration.js +0 -89
  137. package/dist/scanners/network/index.d.ts +0 -7
  138. package/dist/scanners/network/index.d.ts.map +0 -1
  139. package/dist/scanners/network/index.js +0 -18
  140. package/dist/scanners/network/missing-timeout.d.ts +0 -15
  141. package/dist/scanners/network/missing-timeout.d.ts.map +0 -1
  142. package/dist/scanners/network/missing-timeout.js +0 -93
  143. package/dist/scanners/network/open-redirect.d.ts +0 -15
  144. package/dist/scanners/network/open-redirect.d.ts.map +0 -1
  145. package/dist/scanners/network/open-redirect.js +0 -88
  146. package/dist/scanners/network/ssrf-prone-fetch.d.ts +0 -12
  147. package/dist/scanners/network/ssrf-prone-fetch.d.ts.map +0 -1
  148. package/dist/scanners/network/ssrf-prone-fetch.js +0 -90
  149. package/dist/scanners/nextjs-middleware.d.ts +0 -26
  150. package/dist/scanners/nextjs-middleware.d.ts.map +0 -1
  151. package/dist/scanners/nextjs-middleware.js +0 -246
  152. package/dist/scanners/privacy/debug-flags.d.ts +0 -13
  153. package/dist/scanners/privacy/debug-flags.d.ts.map +0 -1
  154. package/dist/scanners/privacy/debug-flags.js +0 -124
  155. package/dist/scanners/privacy/index.d.ts +0 -6
  156. package/dist/scanners/privacy/index.d.ts.map +0 -1
  157. package/dist/scanners/privacy/index.js +0 -11
  158. package/dist/scanners/privacy/over-broad-response.d.ts +0 -15
  159. package/dist/scanners/privacy/over-broad-response.d.ts.map +0 -1
  160. package/dist/scanners/privacy/over-broad-response.js +0 -109
  161. package/dist/scanners/privacy/sensitive-logging.d.ts +0 -11
  162. package/dist/scanners/privacy/sensitive-logging.d.ts.map +0 -1
  163. package/dist/scanners/privacy/sensitive-logging.js +0 -78
  164. package/dist/scanners/types.d.ts +0 -456
  165. package/dist/scanners/types.d.ts.map +0 -1
  166. package/dist/scanners/types.js +0 -16
  167. package/dist/scanners/unused-security-imports.d.ts +0 -34
  168. package/dist/scanners/unused-security-imports.d.ts.map +0 -1
  169. package/dist/scanners/unused-security-imports.js +0 -206
  170. package/dist/scanners/uploads/index.d.ts +0 -5
  171. package/dist/scanners/uploads/index.d.ts.map +0 -1
  172. package/dist/scanners/uploads/index.js +0 -9
  173. package/dist/scanners/uploads/missing-constraints.d.ts +0 -15
  174. package/dist/scanners/uploads/missing-constraints.d.ts.map +0 -1
  175. package/dist/scanners/uploads/missing-constraints.js +0 -109
  176. package/dist/scanners/uploads/public-path.d.ts +0 -11
  177. package/dist/scanners/uploads/public-path.d.ts.map +0 -1
  178. package/dist/scanners/uploads/public-path.js +0 -87
  179. package/dist/scanners/validation/client-side-only.d.ts +0 -14
  180. package/dist/scanners/validation/client-side-only.d.ts.map +0 -1
  181. package/dist/scanners/validation/client-side-only.js +0 -140
  182. package/dist/scanners/validation/ignored-validation.d.ts +0 -12
  183. package/dist/scanners/validation/ignored-validation.d.ts.map +0 -1
  184. package/dist/scanners/validation/ignored-validation.js +0 -119
  185. package/dist/scanners/validation/index.d.ts +0 -5
  186. package/dist/scanners/validation/index.d.ts.map +0 -1
  187. package/dist/scanners/validation/index.js +0 -9
  188. package/dist/utils/exclude-patterns.d.ts +0 -35
  189. package/dist/utils/exclude-patterns.d.ts.map +0 -1
  190. package/dist/utils/exclude-patterns.js +0 -78
  191. package/dist/utils/file-utils.d.ts +0 -37
  192. package/dist/utils/file-utils.d.ts.map +0 -1
  193. package/dist/utils/file-utils.js +0 -77
  194. package/dist/utils/fingerprint.d.ts +0 -25
  195. package/dist/utils/fingerprint.d.ts.map +0 -1
  196. package/dist/utils/fingerprint.js +0 -28
  197. package/dist/utils/git-info.d.ts +0 -14
  198. package/dist/utils/git-info.d.ts.map +0 -1
  199. package/dist/utils/git-info.js +0 -55
  200. package/dist/utils/index.d.ts +0 -4
  201. package/dist/utils/index.d.ts.map +0 -1
  202. package/dist/utils/index.js +0 -3
  203. package/dist/utils/progress.d.ts +0 -42
  204. package/dist/utils/progress.d.ts.map +0 -1
  205. package/dist/utils/progress.js +0 -165
  206. package/dist/utils/sarif-formatter.d.ts +0 -92
  207. package/dist/utils/sarif-formatter.d.ts.map +0 -1
  208. package/dist/utils/sarif-formatter.js +0 -172
@@ -1,497 +0,0 @@
1
- import path from "node:path";
2
- import { ARTIFACT_VERSION, computeSummary, validateArtifact, } from "@vibecheck/schema";
3
- import { writeFileSync, resolvePath, ensureDir, } from "../utils/file-utils.js";
4
- import { hashPath } from "../utils/fingerprint.js";
5
- import { getGitInfo, getRepoName } from "../utils/git-info.js";
6
- import { ALL_SCANNERS, ALL_SCANNER_PACKS, buildScanContext, severityMeetsThreshold, } from "../scanners/index.js";
7
- import { buildRouteMap, buildMiddlewareMap, buildAllProofTraces, calculateCoverage, mineAllIntentClaims, } from "../phase3/index.js";
8
- import { toSarif, sarifToJson } from "../utils/sarif-formatter.js";
9
- import { ScanProgress, Spinner } from "../utils/progress.js";
10
- const DEFAULT_OUTPUT_DIR = "vibecheck-artifacts";
11
- const DEFAULT_OUTPUT_FILE = "vibecheck-scan.json";
12
- /**
13
- * Normalize path for Windows compatibility
14
- */
15
- function normalizePath(p) {
16
- // Use forward slashes for consistency, but respect platform separators for filesystem ops
17
- return p.replace(/\\/g, "/");
18
- }
19
- /**
20
- * Get output file path with correct extension
21
- */
22
- function getOutputPath(basePath, format, targetDir) {
23
- let outputPath = basePath;
24
- // If path is relative, resolve against target directory
25
- if (!path.isAbsolute(outputPath)) {
26
- outputPath = resolvePath(targetDir, outputPath);
27
- }
28
- // Handle directory vs file path
29
- const ext = path.extname(outputPath);
30
- if (!ext) {
31
- // It's a directory, add default filename
32
- const filename = format === "sarif" ? "vibecheck-scan.sarif" : "vibecheck-scan.json";
33
- outputPath = path.join(outputPath, filename);
34
- }
35
- else if (format === "sarif" && ext === ".json") {
36
- // Replace .json with .sarif for sarif format
37
- outputPath = outputPath.replace(/\.json$/, ".sarif");
38
- }
39
- else if (format === "json" && ext === ".sarif") {
40
- // Replace .sarif with .json for json format
41
- outputPath = outputPath.replace(/\.sarif$/, ".json");
42
- }
43
- return outputPath;
44
- }
45
- /**
46
- * Run all scanners with file-level progress tracking
47
- */
48
- async function runScannersWithProgress(baseContext, totalFiles) {
49
- const allFindings = [];
50
- const seenFingerprints = new Set();
51
- // Initialize progress display with file count
52
- const progress = new ScanProgress(ALL_SCANNER_PACKS.map((pack) => ({
53
- id: pack.id,
54
- name: pack.name,
55
- scannerCount: pack.scanners.length,
56
- })), totalFiles);
57
- // Create file progress callback
58
- const onFileProgress = (file, processed, total) => {
59
- progress.onFileProgress(file, processed, total);
60
- };
61
- // Rebuild context with progress callback
62
- const contextOptions = {
63
- onFileProgress,
64
- };
65
- const context = await buildScanContext(baseContext.repoRoot, {
66
- ...contextOptions,
67
- excludePatterns: [],
68
- includeTests: false,
69
- });
70
- // Copy over the file index from base context (already computed)
71
- Object.assign(context, {
72
- fileIndex: baseContext.fileIndex,
73
- repoMeta: baseContext.repoMeta,
74
- frameworkHints: baseContext.frameworkHints,
75
- prismaSchemaInfo: baseContext.prismaSchemaInfo,
76
- });
77
- progress.start();
78
- // Iterate through packs for progress tracking
79
- for (let packIndex = 0; packIndex < ALL_SCANNER_PACKS.length; packIndex++) {
80
- const pack = ALL_SCANNER_PACKS[packIndex];
81
- progress.startPack(packIndex);
82
- for (let scannerIndex = 0; scannerIndex < pack.scanners.length; scannerIndex++) {
83
- const scanner = pack.scanners[scannerIndex];
84
- progress.startScanner(packIndex, scannerIndex);
85
- try {
86
- const findings = await scanner(context);
87
- let newFindingsCount = 0;
88
- // Dedupe by fingerprint
89
- for (const finding of findings) {
90
- if (!seenFingerprints.has(finding.fingerprint)) {
91
- seenFingerprints.add(finding.fingerprint);
92
- allFindings.push(finding);
93
- newFindingsCount++;
94
- }
95
- }
96
- progress.completeScanner(packIndex, newFindingsCount);
97
- }
98
- catch (error) {
99
- // Log error but continue with other scanners
100
- progress.completeScanner(packIndex, 0);
101
- }
102
- }
103
- progress.completePack(packIndex);
104
- }
105
- progress.stop();
106
- return allFindings;
107
- }
108
- /**
109
- * Create scan artifact from findings
110
- */
111
- function createArtifact(findings, targetDir, fileCount, repoName, metrics, phase3Data) {
112
- const summary = computeSummary(findings);
113
- const gitInfo = getGitInfo(targetDir);
114
- const name = repoName ?? getRepoName(targetDir);
115
- const artifact = {
116
- artifactVersion: ARTIFACT_VERSION,
117
- generatedAt: new Date().toISOString(),
118
- tool: {
119
- name: "vibecheck",
120
- version: "0.0.1",
121
- },
122
- repo: {
123
- name,
124
- rootPathHash: hashPath(targetDir),
125
- git: gitInfo,
126
- },
127
- summary,
128
- findings,
129
- };
130
- if (metrics) {
131
- artifact.metrics = {
132
- filesScanned: metrics.filesScanned,
133
- linesOfCode: 0, // Not implemented yet
134
- scanDurationMs: metrics.scanDurationMs,
135
- rulesExecuted: ALL_SCANNERS.length,
136
- };
137
- }
138
- // Add Phase 3 data if provided
139
- if (phase3Data) {
140
- artifact.routeMap = phase3Data.routeMap;
141
- artifact.middlewareMap = phase3Data.middlewareMap;
142
- artifact.intentMap = phase3Data.intentMap;
143
- artifact.proofTraces = phase3Data.proofTraces;
144
- // Add coverage to metrics
145
- if (artifact.metrics) {
146
- artifact.metrics = {
147
- ...artifact.metrics,
148
- ...phase3Data.coverageMetrics,
149
- };
150
- }
151
- }
152
- return artifact;
153
- }
154
- /**
155
- * Format severity for console output with color
156
- */
157
- function formatSeverity(severity) {
158
- const colors = {
159
- critical: "\x1b[35m", // Magenta
160
- high: "\x1b[31m", // Red
161
- medium: "\x1b[33m", // Yellow
162
- low: "\x1b[36m", // Cyan
163
- info: "\x1b[90m", // Gray
164
- };
165
- const reset = "\x1b[0m";
166
- return `${colors[severity]}${severity.toUpperCase()}${reset}`;
167
- }
168
- /**
169
- * Print scan summary to console
170
- */
171
- function printSummary(artifact, options) {
172
- const { summary, findings } = artifact;
173
- console.log("\n" + "=".repeat(60));
174
- console.log("VibeCheck Scan Complete");
175
- console.log("=".repeat(60));
176
- console.log(`\nScanner packs: ${ALL_SCANNER_PACKS.map((p) => p.name).join(", ")}`);
177
- console.log(`Total scanners: ${ALL_SCANNERS.length}`);
178
- console.log(`Total findings: ${summary.totalFindings}`);
179
- if (options.exclude.length > 0) {
180
- console.log(`Custom excludes: ${options.exclude.length} pattern(s)`);
181
- }
182
- if (options.includeTests) {
183
- console.log("Test files: included");
184
- }
185
- if (summary.totalFindings > 0) {
186
- console.log("\nBy severity:");
187
- if (summary.bySeverity.critical > 0) {
188
- console.log(` ${formatSeverity("critical")}: ${summary.bySeverity.critical}`);
189
- }
190
- if (summary.bySeverity.high > 0) {
191
- console.log(` ${formatSeverity("high")}: ${summary.bySeverity.high}`);
192
- }
193
- if (summary.bySeverity.medium > 0) {
194
- console.log(` ${formatSeverity("medium")}: ${summary.bySeverity.medium}`);
195
- }
196
- if (summary.bySeverity.low > 0) {
197
- console.log(` ${formatSeverity("low")}: ${summary.bySeverity.low}`);
198
- }
199
- if (summary.bySeverity.info > 0) {
200
- console.log(` ${formatSeverity("info")}: ${summary.bySeverity.info}`);
201
- }
202
- console.log("\nTop findings:");
203
- const topFindings = findings
204
- .sort((a, b) => {
205
- const order = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
206
- return order[b.severity] - order[a.severity];
207
- })
208
- .slice(0, 5);
209
- for (const finding of topFindings) {
210
- console.log(` [${formatSeverity(finding.severity)}] ${finding.title}`);
211
- if (finding.evidence[0]) {
212
- console.log(` ${normalizePath(finding.evidence[0].file)}:${finding.evidence[0].startLine}`);
213
- }
214
- }
215
- }
216
- console.log("");
217
- }
218
- /**
219
- * Check if any finding meets the fail threshold
220
- */
221
- function shouldFail(findings, threshold) {
222
- if (threshold === "off") {
223
- return false;
224
- }
225
- return findings.some((f) => severityMeetsThreshold(f.severity, threshold));
226
- }
227
- /**
228
- * Validate format option
229
- */
230
- function validateFormat(format) {
231
- const validFormats = ["json", "sarif", "both"];
232
- if (!validFormats.includes(format)) {
233
- console.error(`\x1b[31mError: Invalid format "${format}". Valid options: ${validFormats.join(", ")}\x1b[0m`);
234
- process.exit(1);
235
- }
236
- return format;
237
- }
238
- /**
239
- * Validate fail-on option
240
- */
241
- function validateFailOn(failOn) {
242
- const validThresholds = ["off", "info", "low", "medium", "high", "critical"];
243
- if (!validThresholds.includes(failOn)) {
244
- console.error(`\x1b[31mError: Invalid fail-on value "${failOn}". Valid options: ${validThresholds.join(", ")}\x1b[0m`);
245
- process.exit(1);
246
- }
247
- return failOn;
248
- }
249
- /**
250
- * Execute the scan command
251
- */
252
- export async function executeScan(targetDir, options) {
253
- const absoluteTarget = resolvePath(targetDir);
254
- // Validate options
255
- const format = validateFormat(options.format);
256
- const failOn = validateFailOn(options.failOn);
257
- // Handle --changed flag
258
- if (options.changed) {
259
- console.warn("\x1b[33mWarning: --changed flag is not implemented yet\x1b[0m");
260
- }
261
- console.log(`\n\x1b[36m\x1b[1m ╭${"─".repeat(88)}╮\x1b[0m`);
262
- console.log(`\x1b[36m │\x1b[0m \x1b[1mVIBECHECK\x1b[0m ${" ".repeat(68)}\x1b[90mv0.0.1\x1b[0m \x1b[36m│\x1b[0m`);
263
- console.log(`\x1b[36m │\x1b[0m \x1b[90m${normalizePath(absoluteTarget).slice(0, 84).padEnd(84)}\x1b[0m \x1b[36m│\x1b[0m`);
264
- console.log(`\x1b[36m ╰${"─".repeat(88)}╯\x1b[0m\n`);
265
- const startTime = Date.now();
266
- // Build scan context with file index and helpers
267
- const contextSpinner = new Spinner("Indexing source files");
268
- contextSpinner.start();
269
- // First build context without progress callback to get file count
270
- const contextOptions = {
271
- excludePatterns: options.exclude,
272
- includeTests: options.includeTests,
273
- };
274
- const initialContext = await buildScanContext(absoluteTarget, contextOptions);
275
- const totalFiles = initialContext.fileIndex.allSourceFiles.length;
276
- contextSpinner.succeed(`Indexed ${totalFiles} source files`);
277
- console.log(`\x1b[90m ├─ API routes: ${initialContext.fileIndex.apiRouteFiles.length}\x1b[0m`);
278
- console.log(`\x1b[90m ├─ Config files: ${initialContext.fileIndex.configFiles.length}\x1b[0m`);
279
- console.log(`\x1b[90m └─ Framework: ${initialContext.repoMeta.framework}\x1b[0m`);
280
- // Run scanners with progress tracking
281
- const findings = await runScannersWithProgress(initialContext, totalFiles);
282
- const endTime = Date.now();
283
- const scanDurationMs = endTime - startTime;
284
- // Build Phase 3 data - enabled by default
285
- let phase3Data;
286
- const shouldEmitPhase3 = options.emitRouteMap || options.emitIntents || options.emitTraces;
287
- if (shouldEmitPhase3) {
288
- const intentSpinner = new Spinner("Building route map and proof traces");
289
- intentSpinner.start();
290
- const routeMapRaw = buildRouteMap(initialContext);
291
- const middlewareMapRaw = buildMiddlewareMap(initialContext);
292
- const intentMapRaw = mineAllIntentClaims(initialContext, routeMapRaw);
293
- const proofTracesRaw = buildAllProofTraces(initialContext, routeMapRaw);
294
- const coverageRaw = calculateCoverage(routeMapRaw, proofTracesRaw, middlewareMapRaw);
295
- // Convert to schema format
296
- const proofTracesRecord = {};
297
- for (const [key, value] of proofTracesRaw) {
298
- proofTracesRecord[key] = {
299
- summary: value.authProven
300
- ? "Auth proven"
301
- : value.middlewareCovered
302
- ? "Protected by middleware"
303
- : "No protection proven",
304
- nodes: value.steps.map((step) => ({
305
- kind: "handler",
306
- label: step.label,
307
- file: step.file,
308
- line: step.line,
309
- })),
310
- };
311
- }
312
- // Build middleware coverage
313
- const allMatchers = middlewareMapRaw.flatMap((m) => m.matchers);
314
- const middlewareCoverage = routeMapRaw.map((route) => {
315
- const isRouteCovered = allMatchers.some((matcher) => {
316
- const pattern = matcher.replace(/\*/g, ".*").replace(/\/:path\*/g, "/.*");
317
- try {
318
- return new RegExp(`^${pattern}`).test(route.path);
319
- }
320
- catch {
321
- return route.path.startsWith(matcher.replace(/\/:path\*$/, ""));
322
- }
323
- });
324
- return {
325
- routeId: route.routeId,
326
- covered: isRouteCovered,
327
- };
328
- });
329
- // Calculate coverage stats
330
- const stateChangingMethods = ["POST", "PUT", "PATCH", "DELETE"];
331
- const stateChangingRoutes = routeMapRaw.filter((r) => stateChangingMethods.includes(r.method));
332
- const protectedRoutes = stateChangingRoutes.filter((r) => {
333
- const trace = proofTracesRaw.get(r.routeId);
334
- return trace?.authProven || trace?.middlewareCovered;
335
- });
336
- const validatedRoutes = routeMapRaw
337
- .filter((r) => ["POST", "PUT", "PATCH"].includes(r.method))
338
- .filter((r) => proofTracesRaw.get(r.routeId)?.validationProven);
339
- const coveredApiRoutes = middlewareCoverage.filter((c) => c.covered);
340
- // Build phase3Data with optional fields based on flags
341
- phase3Data = {
342
- routeMap: options.emitRouteMap ? { routes: routeMapRaw } : { routes: [] },
343
- middlewareMap: options.emitRouteMap ? {
344
- middlewareFile: middlewareMapRaw[0]?.file,
345
- matcher: allMatchers,
346
- coverage: middlewareCoverage,
347
- } : { matcher: [], coverage: [] },
348
- intentMap: options.emitIntents ? { intents: intentMapRaw } : { intents: [] },
349
- proofTraces: options.emitTraces ? proofTracesRecord : {},
350
- coverageMetrics: {
351
- authCoverage: {
352
- totalStateChanging: stateChangingRoutes.length,
353
- protectedCount: protectedRoutes.length,
354
- unprotectedCount: stateChangingRoutes.length - protectedRoutes.length,
355
- },
356
- validationCoverage: {
357
- totalStateChanging: routeMapRaw.filter((r) => ["POST", "PUT", "PATCH"].includes(r.method)).length,
358
- validatedCount: validatedRoutes.length,
359
- },
360
- middlewareCoverage: {
361
- totalApiRoutes: routeMapRaw.length,
362
- coveredApiRoutes: coveredApiRoutes.length,
363
- },
364
- },
365
- };
366
- const parts = [];
367
- if (options.emitRouteMap)
368
- parts.push(`${routeMapRaw.length} routes`);
369
- if (options.emitIntents)
370
- parts.push(`${intentMapRaw.length} intents`);
371
- if (options.emitTraces)
372
- parts.push(`${Object.keys(proofTracesRecord).length} traces`);
373
- intentSpinner.succeed(`Built Phase 3 data (${parts.join(", ")})`);
374
- console.log(`\x1b[90m └─ Auth coverage: ${Math.round(coverageRaw.authCoverage * 100)}%\x1b[0m`);
375
- }
376
- // Create artifact
377
- const artifact = createArtifact(findings, absoluteTarget, initialContext.fileIndex.allSourceFiles.length, options.repoName, {
378
- filesScanned: initialContext.fileIndex.allSourceFiles.length,
379
- scanDurationMs,
380
- }, phase3Data);
381
- // Validate artifact before writing
382
- try {
383
- validateArtifact(artifact);
384
- }
385
- catch (error) {
386
- console.error("\x1b[31mError: Generated artifact failed validation\x1b[0m");
387
- console.error(error);
388
- return 1;
389
- }
390
- // Write output file(s)
391
- const outputFiles = [];
392
- if (format === "json" || format === "both") {
393
- const jsonPath = getOutputPath(options.out, "json", absoluteTarget);
394
- ensureDir(path.dirname(jsonPath));
395
- writeFileSync(jsonPath, JSON.stringify(artifact, null, 2));
396
- outputFiles.push(jsonPath);
397
- }
398
- if (format === "sarif" || format === "both") {
399
- const sarifPath = getOutputPath(options.out, "sarif", absoluteTarget);
400
- ensureDir(path.dirname(sarifPath));
401
- const sarifLog = toSarif(artifact);
402
- writeFileSync(sarifPath, sarifToJson(sarifLog));
403
- outputFiles.push(sarifPath);
404
- }
405
- if (outputFiles.length > 0) {
406
- console.log("\nArtifact(s) written to:");
407
- for (const f of outputFiles) {
408
- console.log(` ${normalizePath(f)}`);
409
- }
410
- }
411
- // Print summary
412
- printSummary(artifact, options);
413
- // Check fail threshold
414
- if (shouldFail(findings, failOn)) {
415
- console.log(`\x1b[31mFailing: Found findings with severity >= ${failOn}\x1b[0m`);
416
- return 1;
417
- }
418
- return 0;
419
- }
420
- /**
421
- * Collect multiple --exclude values into an array
422
- */
423
- function collectExcludes(value, previous) {
424
- return previous.concat([value]);
425
- }
426
- /**
427
- * Register scan command with commander
428
- */
429
- export function registerScanCommand(program) {
430
- program
431
- .command("scan [target]")
432
- .description("Scan a directory for security issues")
433
- .option("-t, --target <path>", "Target directory to scan (alternative to positional argument)")
434
- .option("-o, --out <path>", "Output file or directory path", path.join(DEFAULT_OUTPUT_DIR, DEFAULT_OUTPUT_FILE))
435
- .option("-f, --format <format>", "Output format: json, sarif, or both", "json")
436
- .option("--repo-name <name>", "Override repository name")
437
- .option("--fail-on <threshold>", "Exit with non-zero if findings >= threshold (off, info, low, medium, high, critical)", "high")
438
- .option("-e, --exclude <glob>", "Glob pattern to exclude (repeatable)", collectExcludes, [])
439
- .option("--include-tests", "Include test files in scan (excluded by default)")
440
- .option("--changed", "Only scan changed files (not implemented)")
441
- .option("--emit-route-map", "Include route map in output (default: true)")
442
- .option("--no-emit-route-map", "Exclude route map from output")
443
- .option("--emit-intents", "Include intent claims in output (default: true)")
444
- .option("--no-emit-intents", "Exclude intent claims from output")
445
- .option("--emit-traces", "Include proof traces in output (default: true)")
446
- .option("--no-emit-traces", "Exclude proof traces from output")
447
- .addHelpText("after", `
448
- Quickstart (no install):
449
- $ npx vibecheck scan --fail-on off --out vibecheck-scan.json
450
- $ pnpm dlx vibecheck scan --fail-on off --out vibecheck-scan.json
451
-
452
- Examples:
453
- $ vibecheck scan Scan current directory
454
- $ vibecheck scan ./my-app Scan specific directory
455
- $ vibecheck scan --target ./my-app Same as above (explicit target)
456
- $ vibecheck scan --target ../myapp --out scan.json Scan another folder
457
- $ vibecheck scan --format sarif Output in SARIF format
458
- $ vibecheck scan --format both Output both JSON and SARIF
459
- $ vibecheck scan -o ./reports Custom output directory
460
- $ vibecheck scan --fail-on medium Fail on medium or higher
461
- $ vibecheck scan --fail-on off Never fail based on findings
462
- $ vibecheck scan -e "**/legacy/**" Exclude legacy directory
463
- $ vibecheck scan -e "**/*.old.ts" -e "**/v1/**" Multiple excludes
464
- $ vibecheck scan --include-tests Include test files
465
- $ vibecheck scan --no-emit-intents Skip intent mining
466
- $ vibecheck scan --no-emit-traces Skip proof trace building
467
-
468
- Windows-safe output paths:
469
- $ vibecheck scan --out vibecheck-scan.json Relative path (recommended)
470
- $ vibecheck scan --out ./reports/scan.json Subdirectory path
471
-
472
- Default excludes:
473
- node_modules, dist, .git, build, .next, coverage, .turbo, .cache,
474
- __tests__, *.test.*, *.spec.*, test/, tests/, fixtures/, __mocks__,
475
- cypress/, e2e/, *.stories.*
476
- `)
477
- .action(async (positionalTarget, cmdOptions) => {
478
- // Prefer --target over positional, fall back to cwd
479
- const targetDir = cmdOptions.target
480
- ?? positionalTarget
481
- ?? process.cwd();
482
- const options = {
483
- out: cmdOptions.out,
484
- format: cmdOptions.format,
485
- repoName: cmdOptions.repoName,
486
- failOn: cmdOptions.failOn,
487
- changed: Boolean(cmdOptions.changed),
488
- emitRouteMap: cmdOptions.emitRouteMap !== false, // default true
489
- emitIntents: cmdOptions.emitIntents !== false, // default true
490
- emitTraces: cmdOptions.emitTraces !== false, // default true
491
- exclude: cmdOptions.exclude,
492
- includeTests: Boolean(cmdOptions.includeTests),
493
- };
494
- const exitCode = await executeScan(targetDir, options);
495
- process.exit(exitCode);
496
- });
497
- }
@@ -1,30 +0,0 @@
1
- import type { Command } from "commander";
2
- /**
3
- * Initialize a new waivers file
4
- */
5
- export declare function executeWaiversInit(filepath: string, force: boolean): number;
6
- /**
7
- * Add a waiver to the file
8
- */
9
- export declare function executeWaiversAdd(filepath: string, options: {
10
- fingerprint?: string;
11
- ruleId?: string;
12
- pathPattern?: string;
13
- reason: string;
14
- createdBy: string;
15
- expiresAt?: string;
16
- ticketRef?: string;
17
- }): number;
18
- /**
19
- * List waivers in the file
20
- */
21
- export declare function executeWaiversList(filepath: string, verbose: boolean): number;
22
- /**
23
- * Remove a waiver from the file
24
- */
25
- export declare function executeWaiversRemove(filepath: string, waiverId: string): number;
26
- /**
27
- * Register waivers command with subcommands
28
- */
29
- export declare function registerWaiversCommand(program: Command): void;
30
- //# sourceMappingURL=waivers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"waivers.d.ts","sourceRoot":"","sources":["../../src/commands/waivers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqDzC;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAc3E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,MAAM,CAsER;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CA0C7E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAe/E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkF7D"}