@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,249 @@
1
+ import path from "node:path";
2
+ import { readFileSync, fileExists, resolvePath, writeFileSync, ensureDir, } from "../utils/file-utils.js";
3
+ import { createEmptyWaiversFile, createWaiver, addWaiver, removeWaiver, WaiversFileSchema, } from "@vibecheck/policy";
4
+ const DEFAULT_WAIVERS_FILE = "vibecheck-waivers.json";
5
+ /**
6
+ * Load waivers file from path
7
+ */
8
+ function loadWaiversFile(filepath) {
9
+ const absolutePath = resolvePath(filepath);
10
+ if (!fileExists(absolutePath)) {
11
+ throw new Error(`Waivers file not found: ${filepath}`);
12
+ }
13
+ const content = readFileSync(absolutePath);
14
+ if (!content) {
15
+ throw new Error(`Failed to read waivers file: ${filepath}`);
16
+ }
17
+ const data = JSON.parse(content);
18
+ const result = WaiversFileSchema.safeParse(data);
19
+ if (!result.success) {
20
+ throw new Error(`Invalid waivers file: ${result.error.message}`);
21
+ }
22
+ return result.data;
23
+ }
24
+ /**
25
+ * Save waivers file to path
26
+ */
27
+ function saveWaiversFile(filepath, file) {
28
+ const absolutePath = resolvePath(filepath);
29
+ ensureDir(path.dirname(absolutePath));
30
+ writeFileSync(absolutePath, JSON.stringify(file, null, 2));
31
+ }
32
+ /**
33
+ * Initialize a new waivers file
34
+ */
35
+ export function executeWaiversInit(filepath, force) {
36
+ const absolutePath = resolvePath(filepath);
37
+ if (fileExists(absolutePath) && !force) {
38
+ console.error(`\x1b[31mError: Waivers file already exists: ${absolutePath}\x1b[0m`);
39
+ console.error("Use --force to overwrite");
40
+ return 1;
41
+ }
42
+ const emptyFile = createEmptyWaiversFile();
43
+ saveWaiversFile(filepath, emptyFile);
44
+ console.log(`Created waivers file: ${absolutePath}`);
45
+ return 0;
46
+ }
47
+ /**
48
+ * Add a waiver to the file
49
+ */
50
+ export function executeWaiversAdd(filepath, options) {
51
+ // Validate options
52
+ if (!options.fingerprint && !options.ruleId) {
53
+ console.error("\x1b[31mError: Must specify either --fingerprint or --ruleId\x1b[0m");
54
+ return 1;
55
+ }
56
+ if (options.fingerprint && options.ruleId) {
57
+ console.error("\x1b[31mError: Cannot specify both --fingerprint and --ruleId\x1b[0m");
58
+ return 1;
59
+ }
60
+ // Validate expiry date if provided
61
+ if (options.expiresAt) {
62
+ const date = new Date(options.expiresAt);
63
+ if (isNaN(date.getTime())) {
64
+ console.error(`\x1b[31mError: Invalid expiry date: ${options.expiresAt}\x1b[0m`);
65
+ return 1;
66
+ }
67
+ if (date <= new Date()) {
68
+ console.error("\x1b[31mError: Expiry date must be in the future\x1b[0m");
69
+ return 1;
70
+ }
71
+ }
72
+ // Load or create waivers file
73
+ let file;
74
+ const absolutePath = resolvePath(filepath);
75
+ if (fileExists(absolutePath)) {
76
+ file = loadWaiversFile(filepath);
77
+ }
78
+ else {
79
+ console.log("Creating new waivers file...");
80
+ file = createEmptyWaiversFile();
81
+ }
82
+ // Create waiver
83
+ const waiver = createWaiver({
84
+ fingerprint: options.fingerprint,
85
+ ruleId: options.ruleId,
86
+ pathPattern: options.pathPattern,
87
+ reason: options.reason,
88
+ createdBy: options.createdBy,
89
+ expiresAt: options.expiresAt ? new Date(options.expiresAt).toISOString() : undefined,
90
+ ticketRef: options.ticketRef,
91
+ });
92
+ // Add to file
93
+ const updated = addWaiver(file, waiver);
94
+ saveWaiversFile(filepath, updated);
95
+ console.log(`Added waiver: ${waiver.id}`);
96
+ if (options.fingerprint) {
97
+ console.log(` Fingerprint: ${options.fingerprint}`);
98
+ }
99
+ else {
100
+ console.log(` Rule ID: ${options.ruleId}`);
101
+ if (options.pathPattern) {
102
+ console.log(` Path pattern: ${options.pathPattern}`);
103
+ }
104
+ }
105
+ console.log(` Reason: ${options.reason}`);
106
+ console.log(` Created by: ${options.createdBy}`);
107
+ if (options.expiresAt) {
108
+ console.log(` Expires: ${options.expiresAt}`);
109
+ }
110
+ if (options.ticketRef) {
111
+ console.log(` Ticket: ${options.ticketRef}`);
112
+ }
113
+ return 0;
114
+ }
115
+ /**
116
+ * List waivers in the file
117
+ */
118
+ export function executeWaiversList(filepath, verbose) {
119
+ const file = loadWaiversFile(filepath);
120
+ if (file.waivers.length === 0) {
121
+ console.log("No waivers in file");
122
+ return 0;
123
+ }
124
+ console.log(`Found ${file.waivers.length} waiver(s):\n`);
125
+ for (const waiver of file.waivers) {
126
+ const isExpired = waiver.expiresAt && new Date(waiver.expiresAt) < new Date();
127
+ const expiredNote = isExpired ? " \x1b[31m[EXPIRED]\x1b[0m" : "";
128
+ console.log(`ID: ${waiver.id}${expiredNote}`);
129
+ if (waiver.match.fingerprint) {
130
+ console.log(` Match: fingerprint = ${waiver.match.fingerprint}`);
131
+ }
132
+ else {
133
+ console.log(` Match: ruleId = ${waiver.match.ruleId}`);
134
+ if (waiver.match.pathPattern) {
135
+ console.log(` pathPattern = ${waiver.match.pathPattern}`);
136
+ }
137
+ }
138
+ console.log(` Reason: ${waiver.reason}`);
139
+ if (verbose) {
140
+ console.log(` Created by: ${waiver.createdBy}`);
141
+ console.log(` Created at: ${waiver.createdAt}`);
142
+ if (waiver.expiresAt) {
143
+ console.log(` Expires at: ${waiver.expiresAt}`);
144
+ }
145
+ if (waiver.ticketRef) {
146
+ console.log(` Ticket: ${waiver.ticketRef}`);
147
+ }
148
+ }
149
+ console.log("");
150
+ }
151
+ return 0;
152
+ }
153
+ /**
154
+ * Remove a waiver from the file
155
+ */
156
+ export function executeWaiversRemove(filepath, waiverId) {
157
+ const file = loadWaiversFile(filepath);
158
+ // Check if waiver exists
159
+ const exists = file.waivers.some((w) => w.id === waiverId);
160
+ if (!exists) {
161
+ console.error(`\x1b[31mError: Waiver not found: ${waiverId}\x1b[0m`);
162
+ return 1;
163
+ }
164
+ const updated = removeWaiver(file, waiverId);
165
+ saveWaiversFile(filepath, updated);
166
+ console.log(`Removed waiver: ${waiverId}`);
167
+ return 0;
168
+ }
169
+ /**
170
+ * Register waivers command with subcommands
171
+ */
172
+ export function registerWaiversCommand(program) {
173
+ const waiversCmd = program
174
+ .command("waivers")
175
+ .description("Manage policy waivers");
176
+ // waivers init
177
+ waiversCmd
178
+ .command("init [path]")
179
+ .description("Initialize a new waivers file")
180
+ .option("-f, --force", "Overwrite existing file")
181
+ .action((pathArg, cmdOptions) => {
182
+ const filepath = pathArg ?? DEFAULT_WAIVERS_FILE;
183
+ const exitCode = executeWaiversInit(filepath, Boolean(cmdOptions.force));
184
+ process.exit(exitCode);
185
+ });
186
+ // waivers add
187
+ waiversCmd
188
+ .command("add [path]")
189
+ .description("Add a waiver to the file")
190
+ .option("--fingerprint <sha256>", "Match by finding fingerprint")
191
+ .option("--rule-id <id>", "Match by rule ID (supports wildcards like VC-AUTH-*)")
192
+ .option("--path-pattern <glob>", "Additional path pattern for rule ID match")
193
+ .requiredOption("-r, --reason <text>", "Justification for the waiver")
194
+ .requiredOption("--created-by <email>", "Who is creating this waiver")
195
+ .option("--expires <date>", "Expiration date (ISO format)")
196
+ .option("--ticket <ref>", "Reference to ticket/issue")
197
+ .addHelpText("after", `
198
+ Examples:
199
+ $ vibecheck waivers add --fingerprint sha256:abc123 -r "False positive" --created-by dev@example.com
200
+ $ vibecheck waivers add --rule-id VC-AUTH-001 -r "Accepted risk" --created-by dev@example.com
201
+ $ vibecheck waivers add --rule-id "VC-VAL-*" --path-pattern "src/legacy/**" -r "Legacy code" --created-by dev@example.com
202
+ $ vibecheck waivers add --fingerprint sha256:xyz --expires 2025-12-31 -r "Temporary" --created-by dev@example.com
203
+ $ vibecheck waivers add ./custom-waivers.json --fingerprint sha256:abc -r "Custom file" --created-by dev@example.com
204
+ `)
205
+ .action((pathArg, cmdOptions) => {
206
+ const filepath = pathArg ?? DEFAULT_WAIVERS_FILE;
207
+ const exitCode = executeWaiversAdd(filepath, {
208
+ fingerprint: cmdOptions.fingerprint,
209
+ ruleId: cmdOptions.ruleId,
210
+ pathPattern: cmdOptions.pathPattern,
211
+ reason: cmdOptions.reason,
212
+ createdBy: cmdOptions.createdBy,
213
+ expiresAt: cmdOptions.expires,
214
+ ticketRef: cmdOptions.ticket,
215
+ });
216
+ process.exit(exitCode);
217
+ });
218
+ // waivers list
219
+ waiversCmd
220
+ .command("list [path]")
221
+ .description("List waivers in the file")
222
+ .option("-v, --verbose", "Show all waiver details")
223
+ .action((pathArg, cmdOptions) => {
224
+ const filepath = pathArg ?? DEFAULT_WAIVERS_FILE;
225
+ try {
226
+ const exitCode = executeWaiversList(filepath, Boolean(cmdOptions.verbose));
227
+ process.exit(exitCode);
228
+ }
229
+ catch (error) {
230
+ console.error(`\x1b[31mError: ${error instanceof Error ? error.message : String(error)}\x1b[0m`);
231
+ process.exit(1);
232
+ }
233
+ });
234
+ // waivers remove
235
+ waiversCmd
236
+ .command("remove <waiverId> [path]")
237
+ .description("Remove a waiver from the file")
238
+ .action((waiverId, pathArg) => {
239
+ const filepath = pathArg ?? DEFAULT_WAIVERS_FILE;
240
+ try {
241
+ const exitCode = executeWaiversRemove(filepath, waiverId);
242
+ process.exit(exitCode);
243
+ }
244
+ catch (error) {
245
+ console.error(`\x1b[31mError: ${error instanceof Error ? error.message : String(error)}\x1b[0m`);
246
+ process.exit(1);
247
+ }
248
+ });
249
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { registerScanCommand, registerExplainCommand, registerDemoArtifactCommand, registerIntentCommand, registerEvaluateCommand, registerWaiversCommand, } from "./commands/index.js";
4
+ const program = new Command();
5
+ program
6
+ .name("vibecheck")
7
+ .description("Security scanner for modern web applications")
8
+ .version("0.0.1");
9
+ // Register commands
10
+ registerScanCommand(program);
11
+ registerExplainCommand(program);
12
+ registerDemoArtifactCommand(program);
13
+ registerIntentCommand(program);
14
+ registerEvaluateCommand(program);
15
+ registerWaiversCommand(program);
16
+ // Parse arguments
17
+ program.parse();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Phase 3: Hallucination Detection Engine
3
+ *
4
+ * Cross-file proof traces, intent claim mining, and hallucination detection.
5
+ * All deterministic, local-only.
6
+ */
7
+ export { generateRouteId, filePathToRoutePath, buildRouteMap, buildMiddlewareMap, isRouteCoveredByMiddleware, buildProofTrace, buildAllProofTraces, calculateCoverage, } from "./proof-trace-builder.js";
8
+ export { generateIntentId, mineIntentClaims, mineAllIntentClaims, findClaimsForRoute, findUnprovenClaims, } from "./intent-miner.js";
9
+ export { phase3Scanners, hallucinationsPack, authPackPhase3, scanCommentClaimUnproven, scanMiddlewareAssumedNotMatching, scanValidationClaimedMissing, scanAuthByUiServerGap, } from "./scanners/index.js";
10
+ export type { RouteInfo, MiddlewareInfo, IntentClaim, IntentClaimType, IntentClaimSource, IntentClaimScope, IntentClaimStrength, ProofTrace, ProofTraceStep, CoverageMetrics, Phase3Context, } from "../scanners/types.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/phase3/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,0BAA0B,EAC1B,eAAe,EACf,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,wBAAwB,EACxB,gCAAgC,EAChC,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAG7B,YAAY,EACV,SAAS,EACT,cAAc,EACd,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Phase 3: Hallucination Detection Engine
3
+ *
4
+ * Cross-file proof traces, intent claim mining, and hallucination detection.
5
+ * All deterministic, local-only.
6
+ */
7
+ // Proof trace builder
8
+ export { generateRouteId, filePathToRoutePath, buildRouteMap, buildMiddlewareMap, isRouteCoveredByMiddleware, buildProofTrace, buildAllProofTraces, calculateCoverage, } from "./proof-trace-builder.js";
9
+ // Intent claim miner
10
+ export { generateIntentId, mineIntentClaims, mineAllIntentClaims, findClaimsForRoute, findUnprovenClaims, } from "./intent-miner.js";
11
+ // Scanners
12
+ export { phase3Scanners, hallucinationsPack, authPackPhase3, scanCommentClaimUnproven, scanMiddlewareAssumedNotMatching, scanValidationClaimedMissing, scanAuthByUiServerGap, } from "./scanners/index.js";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Phase 3: Intent Claim Miner
3
+ *
4
+ * Parses comments, identifiers, imports, and config for security claims.
5
+ * Deterministic, local-only.
6
+ */
7
+ import { type SourceFile } from "ts-morph";
8
+ import type { ScanContext, IntentClaim, IntentClaimType, RouteInfo } from "../scanners/types.js";
9
+ /**
10
+ * Generate a stable intent ID
11
+ */
12
+ export declare function generateIntentId(type: IntentClaimType, file: string, line: number, evidence: string): string;
13
+ /**
14
+ * Mine intent claims from a source file
15
+ */
16
+ export declare function mineIntentClaims(ctx: ScanContext, sourceFile: SourceFile, routes: RouteInfo[]): IntentClaim[];
17
+ /**
18
+ * Mine all intent claims from scan context
19
+ */
20
+ export declare function mineAllIntentClaims(ctx: ScanContext, routes: RouteInfo[]): IntentClaim[];
21
+ /**
22
+ * Find claims that target a specific route
23
+ */
24
+ export declare function findClaimsForRoute(claims: IntentClaim[], routeId: string): IntentClaim[];
25
+ /**
26
+ * Find unproven claims (claims without corresponding proof)
27
+ */
28
+ export declare function findUnprovenClaims(claims: IntentClaim[], proofTraces: Map<string, {
29
+ authProven: boolean;
30
+ validationProven: boolean;
31
+ }>): IntentClaim[];
32
+ //# sourceMappingURL=intent-miner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent-miner.d.ts","sourceRoot":"","sources":["../../src/phase3/intent-miner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,eAAe,EAIf,SAAS,EACV,MAAM,sBAAsB,CAAC;AAkD9B;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,SAAS,EAAE,GAClB,WAAW,EAAE,CAef;AA6PD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,SAAS,EAAE,GAClB,WAAW,EAAE,CAuBf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EAAE,EACrB,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,CAIf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EAAE,EACrB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAE,CAAC,GAC3E,WAAW,EAAE,CAqBf"}
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Phase 3: Intent Claim Miner
3
+ *
4
+ * Parses comments, identifiers, imports, and config for security claims.
5
+ * Deterministic, local-only.
6
+ */
7
+ import crypto from "node:crypto";
8
+ import path from "node:path";
9
+ import { SyntaxKind } from "ts-morph";
10
+ /**
11
+ * Patterns for detecting security intent claims
12
+ */
13
+ const INTENT_PATTERNS = [
14
+ // Auth patterns
15
+ { pattern: /\b(auth|authenticate|authorized|protected|secured)\b/i, type: "AUTH_ENFORCED", strength: "strong" },
16
+ { pattern: /\brequires?\s*(auth|login|session)\b/i, type: "AUTH_ENFORCED", strength: "strong" },
17
+ { pattern: /\b(getServerSession|useSession|withAuth|requireAuth)\b/, type: "AUTH_ENFORCED", strength: "medium" },
18
+ // Validation patterns
19
+ { pattern: /\b(validat(e|ed|ion|or)|sanitiz(e|ed))\b/i, type: "INPUT_VALIDATED", strength: "strong" },
20
+ { pattern: /\b(schema|zod|yup|joi)\b/i, type: "INPUT_VALIDATED", strength: "medium" },
21
+ { pattern: /\.parse\(|\.validate\(|\.safeParse\(/, type: "INPUT_VALIDATED", strength: "medium" },
22
+ // CSRF patterns
23
+ { pattern: /\b(csrf|xsrf|cross.?site)\b/i, type: "CSRF_ENABLED", strength: "strong" },
24
+ // Rate limiting patterns
25
+ { pattern: /\b(rate.?limit|throttl|limit.?rate)\b/i, type: "RATE_LIMITED", strength: "strong" },
26
+ // Encryption patterns
27
+ { pattern: /\b(encrypt|cipher|aes|rsa)\b/i, type: "ENCRYPTED_AT_REST", strength: "medium" },
28
+ // Middleware patterns
29
+ { pattern: /\b(middleware|intercept|guard)\b/i, type: "MIDDLEWARE_PROTECTED", strength: "medium" },
30
+ ];
31
+ /**
32
+ * Security-related import patterns
33
+ */
34
+ const SECURITY_IMPORTS = [
35
+ { module: /^next-auth/, type: "AUTH_ENFORCED" },
36
+ { module: /^@auth\//, type: "AUTH_ENFORCED" },
37
+ { module: /^passport/, type: "AUTH_ENFORCED" },
38
+ { module: /^zod$/, type: "INPUT_VALIDATED" },
39
+ { module: /^yup$/, type: "INPUT_VALIDATED" },
40
+ { module: /^joi$/, type: "INPUT_VALIDATED" },
41
+ { module: /^csurf$/, type: "CSRF_ENABLED" },
42
+ { module: /^express-rate-limit$/, type: "RATE_LIMITED" },
43
+ { module: /^rate-limiter-flexible$/, type: "RATE_LIMITED" },
44
+ { module: /^helmet$/, type: "MIDDLEWARE_PROTECTED" },
45
+ ];
46
+ /**
47
+ * Generate a stable intent ID
48
+ */
49
+ export function generateIntentId(type, file, line, evidence) {
50
+ const normalized = `${type}:${file}:${line}:${evidence.slice(0, 50)}`.toLowerCase();
51
+ return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 12);
52
+ }
53
+ /**
54
+ * Mine intent claims from a source file
55
+ */
56
+ export function mineIntentClaims(ctx, sourceFile, routes) {
57
+ const claims = [];
58
+ const filePath = sourceFile.getFilePath();
59
+ const relPath = path.relative(ctx.repoRoot, filePath).replace(/\\/g, "/");
60
+ // Mine from comments
61
+ claims.push(...mineFromComments(sourceFile, relPath, routes));
62
+ // Mine from imports
63
+ claims.push(...mineFromImports(sourceFile, relPath));
64
+ // Mine from identifiers (function names, variable names)
65
+ claims.push(...mineFromIdentifiers(sourceFile, relPath, routes));
66
+ return claims;
67
+ }
68
+ /**
69
+ * Mine claims from code comments
70
+ */
71
+ function mineFromComments(sourceFile, relPath, routes) {
72
+ const claims = [];
73
+ // Get all comments in the file
74
+ const leadingComments = [];
75
+ sourceFile.forEachDescendant((node) => {
76
+ const nodeComments = node.getLeadingCommentRanges();
77
+ for (const comment of nodeComments) {
78
+ leadingComments.push({
79
+ text: comment.getText(),
80
+ line: sourceFile.getLineAndColumnAtPos(comment.getPos()).line,
81
+ });
82
+ }
83
+ });
84
+ // Also get file-level comments
85
+ const fullText = sourceFile.getFullText();
86
+ const commentMatches = fullText.matchAll(/\/\*\*?[\s\S]*?\*\/|\/\/[^\n]*/g);
87
+ for (const match of commentMatches) {
88
+ const line = sourceFile.getLineAndColumnAtPos(match.index || 0).line;
89
+ // Avoid duplicates
90
+ if (!leadingComments.some((c) => c.line === line)) {
91
+ leadingComments.push({ text: match[0], line });
92
+ }
93
+ }
94
+ for (const { text, line } of leadingComments) {
95
+ for (const { pattern, type, strength } of INTENT_PATTERNS) {
96
+ if (pattern.test(text)) {
97
+ // Determine scope based on comment context
98
+ const scope = determineCommentScope(text, relPath);
99
+ // Try to find associated route
100
+ const targetRouteId = findAssociatedRoute(relPath, line, routes);
101
+ claims.push({
102
+ intentId: generateIntentId(type, relPath, line, text),
103
+ type,
104
+ scope,
105
+ targetRouteId,
106
+ source: "comment",
107
+ location: {
108
+ file: relPath,
109
+ startLine: line,
110
+ endLine: line,
111
+ },
112
+ strength,
113
+ textEvidence: truncateEvidence(text),
114
+ });
115
+ break; // One claim per comment
116
+ }
117
+ }
118
+ }
119
+ return claims;
120
+ }
121
+ /**
122
+ * Mine claims from import statements
123
+ */
124
+ function mineFromImports(sourceFile, relPath) {
125
+ const claims = [];
126
+ for (const importDecl of sourceFile.getImportDeclarations()) {
127
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
128
+ const line = importDecl.getStartLineNumber();
129
+ for (const { module, type } of SECURITY_IMPORTS) {
130
+ if (module.test(moduleSpecifier)) {
131
+ claims.push({
132
+ intentId: generateIntentId(type, relPath, line, moduleSpecifier),
133
+ type,
134
+ scope: "module",
135
+ source: "import",
136
+ location: {
137
+ file: relPath,
138
+ startLine: line,
139
+ endLine: line,
140
+ },
141
+ strength: "medium",
142
+ textEvidence: `import from "${moduleSpecifier}"`,
143
+ });
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ return claims;
149
+ }
150
+ /**
151
+ * Mine claims from function and variable identifiers
152
+ */
153
+ function mineFromIdentifiers(sourceFile, relPath, routes) {
154
+ const claims = [];
155
+ // Function declarations
156
+ for (const func of sourceFile.getFunctions()) {
157
+ const name = func.getName() || "";
158
+ const line = func.getStartLineNumber();
159
+ for (const { pattern, type, strength } of INTENT_PATTERNS) {
160
+ if (pattern.test(name)) {
161
+ const targetRouteId = findAssociatedRoute(relPath, line, routes);
162
+ claims.push({
163
+ intentId: generateIntentId(type, relPath, line, name),
164
+ type,
165
+ scope: "route",
166
+ targetRouteId,
167
+ source: "identifier",
168
+ location: {
169
+ file: relPath,
170
+ startLine: line,
171
+ endLine: func.getEndLineNumber(),
172
+ },
173
+ strength,
174
+ textEvidence: `function ${name}`,
175
+ });
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ // Variable declarations with security-related names
181
+ sourceFile.forEachDescendant((node) => {
182
+ if (node.getKind() === SyntaxKind.VariableDeclaration) {
183
+ const text = node.getText();
184
+ const name = text.split("=")[0].trim();
185
+ const line = node.getStartLineNumber();
186
+ for (const { pattern, type, strength } of INTENT_PATTERNS) {
187
+ if (pattern.test(name)) {
188
+ claims.push({
189
+ intentId: generateIntentId(type, relPath, line, name),
190
+ type,
191
+ scope: "route",
192
+ source: "identifier",
193
+ location: {
194
+ file: relPath,
195
+ startLine: line,
196
+ endLine: line,
197
+ },
198
+ strength,
199
+ textEvidence: `variable: ${truncateEvidence(name)}`,
200
+ });
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ });
206
+ return claims;
207
+ }
208
+ /**
209
+ * Determine the scope of a comment-based claim
210
+ */
211
+ function determineCommentScope(text, filePath) {
212
+ const lowerText = text.toLowerCase();
213
+ // Module-level indicators (file or function scope maps to module)
214
+ if (lowerText.includes("@file") ||
215
+ lowerText.includes("this file") ||
216
+ lowerText.includes("this module")) {
217
+ return "module";
218
+ }
219
+ // Route-level indicators
220
+ if (lowerText.includes("this route") ||
221
+ lowerText.includes("this endpoint") ||
222
+ lowerText.includes("this handler")) {
223
+ return "route";
224
+ }
225
+ // Global indicators
226
+ if (lowerText.includes("all routes") ||
227
+ lowerText.includes("every request") ||
228
+ lowerText.includes("globally")) {
229
+ return "global";
230
+ }
231
+ // Default to route scope for comments near handlers
232
+ return "route";
233
+ }
234
+ /**
235
+ * Find an associated route for a given file and line
236
+ */
237
+ function findAssociatedRoute(filePath, line, routes) {
238
+ // Find routes in the same file
239
+ const fileRoutes = routes.filter((r) => r.file === filePath);
240
+ if (fileRoutes.length === 0) {
241
+ return undefined;
242
+ }
243
+ // If only one route, associate with it
244
+ if (fileRoutes.length === 1) {
245
+ return fileRoutes[0].routeId;
246
+ }
247
+ // Find the closest route that starts after or contains this line
248
+ for (const route of fileRoutes) {
249
+ if (line >= route.startLine && line <= route.endLine) {
250
+ return route.routeId;
251
+ }
252
+ }
253
+ // Find the closest route that starts after this line
254
+ const afterRoutes = fileRoutes.filter((r) => r.startLine > line);
255
+ if (afterRoutes.length > 0) {
256
+ return afterRoutes.sort((a, b) => a.startLine - b.startLine)[0].routeId;
257
+ }
258
+ return undefined;
259
+ }
260
+ /**
261
+ * Truncate evidence text
262
+ */
263
+ function truncateEvidence(text) {
264
+ const cleaned = text.replace(/\s+/g, " ").trim();
265
+ if (cleaned.length <= 100) {
266
+ return cleaned;
267
+ }
268
+ return cleaned.slice(0, 97) + "...";
269
+ }
270
+ /**
271
+ * Mine all intent claims from scan context
272
+ */
273
+ export function mineAllIntentClaims(ctx, routes) {
274
+ const allClaims = [];
275
+ // Mine from all source files
276
+ for (const file of ctx.fileIndex.allSourceFiles) {
277
+ // allSourceFiles are relative paths, resolve to absolute for parseFile
278
+ const absolutePath = path.join(ctx.repoRoot, file);
279
+ const sourceFile = ctx.helpers.parseFile(absolutePath);
280
+ if (!sourceFile)
281
+ continue;
282
+ const claims = mineIntentClaims(ctx, sourceFile, routes);
283
+ allClaims.push(...claims);
284
+ }
285
+ // Deduplicate by intentId
286
+ const seen = new Set();
287
+ return allClaims.filter((claim) => {
288
+ if (seen.has(claim.intentId)) {
289
+ return false;
290
+ }
291
+ seen.add(claim.intentId);
292
+ return true;
293
+ });
294
+ }
295
+ /**
296
+ * Find claims that target a specific route
297
+ */
298
+ export function findClaimsForRoute(claims, routeId) {
299
+ return claims.filter((c) => c.targetRouteId === routeId || c.scope === "global" || c.scope === "module");
300
+ }
301
+ /**
302
+ * Find unproven claims (claims without corresponding proof)
303
+ */
304
+ export function findUnprovenClaims(claims, proofTraces) {
305
+ const unproven = [];
306
+ for (const claim of claims) {
307
+ if (!claim.targetRouteId)
308
+ continue;
309
+ const proof = proofTraces.get(claim.targetRouteId);
310
+ if (!proof) {
311
+ unproven.push(claim);
312
+ continue;
313
+ }
314
+ // Check if the claim type matches the proof
315
+ if (claim.type === "AUTH_ENFORCED" && !proof.authProven) {
316
+ unproven.push(claim);
317
+ }
318
+ else if (claim.type === "INPUT_VALIDATED" && !proof.validationProven) {
319
+ unproven.push(claim);
320
+ }
321
+ }
322
+ return unproven;
323
+ }