@quantracode/vibecheck 0.0.1

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +839 -0
  3. package/dist/__tests__/cli.test.d.ts +2 -0
  4. package/dist/__tests__/cli.test.d.ts.map +1 -0
  5. package/dist/__tests__/cli.test.js +243 -0
  6. package/dist/__tests__/fixtures/safe-app/app/api/users/route.js +36 -0
  7. package/dist/__tests__/fixtures/vulnerable-app/app/api/users/route.js +28 -0
  8. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts +4 -0
  9. package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/vulnerable-app/lib/config.js +6 -0
  11. package/dist/__tests__/scanners/env-config.test.d.ts +2 -0
  12. package/dist/__tests__/scanners/env-config.test.d.ts.map +1 -0
  13. package/dist/__tests__/scanners/env-config.test.js +142 -0
  14. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts +2 -0
  15. package/dist/__tests__/scanners/nextjs-middleware.test.d.ts.map +1 -0
  16. package/dist/__tests__/scanners/nextjs-middleware.test.js +193 -0
  17. package/dist/__tests__/scanners/scanner-packs.test.d.ts +2 -0
  18. package/dist/__tests__/scanners/scanner-packs.test.d.ts.map +1 -0
  19. package/dist/__tests__/scanners/scanner-packs.test.js +126 -0
  20. package/dist/__tests__/scanners/unused-security-imports.test.d.ts +2 -0
  21. package/dist/__tests__/scanners/unused-security-imports.test.d.ts.map +1 -0
  22. package/dist/__tests__/scanners/unused-security-imports.test.js +145 -0
  23. package/dist/commands/demo-artifact.d.ts +7 -0
  24. package/dist/commands/demo-artifact.d.ts.map +1 -0
  25. package/dist/commands/demo-artifact.js +322 -0
  26. package/dist/commands/evaluate.d.ts +30 -0
  27. package/dist/commands/evaluate.d.ts.map +1 -0
  28. package/dist/commands/evaluate.js +258 -0
  29. package/dist/commands/explain.d.ts +12 -0
  30. package/dist/commands/explain.d.ts.map +1 -0
  31. package/dist/commands/explain.js +214 -0
  32. package/dist/commands/index.d.ts +7 -0
  33. package/dist/commands/index.d.ts.map +1 -0
  34. package/dist/commands/index.js +6 -0
  35. package/dist/commands/intent.d.ts +21 -0
  36. package/dist/commands/intent.d.ts.map +1 -0
  37. package/dist/commands/intent.js +192 -0
  38. package/dist/commands/scan.d.ts +44 -0
  39. package/dist/commands/scan.d.ts.map +1 -0
  40. package/dist/commands/scan.js +497 -0
  41. package/dist/commands/waivers.d.ts +30 -0
  42. package/dist/commands/waivers.d.ts.map +1 -0
  43. package/dist/commands/waivers.js +249 -0
  44. package/dist/index.d.ts +3 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +17 -0
  47. package/dist/phase3/index.d.ts +11 -0
  48. package/dist/phase3/index.d.ts.map +1 -0
  49. package/dist/phase3/index.js +12 -0
  50. package/dist/phase3/intent-miner.d.ts +32 -0
  51. package/dist/phase3/intent-miner.d.ts.map +1 -0
  52. package/dist/phase3/intent-miner.js +323 -0
  53. package/dist/phase3/proof-trace-builder.d.ts +42 -0
  54. package/dist/phase3/proof-trace-builder.d.ts.map +1 -0
  55. package/dist/phase3/proof-trace-builder.js +441 -0
  56. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts +15 -0
  57. package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts.map +1 -0
  58. package/dist/phase3/scanners/auth-by-ui-server-gap.js +237 -0
  59. package/dist/phase3/scanners/comment-claim-unproven.d.ts +14 -0
  60. package/dist/phase3/scanners/comment-claim-unproven.d.ts.map +1 -0
  61. package/dist/phase3/scanners/comment-claim-unproven.js +161 -0
  62. package/dist/phase3/scanners/index.d.ts +31 -0
  63. package/dist/phase3/scanners/index.d.ts.map +1 -0
  64. package/dist/phase3/scanners/index.js +40 -0
  65. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts +14 -0
  66. package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts.map +1 -0
  67. package/dist/phase3/scanners/middleware-assumed-not-matching.js +172 -0
  68. package/dist/phase3/scanners/validation-claimed-missing.d.ts +15 -0
  69. package/dist/phase3/scanners/validation-claimed-missing.d.ts.map +1 -0
  70. package/dist/phase3/scanners/validation-claimed-missing.js +204 -0
  71. package/dist/scanners/abuse/compute-abuse.d.ts +20 -0
  72. package/dist/scanners/abuse/compute-abuse.d.ts.map +1 -0
  73. package/dist/scanners/abuse/compute-abuse.js +509 -0
  74. package/dist/scanners/abuse/index.d.ts +12 -0
  75. package/dist/scanners/abuse/index.d.ts.map +1 -0
  76. package/dist/scanners/abuse/index.js +15 -0
  77. package/dist/scanners/auth/index.d.ts +5 -0
  78. package/dist/scanners/auth/index.d.ts.map +1 -0
  79. package/dist/scanners/auth/index.js +10 -0
  80. package/dist/scanners/auth/middleware-gap.d.ts +22 -0
  81. package/dist/scanners/auth/middleware-gap.d.ts.map +1 -0
  82. package/dist/scanners/auth/middleware-gap.js +203 -0
  83. package/dist/scanners/auth/unprotected-api-route.d.ts +12 -0
  84. package/dist/scanners/auth/unprotected-api-route.d.ts.map +1 -0
  85. package/dist/scanners/auth/unprotected-api-route.js +126 -0
  86. package/dist/scanners/config/index.d.ts +5 -0
  87. package/dist/scanners/config/index.d.ts.map +1 -0
  88. package/dist/scanners/config/index.js +10 -0
  89. package/dist/scanners/config/insecure-defaults.d.ts +12 -0
  90. package/dist/scanners/config/insecure-defaults.d.ts.map +1 -0
  91. package/dist/scanners/config/insecure-defaults.js +77 -0
  92. package/dist/scanners/config/undocumented-env.d.ts +24 -0
  93. package/dist/scanners/config/undocumented-env.d.ts.map +1 -0
  94. package/dist/scanners/config/undocumented-env.js +159 -0
  95. package/dist/scanners/crypto/index.d.ts +6 -0
  96. package/dist/scanners/crypto/index.d.ts.map +1 -0
  97. package/dist/scanners/crypto/index.js +11 -0
  98. package/dist/scanners/crypto/jwt-decode-unverified.d.ts +14 -0
  99. package/dist/scanners/crypto/jwt-decode-unverified.d.ts.map +1 -0
  100. package/dist/scanners/crypto/jwt-decode-unverified.js +87 -0
  101. package/dist/scanners/crypto/math-random-tokens.d.ts +13 -0
  102. package/dist/scanners/crypto/math-random-tokens.d.ts.map +1 -0
  103. package/dist/scanners/crypto/math-random-tokens.js +80 -0
  104. package/dist/scanners/crypto/weak-hashing.d.ts +11 -0
  105. package/dist/scanners/crypto/weak-hashing.d.ts.map +1 -0
  106. package/dist/scanners/crypto/weak-hashing.js +95 -0
  107. package/dist/scanners/env-config.d.ts +24 -0
  108. package/dist/scanners/env-config.d.ts.map +1 -0
  109. package/dist/scanners/env-config.js +164 -0
  110. package/dist/scanners/hallucinations/index.d.ts +4 -0
  111. package/dist/scanners/hallucinations/index.d.ts.map +1 -0
  112. package/dist/scanners/hallucinations/index.js +8 -0
  113. package/dist/scanners/hallucinations/unused-security-imports.d.ts +36 -0
  114. package/dist/scanners/hallucinations/unused-security-imports.d.ts.map +1 -0
  115. package/dist/scanners/hallucinations/unused-security-imports.js +309 -0
  116. package/dist/scanners/helpers/ast-helpers.d.ts +6 -0
  117. package/dist/scanners/helpers/ast-helpers.d.ts.map +1 -0
  118. package/dist/scanners/helpers/ast-helpers.js +945 -0
  119. package/dist/scanners/helpers/context-builder.d.ts +17 -0
  120. package/dist/scanners/helpers/context-builder.d.ts.map +1 -0
  121. package/dist/scanners/helpers/context-builder.js +148 -0
  122. package/dist/scanners/helpers/index.d.ts +3 -0
  123. package/dist/scanners/helpers/index.d.ts.map +1 -0
  124. package/dist/scanners/helpers/index.js +2 -0
  125. package/dist/scanners/index.d.ts +30 -0
  126. package/dist/scanners/index.d.ts.map +1 -0
  127. package/dist/scanners/index.js +102 -0
  128. package/dist/scanners/middleware/index.d.ts +4 -0
  129. package/dist/scanners/middleware/index.d.ts.map +1 -0
  130. package/dist/scanners/middleware/index.js +7 -0
  131. package/dist/scanners/middleware/missing-rate-limit.d.ts +13 -0
  132. package/dist/scanners/middleware/missing-rate-limit.d.ts.map +1 -0
  133. package/dist/scanners/middleware/missing-rate-limit.js +140 -0
  134. package/dist/scanners/network/cors-misconfiguration.d.ts +14 -0
  135. package/dist/scanners/network/cors-misconfiguration.d.ts.map +1 -0
  136. package/dist/scanners/network/cors-misconfiguration.js +89 -0
  137. package/dist/scanners/network/index.d.ts +7 -0
  138. package/dist/scanners/network/index.d.ts.map +1 -0
  139. package/dist/scanners/network/index.js +18 -0
  140. package/dist/scanners/network/missing-timeout.d.ts +15 -0
  141. package/dist/scanners/network/missing-timeout.d.ts.map +1 -0
  142. package/dist/scanners/network/missing-timeout.js +93 -0
  143. package/dist/scanners/network/open-redirect.d.ts +15 -0
  144. package/dist/scanners/network/open-redirect.d.ts.map +1 -0
  145. package/dist/scanners/network/open-redirect.js +88 -0
  146. package/dist/scanners/network/ssrf-prone-fetch.d.ts +12 -0
  147. package/dist/scanners/network/ssrf-prone-fetch.d.ts.map +1 -0
  148. package/dist/scanners/network/ssrf-prone-fetch.js +90 -0
  149. package/dist/scanners/nextjs-middleware.d.ts +26 -0
  150. package/dist/scanners/nextjs-middleware.d.ts.map +1 -0
  151. package/dist/scanners/nextjs-middleware.js +246 -0
  152. package/dist/scanners/privacy/debug-flags.d.ts +13 -0
  153. package/dist/scanners/privacy/debug-flags.d.ts.map +1 -0
  154. package/dist/scanners/privacy/debug-flags.js +124 -0
  155. package/dist/scanners/privacy/index.d.ts +6 -0
  156. package/dist/scanners/privacy/index.d.ts.map +1 -0
  157. package/dist/scanners/privacy/index.js +11 -0
  158. package/dist/scanners/privacy/over-broad-response.d.ts +15 -0
  159. package/dist/scanners/privacy/over-broad-response.d.ts.map +1 -0
  160. package/dist/scanners/privacy/over-broad-response.js +109 -0
  161. package/dist/scanners/privacy/sensitive-logging.d.ts +11 -0
  162. package/dist/scanners/privacy/sensitive-logging.d.ts.map +1 -0
  163. package/dist/scanners/privacy/sensitive-logging.js +78 -0
  164. package/dist/scanners/types.d.ts +456 -0
  165. package/dist/scanners/types.d.ts.map +1 -0
  166. package/dist/scanners/types.js +16 -0
  167. package/dist/scanners/unused-security-imports.d.ts +34 -0
  168. package/dist/scanners/unused-security-imports.d.ts.map +1 -0
  169. package/dist/scanners/unused-security-imports.js +206 -0
  170. package/dist/scanners/uploads/index.d.ts +5 -0
  171. package/dist/scanners/uploads/index.d.ts.map +1 -0
  172. package/dist/scanners/uploads/index.js +9 -0
  173. package/dist/scanners/uploads/missing-constraints.d.ts +15 -0
  174. package/dist/scanners/uploads/missing-constraints.d.ts.map +1 -0
  175. package/dist/scanners/uploads/missing-constraints.js +109 -0
  176. package/dist/scanners/uploads/public-path.d.ts +11 -0
  177. package/dist/scanners/uploads/public-path.d.ts.map +1 -0
  178. package/dist/scanners/uploads/public-path.js +87 -0
  179. package/dist/scanners/validation/client-side-only.d.ts +14 -0
  180. package/dist/scanners/validation/client-side-only.d.ts.map +1 -0
  181. package/dist/scanners/validation/client-side-only.js +140 -0
  182. package/dist/scanners/validation/ignored-validation.d.ts +12 -0
  183. package/dist/scanners/validation/ignored-validation.d.ts.map +1 -0
  184. package/dist/scanners/validation/ignored-validation.js +119 -0
  185. package/dist/scanners/validation/index.d.ts +5 -0
  186. package/dist/scanners/validation/index.d.ts.map +1 -0
  187. package/dist/scanners/validation/index.js +9 -0
  188. package/dist/utils/exclude-patterns.d.ts +35 -0
  189. package/dist/utils/exclude-patterns.d.ts.map +1 -0
  190. package/dist/utils/exclude-patterns.js +78 -0
  191. package/dist/utils/file-utils.d.ts +37 -0
  192. package/dist/utils/file-utils.d.ts.map +1 -0
  193. package/dist/utils/file-utils.js +77 -0
  194. package/dist/utils/fingerprint.d.ts +25 -0
  195. package/dist/utils/fingerprint.d.ts.map +1 -0
  196. package/dist/utils/fingerprint.js +28 -0
  197. package/dist/utils/git-info.d.ts +14 -0
  198. package/dist/utils/git-info.d.ts.map +1 -0
  199. package/dist/utils/git-info.js +55 -0
  200. package/dist/utils/index.d.ts +4 -0
  201. package/dist/utils/index.d.ts.map +1 -0
  202. package/dist/utils/index.js +3 -0
  203. package/dist/utils/progress.d.ts +42 -0
  204. package/dist/utils/progress.d.ts.map +1 -0
  205. package/dist/utils/progress.js +165 -0
  206. package/dist/utils/sarif-formatter.d.ts +92 -0
  207. package/dist/utils/sarif-formatter.d.ts.map +1 -0
  208. package/dist/utils/sarif-formatter.js +172 -0
  209. package/package.json +66 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cli.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,243 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { executeScan } from "../commands/scan.js";
3
+ import { executeExplain } from "../commands/explain.js";
4
+ import { ScanArtifactSchema } from "@vibecheck/schema";
5
+ import path from "node:path";
6
+ import fs from "node:fs";
7
+ import os from "node:os";
8
+ describe("scan command", () => {
9
+ it("produces valid artifact", async () => {
10
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
11
+ const outputPath = path.join(tmpDir, "output", "scan.json");
12
+ // Create a simple test file
13
+ fs.writeFileSync(path.join(tmpDir, "app.ts"), `const url = process.env.DATABASE_URL;`);
14
+ try {
15
+ const options = {
16
+ out: outputPath,
17
+ format: "json",
18
+ failOn: "critical", // Don't fail on high so we can test output
19
+ changed: false,
20
+ };
21
+ // Capture console output
22
+ const originalLog = console.log;
23
+ const logs = [];
24
+ console.log = (...args) => logs.push(args.join(" "));
25
+ await executeScan(tmpDir, options);
26
+ console.log = originalLog;
27
+ // Read and validate output
28
+ const content = fs.readFileSync(outputPath, "utf-8");
29
+ const artifact = JSON.parse(content);
30
+ const result = ScanArtifactSchema.safeParse(artifact);
31
+ expect(result.success).toBe(true);
32
+ if (result.success) {
33
+ expect(result.data.artifactVersion).toBe("0.1");
34
+ expect(result.data.tool.name).toBe("vibecheck");
35
+ expect(result.data.findings.length).toBeGreaterThan(0);
36
+ }
37
+ }
38
+ finally {
39
+ fs.rmSync(tmpDir, { recursive: true });
40
+ }
41
+ });
42
+ it("includes repo info when available", async () => {
43
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
44
+ const outputPath = path.join(tmpDir, "scan.json");
45
+ fs.writeFileSync(path.join(tmpDir, "app.ts"), `const x = 1;`);
46
+ try {
47
+ const options = {
48
+ out: outputPath,
49
+ format: "json",
50
+ repoName: "test-repo",
51
+ failOn: "critical",
52
+ changed: false,
53
+ };
54
+ const originalLog = console.log;
55
+ console.log = () => { };
56
+ await executeScan(tmpDir, options);
57
+ console.log = originalLog;
58
+ const artifact = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
59
+ expect(artifact.repo.name).toBe("test-repo");
60
+ }
61
+ finally {
62
+ fs.rmSync(tmpDir, { recursive: true });
63
+ }
64
+ });
65
+ it("includes metrics in output", async () => {
66
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
67
+ const outputPath = path.join(tmpDir, "scan.json");
68
+ fs.writeFileSync(path.join(tmpDir, "app.ts"), `const x = 1;`);
69
+ try {
70
+ const options = {
71
+ out: outputPath,
72
+ format: "json",
73
+ failOn: "critical",
74
+ changed: false,
75
+ };
76
+ const originalLog = console.log;
77
+ console.log = () => { };
78
+ await executeScan(tmpDir, options);
79
+ console.log = originalLog;
80
+ const artifact = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
81
+ expect(artifact.metrics).toBeDefined();
82
+ expect(artifact.metrics.filesScanned).toBe(1);
83
+ expect(artifact.metrics.scanDurationMs).toBeGreaterThanOrEqual(0);
84
+ }
85
+ finally {
86
+ fs.rmSync(tmpDir, { recursive: true });
87
+ }
88
+ });
89
+ it("exits with non-zero when findings exceed threshold", async () => {
90
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
91
+ const outputPath = path.join(tmpDir, "scan.json");
92
+ // Create file with high severity finding
93
+ fs.writeFileSync(path.join(tmpDir, "app.ts"), `const key = process.env.API_SECRET_KEY;`);
94
+ try {
95
+ const options = {
96
+ out: outputPath,
97
+ format: "json",
98
+ failOn: "high",
99
+ changed: false,
100
+ };
101
+ const originalLog = console.log;
102
+ console.log = () => { };
103
+ const exitCode = await executeScan(tmpDir, options);
104
+ console.log = originalLog;
105
+ expect(exitCode).toBe(1);
106
+ }
107
+ finally {
108
+ fs.rmSync(tmpDir, { recursive: true });
109
+ }
110
+ });
111
+ });
112
+ describe("explain command", () => {
113
+ it("reads and displays artifact", async () => {
114
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
115
+ const artifactPath = path.join(tmpDir, "scan.json");
116
+ const artifact = {
117
+ artifactVersion: "0.1",
118
+ generatedAt: new Date().toISOString(),
119
+ tool: { name: "vibecheck", version: "0.0.1" },
120
+ summary: {
121
+ totalFindings: 1,
122
+ bySeverity: { critical: 0, high: 1, medium: 0, low: 0, info: 0 },
123
+ byCategory: {
124
+ auth: 1,
125
+ validation: 0,
126
+ middleware: 0,
127
+ secrets: 0,
128
+ injection: 0,
129
+ privacy: 0,
130
+ config: 0,
131
+ other: 0,
132
+ },
133
+ },
134
+ findings: [
135
+ {
136
+ id: "test-001",
137
+ severity: "high",
138
+ confidence: 0.9,
139
+ category: "auth",
140
+ ruleId: "VC-AUTH-001",
141
+ title: "Test finding",
142
+ description: "Test description",
143
+ evidence: [
144
+ { file: "test.ts", startLine: 1, endLine: 1, label: "Test label" },
145
+ ],
146
+ remediation: { recommendedFix: "Fix it" },
147
+ fingerprint: "abc123",
148
+ },
149
+ ],
150
+ };
151
+ fs.writeFileSync(artifactPath, JSON.stringify(artifact));
152
+ try {
153
+ const originalLog = console.log;
154
+ const logs = [];
155
+ console.log = (...args) => logs.push(args.join(" "));
156
+ const exitCode = await executeExplain(artifactPath, { limit: 5 });
157
+ console.log = originalLog;
158
+ expect(exitCode).toBe(0);
159
+ expect(logs.some((l) => l.includes("VIBECHECK SCAN REPORT"))).toBe(true);
160
+ expect(logs.some((l) => l.includes("Test finding"))).toBe(true);
161
+ }
162
+ finally {
163
+ fs.rmSync(tmpDir, { recursive: true });
164
+ }
165
+ });
166
+ it("returns error for invalid artifact", async () => {
167
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
168
+ const artifactPath = path.join(tmpDir, "invalid.json");
169
+ fs.writeFileSync(artifactPath, JSON.stringify({ invalid: true }));
170
+ try {
171
+ const originalLog = console.log;
172
+ const originalError = console.error;
173
+ console.log = () => { };
174
+ console.error = () => { };
175
+ const exitCode = await executeExplain(artifactPath, { limit: 5 });
176
+ console.log = originalLog;
177
+ console.error = originalError;
178
+ expect(exitCode).toBe(1);
179
+ }
180
+ finally {
181
+ fs.rmSync(tmpDir, { recursive: true });
182
+ }
183
+ });
184
+ });
185
+ describe("artifact output shape", () => {
186
+ it("has correct structure", async () => {
187
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
188
+ const outputPath = path.join(tmpDir, "scan.json");
189
+ fs.writeFileSync(path.join(tmpDir, "app.ts"), `import { z } from "zod";
190
+ // unused
191
+ `);
192
+ try {
193
+ const options = {
194
+ out: outputPath,
195
+ format: "json",
196
+ failOn: "critical",
197
+ changed: false,
198
+ };
199
+ const originalLog = console.log;
200
+ console.log = () => { };
201
+ await executeScan(tmpDir, options);
202
+ console.log = originalLog;
203
+ const artifact = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
204
+ // Check top-level structure
205
+ expect(artifact).toHaveProperty("artifactVersion", "0.1");
206
+ expect(artifact).toHaveProperty("generatedAt");
207
+ expect(artifact).toHaveProperty("tool");
208
+ expect(artifact).toHaveProperty("summary");
209
+ expect(artifact).toHaveProperty("findings");
210
+ // Check tool info
211
+ expect(artifact.tool).toHaveProperty("name", "vibecheck");
212
+ expect(artifact.tool).toHaveProperty("version");
213
+ // Check summary structure
214
+ expect(artifact.summary).toHaveProperty("totalFindings");
215
+ expect(artifact.summary).toHaveProperty("bySeverity");
216
+ expect(artifact.summary).toHaveProperty("byCategory");
217
+ // Check finding structure (if any)
218
+ if (artifact.findings.length > 0) {
219
+ const finding = artifact.findings[0];
220
+ expect(finding).toHaveProperty("id");
221
+ expect(finding).toHaveProperty("severity");
222
+ expect(finding).toHaveProperty("confidence");
223
+ expect(finding).toHaveProperty("category");
224
+ expect(finding).toHaveProperty("ruleId");
225
+ expect(finding).toHaveProperty("title");
226
+ expect(finding).toHaveProperty("description");
227
+ expect(finding).toHaveProperty("evidence");
228
+ expect(finding).toHaveProperty("remediation");
229
+ expect(finding).toHaveProperty("fingerprint");
230
+ // Check ruleId format
231
+ expect(finding.ruleId).toMatch(/^VC-[A-Z]+-\d{3}$/);
232
+ // Check evidence structure
233
+ expect(finding.evidence[0]).toHaveProperty("file");
234
+ expect(finding.evidence[0]).toHaveProperty("startLine");
235
+ expect(finding.evidence[0]).toHaveProperty("endLine");
236
+ expect(finding.evidence[0]).toHaveProperty("label");
237
+ }
238
+ }
239
+ finally {
240
+ fs.rmSync(tmpDir, { recursive: true });
241
+ }
242
+ });
243
+ });
@@ -0,0 +1,36 @@
1
+ import { prisma } from "@/lib/prisma";
2
+ import { getServerSession } from "next-auth";
3
+ import { z } from "zod";
4
+ const userSchema = z.object({
5
+ name: z.string(),
6
+ email: z.string().email(),
7
+ });
8
+ // Safe: Has auth check
9
+ export async function POST(request) {
10
+ const session = await getServerSession();
11
+ if (!session) {
12
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
13
+ }
14
+ const body = await request.json();
15
+ // Safe: Validation result is used
16
+ const validatedData = userSchema.parse(body);
17
+ const user = await prisma.user.create({
18
+ data: validatedData,
19
+ });
20
+ // Safe: Not logging sensitive data
21
+ console.log("Created user:", { id: user.id, timestamp: Date.now() });
22
+ return Response.json(user);
23
+ }
24
+ // Safe: Has auth check
25
+ export async function DELETE(request) {
26
+ const session = await getServerSession();
27
+ if (!session) {
28
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
29
+ }
30
+ const { searchParams } = new URL(request.url);
31
+ const id = searchParams.get("id");
32
+ await prisma.user.delete({
33
+ where: { id },
34
+ });
35
+ return Response.json({ success: true });
36
+ }
@@ -0,0 +1,28 @@
1
+ import { prisma } from "@/lib/prisma";
2
+ import { z } from "zod";
3
+ const userSchema = z.object({
4
+ name: z.string(),
5
+ email: z.string().email(),
6
+ });
7
+ // VC-AUTH-001: No auth check before database write
8
+ export async function POST(request) {
9
+ const body = await request.json();
10
+ // VC-VAL-001: Validation called but result ignored
11
+ userSchema.parse(body);
12
+ // Using raw body instead of validated data
13
+ const user = await prisma.user.create({
14
+ data: body,
15
+ });
16
+ // VC-PRIV-001: Logging sensitive data
17
+ console.log("Created user:", { email: body.email, password: body.password });
18
+ return Response.json(user);
19
+ }
20
+ // VC-AUTH-001: Critical - delete without auth
21
+ export async function DELETE(request) {
22
+ const { searchParams } = new URL(request.url);
23
+ const id = searchParams.get("id");
24
+ await prisma.user.delete({
25
+ where: { id },
26
+ });
27
+ return Response.json({ success: true });
28
+ }
@@ -0,0 +1,4 @@
1
+ export declare const JWT_SECRET: string;
2
+ export declare const SESSION_SECRET: string;
3
+ export declare const API_URL: string;
4
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../src/__tests__/fixtures/vulnerable-app/lib/config.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,UAAU,QAA6C,CAAC;AAGrE,eAAO,MAAM,cAAc,QAA8C,CAAC;AAG1E,eAAO,MAAM,OAAO,QAAiD,CAAC"}
@@ -0,0 +1,6 @@
1
+ // VC-CONFIG-002: Insecure default for critical secret
2
+ export const JWT_SECRET = process.env.JWT_SECRET || "dev-secret-123";
3
+ // VC-CONFIG-002: Another insecure default
4
+ export const SESSION_SECRET = process.env.SESSION_SECRET ?? "session-dev";
5
+ // Safe - not a secret
6
+ export const API_URL = process.env.API_URL || "http://localhost:3000";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=env-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-config.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/scanners/env-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseEnvExample, findEnvUsages, scanEnvConfig, } from "../../scanners/env-config.js";
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ describe("parseEnvExample", () => {
7
+ it("parses simple env file", () => {
8
+ const content = `
9
+ DATABASE_URL=
10
+ API_KEY=your-key-here
11
+ SECRET_TOKEN=
12
+ `;
13
+ const vars = parseEnvExample(content);
14
+ expect(vars.has("DATABASE_URL")).toBe(true);
15
+ expect(vars.has("API_KEY")).toBe(true);
16
+ expect(vars.has("SECRET_TOKEN")).toBe(true);
17
+ });
18
+ it("ignores comments", () => {
19
+ const content = `
20
+ # This is a comment
21
+ DATABASE_URL=
22
+ # API_KEY=should-be-ignored
23
+ `;
24
+ const vars = parseEnvExample(content);
25
+ expect(vars.has("DATABASE_URL")).toBe(true);
26
+ expect(vars.has("API_KEY")).toBe(false);
27
+ });
28
+ it("handles empty lines", () => {
29
+ const content = `
30
+
31
+ DATABASE_URL=
32
+
33
+ API_KEY=
34
+
35
+ `;
36
+ const vars = parseEnvExample(content);
37
+ expect(vars.size).toBe(2);
38
+ });
39
+ it("handles vars without values", () => {
40
+ const content = `DATABASE_URL`;
41
+ const vars = parseEnvExample(content);
42
+ expect(vars.has("DATABASE_URL")).toBe(true);
43
+ });
44
+ });
45
+ describe("findEnvUsages", () => {
46
+ it("finds process.env.VAR usage", () => {
47
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
48
+ const filePath = path.join(tmpDir, "test.ts");
49
+ fs.writeFileSync(filePath, `
50
+ const url = process.env.DATABASE_URL;
51
+ const key = process.env.API_KEY;
52
+ `);
53
+ try {
54
+ const usages = findEnvUsages(["test.ts"], tmpDir);
55
+ expect(usages).toHaveLength(2);
56
+ expect(usages[0].name).toBe("DATABASE_URL");
57
+ expect(usages[1].name).toBe("API_KEY");
58
+ }
59
+ finally {
60
+ fs.rmSync(tmpDir, { recursive: true });
61
+ }
62
+ });
63
+ it("finds bracket notation usage", () => {
64
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
65
+ const filePath = path.join(tmpDir, "test.ts");
66
+ fs.writeFileSync(filePath, `const url = process.env["DATABASE_URL"];`);
67
+ try {
68
+ const usages = findEnvUsages(["test.ts"], tmpDir);
69
+ expect(usages).toHaveLength(1);
70
+ expect(usages[0].name).toBe("DATABASE_URL");
71
+ }
72
+ finally {
73
+ fs.rmSync(tmpDir, { recursive: true });
74
+ }
75
+ });
76
+ it("returns correct line numbers", () => {
77
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
78
+ const filePath = path.join(tmpDir, "test.ts");
79
+ fs.writeFileSync(filePath, `// line 1
80
+ // line 2
81
+ const x = process.env.MY_VAR; // line 3
82
+ `);
83
+ try {
84
+ const usages = findEnvUsages(["test.ts"], tmpDir);
85
+ expect(usages[0].line).toBe(3);
86
+ }
87
+ finally {
88
+ fs.rmSync(tmpDir, { recursive: true });
89
+ }
90
+ });
91
+ });
92
+ describe("scanEnvConfig", () => {
93
+ it("creates findings for undocumented env vars", async () => {
94
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
95
+ const filePath = path.join(tmpDir, "app.ts");
96
+ fs.writeFileSync(filePath, `const url = process.env.DATABASE_URL;`);
97
+ try {
98
+ const context = {
99
+ targetDir: tmpDir,
100
+ sourceFiles: ["app.ts"],
101
+ };
102
+ const findings = await scanEnvConfig(context);
103
+ expect(findings).toHaveLength(1);
104
+ expect(findings[0].ruleId).toBe("VC-CONFIG-001");
105
+ expect(findings[0].title).toContain("DATABASE_URL");
106
+ }
107
+ finally {
108
+ fs.rmSync(tmpDir, { recursive: true });
109
+ }
110
+ });
111
+ it("does not create findings for documented vars", async () => {
112
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
113
+ fs.writeFileSync(path.join(tmpDir, "app.ts"), `const url = process.env.DATABASE_URL;`);
114
+ fs.writeFileSync(path.join(tmpDir, ".env.example"), "DATABASE_URL=");
115
+ try {
116
+ const context = {
117
+ targetDir: tmpDir,
118
+ sourceFiles: ["app.ts"],
119
+ };
120
+ const findings = await scanEnvConfig(context);
121
+ expect(findings).toHaveLength(0);
122
+ }
123
+ finally {
124
+ fs.rmSync(tmpDir, { recursive: true });
125
+ }
126
+ });
127
+ it("marks SECRET-like vars as high severity", async () => {
128
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
129
+ fs.writeFileSync(path.join(tmpDir, "app.ts"), `const key = process.env.API_SECRET_KEY;`);
130
+ try {
131
+ const context = {
132
+ targetDir: tmpDir,
133
+ sourceFiles: ["app.ts"],
134
+ };
135
+ const findings = await scanEnvConfig(context);
136
+ expect(findings[0].severity).toBe("high");
137
+ }
138
+ finally {
139
+ fs.rmSync(tmpDir, { recursive: true });
140
+ }
141
+ });
142
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nextjs-middleware.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs-middleware.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/scanners/nextjs-middleware.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseMatcherConfig, matcherCoversApi, scanNextjsMiddleware, } from "../../scanners/nextjs-middleware.js";
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ describe("parseMatcherConfig", () => {
7
+ it("parses single string matcher", () => {
8
+ const content = `
9
+ export const config = {
10
+ matcher: '/dashboard/:path*'
11
+ };
12
+ `;
13
+ const matchers = parseMatcherConfig(content);
14
+ expect(matchers).toEqual(["/dashboard/:path*"]);
15
+ });
16
+ it("parses array matcher", () => {
17
+ const content = `
18
+ export const config = {
19
+ matcher: ['/dashboard/:path*', '/admin/:path*']
20
+ };
21
+ `;
22
+ const matchers = parseMatcherConfig(content);
23
+ expect(matchers).toEqual(["/dashboard/:path*", "/admin/:path*"]);
24
+ });
25
+ it("returns null when no config export", () => {
26
+ const content = `
27
+ export function middleware(request) {
28
+ return NextResponse.next();
29
+ }
30
+ `;
31
+ const matchers = parseMatcherConfig(content);
32
+ expect(matchers).toBeNull();
33
+ });
34
+ it("parses complex negation pattern", () => {
35
+ const content = `
36
+ export const config = {
37
+ matcher: '/((?!_next/static|_next/image|favicon.ico).*)'
38
+ };
39
+ `;
40
+ const matchers = parseMatcherConfig(content);
41
+ expect(matchers).toEqual(["/((?!_next/static|_next/image|favicon.ico).*)"]);
42
+ });
43
+ });
44
+ describe("matcherCoversApi", () => {
45
+ it("returns true for direct /api match", () => {
46
+ expect(matcherCoversApi(["/api"])).toBe(true);
47
+ expect(matcherCoversApi(["/api/:path*"])).toBe(true);
48
+ });
49
+ it("returns true for patterns starting with /api", () => {
50
+ expect(matcherCoversApi(["/api/users/:path*"])).toBe(true);
51
+ });
52
+ it("returns true for catch-all patterns", () => {
53
+ expect(matcherCoversApi(["/:path*"])).toBe(true);
54
+ });
55
+ it("returns true for negation patterns not excluding api", () => {
56
+ expect(matcherCoversApi(["/((?!_next/static|_next/image|favicon.ico).*)"])).toBe(true);
57
+ });
58
+ it("returns false for dashboard-only patterns", () => {
59
+ expect(matcherCoversApi(["/dashboard/:path*"])).toBe(false);
60
+ });
61
+ it("returns false for unrelated patterns", () => {
62
+ expect(matcherCoversApi(["/admin", "/profile"])).toBe(false);
63
+ });
64
+ });
65
+ describe("scanNextjsMiddleware", () => {
66
+ function createNextProject(tmpDir, options = {}) {
67
+ // Create package.json
68
+ const pkg = {
69
+ name: "test-app",
70
+ dependencies: {
71
+ next: "14.0.0",
72
+ ...(options.hasNextAuth && { "next-auth": "4.0.0" }),
73
+ },
74
+ };
75
+ fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify(pkg));
76
+ // Create middleware if specified
77
+ if (options.hasMiddleware && options.middlewareContent) {
78
+ fs.writeFileSync(path.join(tmpDir, "middleware.ts"), options.middlewareContent);
79
+ }
80
+ // Create API routes if specified
81
+ if (options.hasApiRoutes) {
82
+ const apiDir = path.join(tmpDir, "app", "api", "users");
83
+ fs.mkdirSync(apiDir, { recursive: true });
84
+ fs.writeFileSync(path.join(apiDir, "route.ts"), `export async function GET() { return Response.json({}); }`);
85
+ }
86
+ }
87
+ it("returns no findings for non-Next.js project", async () => {
88
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
89
+ try {
90
+ fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ name: "test" }));
91
+ const context = {
92
+ targetDir: tmpDir,
93
+ sourceFiles: [],
94
+ };
95
+ const findings = await scanNextjsMiddleware(context);
96
+ expect(findings).toHaveLength(0);
97
+ }
98
+ finally {
99
+ fs.rmSync(tmpDir, { recursive: true });
100
+ }
101
+ });
102
+ it("returns no findings when no API routes exist", async () => {
103
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
104
+ try {
105
+ createNextProject(tmpDir, { hasApiRoutes: false });
106
+ const context = {
107
+ targetDir: tmpDir,
108
+ sourceFiles: [],
109
+ };
110
+ const findings = await scanNextjsMiddleware(context);
111
+ expect(findings).toHaveLength(0);
112
+ }
113
+ finally {
114
+ fs.rmSync(tmpDir, { recursive: true });
115
+ }
116
+ });
117
+ it("finds missing middleware with next-auth", async () => {
118
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
119
+ try {
120
+ createNextProject(tmpDir, {
121
+ hasNextAuth: true,
122
+ hasApiRoutes: true,
123
+ });
124
+ const context = {
125
+ targetDir: tmpDir,
126
+ sourceFiles: [],
127
+ };
128
+ const findings = await scanNextjsMiddleware(context);
129
+ expect(findings).toHaveLength(1);
130
+ expect(findings[0].ruleId).toBe("VC-AUTH-INFO-001");
131
+ expect(findings[0].severity).toBe("medium");
132
+ }
133
+ finally {
134
+ fs.rmSync(tmpDir, { recursive: true });
135
+ }
136
+ });
137
+ it("finds middleware not covering API routes", async () => {
138
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
139
+ try {
140
+ createNextProject(tmpDir, {
141
+ hasMiddleware: true,
142
+ middlewareContent: `
143
+ export function middleware(request) {
144
+ return NextResponse.next();
145
+ }
146
+
147
+ export const config = {
148
+ matcher: '/dashboard/:path*'
149
+ };
150
+ `,
151
+ hasApiRoutes: true,
152
+ });
153
+ const context = {
154
+ targetDir: tmpDir,
155
+ sourceFiles: [],
156
+ };
157
+ const findings = await scanNextjsMiddleware(context);
158
+ expect(findings).toHaveLength(1);
159
+ expect(findings[0].ruleId).toBe("VC-MW-001");
160
+ expect(findings[0].severity).toBe("high");
161
+ }
162
+ finally {
163
+ fs.rmSync(tmpDir, { recursive: true });
164
+ }
165
+ });
166
+ it("returns no findings when middleware covers API", async () => {
167
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
168
+ try {
169
+ createNextProject(tmpDir, {
170
+ hasMiddleware: true,
171
+ middlewareContent: `
172
+ export function middleware(request) {
173
+ return NextResponse.next();
174
+ }
175
+
176
+ export const config = {
177
+ matcher: ['/dashboard/:path*', '/api/:path*']
178
+ };
179
+ `,
180
+ hasApiRoutes: true,
181
+ });
182
+ const context = {
183
+ targetDir: tmpDir,
184
+ sourceFiles: [],
185
+ };
186
+ const findings = await scanNextjsMiddleware(context);
187
+ expect(findings).toHaveLength(0);
188
+ }
189
+ finally {
190
+ fs.rmSync(tmpDir, { recursive: true });
191
+ }
192
+ });
193
+ });