@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,206 +0,0 @@
1
- import { readFileSync, resolvePath } from "../utils/file-utils.js";
2
- import { generateFingerprint, generateFindingId } from "../utils/fingerprint.js";
3
- const RULE_ID = "VC-HALL-001";
4
- const SECURITY_LIBS = [
5
- {
6
- name: "zod",
7
- category: "validation",
8
- severity: "medium",
9
- description: "Schema validation library",
10
- },
11
- {
12
- name: "yup",
13
- category: "validation",
14
- severity: "medium",
15
- description: "Schema validation library",
16
- },
17
- {
18
- name: "joi",
19
- category: "validation",
20
- severity: "medium",
21
- description: "Schema validation library",
22
- },
23
- {
24
- name: "helmet",
25
- category: "middleware",
26
- severity: "medium",
27
- description: "Security headers middleware",
28
- },
29
- {
30
- name: "cors",
31
- category: "middleware",
32
- severity: "low",
33
- description: "CORS middleware",
34
- },
35
- {
36
- name: "csurf",
37
- category: "middleware",
38
- severity: "medium",
39
- description: "CSRF protection middleware",
40
- },
41
- ];
42
- /**
43
- * Find imports of security libraries in a file
44
- *
45
- * Limitations:
46
- * - Uses regex, may match imports in comments
47
- * - Does not handle dynamic imports: import('zod')
48
- * - Does not track re-exports
49
- */
50
- export function findSecurityImports(content, libraries) {
51
- const imports = [];
52
- const lines = content.split("\n");
53
- const libPattern = libraries.join("|");
54
- // Match: import X from 'lib' / import { x, y } from 'lib' / import * as X from 'lib'
55
- const importRegex = new RegExp(`^\\s*import\\s+(?:([\\w]+)|\\{([^}]+)\\}|\\*\\s+as\\s+([\\w]+))\\s+from\\s+['"](?:${libPattern})['"]`, "gm");
56
- for (let i = 0; i < lines.length; i++) {
57
- const line = lines[i];
58
- importRegex.lastIndex = 0;
59
- for (const lib of libraries) {
60
- // Check each library pattern
61
- const patterns = [
62
- // import X from 'lib'
63
- new RegExp(`^\\s*import\\s+(\\w+)\\s+from\\s+['"]${lib}['"]`),
64
- // import { x, y } from 'lib'
65
- new RegExp(`^\\s*import\\s+\\{([^}]+)\\}\\s+from\\s+['"]${lib}['"]`),
66
- // import * as X from 'lib'
67
- new RegExp(`^\\s*import\\s+\\*\\s+as\\s+(\\w+)\\s+from\\s+['"]${lib}['"]`),
68
- // import X, { y } from 'lib'
69
- new RegExp(`^\\s*import\\s+(\\w+)\\s*,\\s*\\{([^}]+)\\}\\s+from\\s+['"]${lib}['"]`),
70
- ];
71
- for (let p = 0; p < patterns.length; p++) {
72
- const match = line.match(patterns[p]);
73
- if (match) {
74
- const importedNames = [];
75
- let isDefaultImport = false;
76
- let isNamespaceImport = false;
77
- if (p === 0) {
78
- // Default import
79
- importedNames.push(match[1]);
80
- isDefaultImport = true;
81
- }
82
- else if (p === 1) {
83
- // Named imports
84
- const names = match[1].split(",").map((n) => n.trim().split(/\s+as\s+/)[0].trim());
85
- importedNames.push(...names.filter(Boolean));
86
- }
87
- else if (p === 2) {
88
- // Namespace import
89
- importedNames.push(match[1]);
90
- isNamespaceImport = true;
91
- }
92
- else if (p === 3) {
93
- // Default + named
94
- importedNames.push(match[1]);
95
- isDefaultImport = true;
96
- const names = match[2].split(",").map((n) => n.trim().split(/\s+as\s+/)[0].trim());
97
- importedNames.push(...names.filter(Boolean));
98
- }
99
- imports.push({
100
- library: lib,
101
- importedNames,
102
- line: i + 1,
103
- snippet: line.trim(),
104
- isDefaultImport,
105
- isNamespaceImport,
106
- });
107
- break;
108
- }
109
- }
110
- }
111
- }
112
- return imports;
113
- }
114
- /**
115
- * Check if any imported identifiers are used after the import line
116
- */
117
- export function checkIdentifierUsage(content, importLine, identifiers, isNamespaceImport) {
118
- const lines = content.split("\n");
119
- const afterImport = lines.slice(importLine).join("\n");
120
- return identifiers.map((id) => {
121
- // For namespace imports, check for namespace.something
122
- if (isNamespaceImport) {
123
- const namespaceRegex = new RegExp(`\\b${id}\\s*\\.`, "g");
124
- return { identifier: id, used: namespaceRegex.test(afterImport) };
125
- }
126
- // For regular imports, check for identifier usage
127
- // Match the identifier as a word boundary, not just as part of another word
128
- const usageRegex = new RegExp(`\\b${id}\\b`, "g");
129
- const matches = afterImport.match(usageRegex);
130
- // If found at least once, it's used (the import line itself is excluded)
131
- return { identifier: id, used: matches !== null && matches.length > 0 };
132
- });
133
- }
134
- /**
135
- * Unused Security Imports Scanner
136
- *
137
- * Detects when security libraries are imported but not used
138
- */
139
- export async function scanUnusedSecurityImports(context) {
140
- const { targetDir, sourceFiles } = context;
141
- const findings = [];
142
- const libNames = SECURITY_LIBS.map((l) => l.name);
143
- for (const relFile of sourceFiles) {
144
- const absPath = resolvePath(targetDir, relFile);
145
- const content = readFileSync(absPath);
146
- if (!content)
147
- continue;
148
- const imports = findSecurityImports(content, libNames);
149
- for (const imp of imports) {
150
- const libInfo = SECURITY_LIBS.find((l) => l.name === imp.library);
151
- if (!libInfo)
152
- continue;
153
- const usageResults = checkIdentifierUsage(content, imp.line, imp.importedNames, imp.isNamespaceImport);
154
- const unusedIdentifiers = usageResults
155
- .filter((r) => !r.used)
156
- .map((r) => r.identifier);
157
- if (unusedIdentifiers.length === 0)
158
- continue;
159
- // All imported identifiers are unused
160
- const allUnused = unusedIdentifiers.length === imp.importedNames.length;
161
- const evidence = [
162
- {
163
- file: relFile,
164
- startLine: imp.line,
165
- endLine: imp.line,
166
- snippet: imp.snippet,
167
- label: allUnused
168
- ? `Unused import of ${imp.library}`
169
- : `Partially unused import: ${unusedIdentifiers.join(", ")}`,
170
- },
171
- ];
172
- const fingerprint = generateFingerprint({
173
- ruleId: RULE_ID,
174
- file: relFile,
175
- symbol: imp.library,
176
- startLine: imp.line,
177
- });
178
- findings.push({
179
- id: generateFindingId({
180
- ruleId: RULE_ID,
181
- file: relFile,
182
- symbol: imp.library,
183
- startLine: imp.line,
184
- }),
185
- severity: libInfo.severity,
186
- confidence: allUnused ? 0.9 : 0.75,
187
- category: libInfo.category,
188
- ruleId: RULE_ID,
189
- title: allUnused
190
- ? `Imported ${libInfo.description.toLowerCase()} "${imp.library}" is not used`
191
- : `Partially unused import from "${imp.library}"`,
192
- description: allUnused
193
- ? `The security library "${imp.library}" is imported but none of its exports are used. This may indicate incomplete implementation of ${libInfo.category} functionality.`
194
- : `Some exports from "${imp.library}" (${unusedIdentifiers.join(", ")}) are imported but not used. This may indicate incomplete implementation.`,
195
- evidence,
196
- remediation: {
197
- recommendedFix: allUnused
198
- ? `Either implement ${libInfo.category} using "${imp.library}" or remove the unused import.`
199
- : `Remove unused imports (${unusedIdentifiers.join(", ")}) or implement their usage.`,
200
- },
201
- fingerprint,
202
- });
203
- }
204
- }
205
- return findings;
206
- }
@@ -1,5 +0,0 @@
1
- import type { ScannerPack } from "../types.js";
2
- export declare const uploadsPack: ScannerPack;
3
- export { scanMissingUploadConstraints } from "./missing-constraints.js";
4
- export { scanPublicUploadPath } from "./public-path.js";
5
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/uploads/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,eAAO,MAAM,WAAW,EAAE,WAIzB,CAAC;AAEF,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,9 +0,0 @@
1
- import { scanMissingUploadConstraints } from "./missing-constraints.js";
2
- import { scanPublicUploadPath } from "./public-path.js";
3
- export const uploadsPack = {
4
- id: "uploads",
5
- name: "File Upload Security",
6
- scanners: [scanMissingUploadConstraints, scanPublicUploadPath],
7
- };
8
- export { scanMissingUploadConstraints } from "./missing-constraints.js";
9
- export { scanPublicUploadPath } from "./public-path.js";
@@ -1,15 +0,0 @@
1
- import type { Finding } from "@vibecheck/schema";
2
- import type { ScanContext } from "../types.js";
3
- /**
4
- * VC-UP-001: File upload without size/type constraints
5
- *
6
- * Targets:
7
- * - multer usage without limits/fileFilter
8
- * - formidable/busboy without size checks
9
- * - Next.js route handlers reading formData and accepting file without checking type/size
10
- *
11
- * Precision:
12
- * - Only flag if a file is accepted AND there is no check of file.size, file.type, or limits
13
- */
14
- export declare function scanMissingUploadConstraints(context: ScanContext): Promise<Finding[]>;
15
- //# sourceMappingURL=missing-constraints.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"missing-constraints.d.ts","sourceRoot":"","sources":["../../../src/scanners/uploads/missing-constraints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAoG3F"}
@@ -1,109 +0,0 @@
1
- import { resolvePath } from "../../utils/file-utils.js";
2
- import { generateFingerprint, generateFindingId } from "../../utils/fingerprint.js";
3
- const RULE_ID = "VC-UP-001";
4
- /**
5
- * VC-UP-001: File upload without size/type constraints
6
- *
7
- * Targets:
8
- * - multer usage without limits/fileFilter
9
- * - formidable/busboy without size checks
10
- * - Next.js route handlers reading formData and accepting file without checking type/size
11
- *
12
- * Precision:
13
- * - Only flag if a file is accepted AND there is no check of file.size, file.type, or limits
14
- */
15
- export async function scanMissingUploadConstraints(context) {
16
- const { repoRoot, fileIndex, helpers } = context;
17
- const findings = [];
18
- // Scan API route files
19
- for (const relPath of fileIndex.apiRouteFiles) {
20
- const absPath = resolvePath(repoRoot, relPath);
21
- const sourceFile = helpers.parseFile(absPath);
22
- if (!sourceFile)
23
- continue;
24
- const handlers = helpers.findRouteHandlers(sourceFile);
25
- for (const handler of handlers) {
26
- const uploadHandlers = helpers.findFileUploadHandlers(handler.functionNode);
27
- for (const upload of uploadHandlers) {
28
- // Only flag if missing both size and type checks
29
- if (upload.hasSizeCheck && upload.hasTypeCheck)
30
- continue;
31
- const missingChecks = [];
32
- if (!upload.hasSizeCheck)
33
- missingChecks.push("size limit");
34
- if (!upload.hasTypeCheck)
35
- missingChecks.push("type validation");
36
- const evidence = [
37
- {
38
- file: relPath,
39
- startLine: upload.line,
40
- endLine: upload.line,
41
- snippet: upload.snippet,
42
- label: `File upload via ${upload.uploadMethod} without ${missingChecks.join(" or ")}`,
43
- },
44
- ];
45
- const fingerprint = generateFingerprint({
46
- ruleId: RULE_ID,
47
- file: relPath,
48
- symbol: upload.uploadMethod,
49
- startLine: upload.line,
50
- });
51
- findings.push({
52
- id: generateFindingId({
53
- ruleId: RULE_ID,
54
- file: relPath,
55
- symbol: upload.uploadMethod,
56
- startLine: upload.line,
57
- }),
58
- ruleId: RULE_ID,
59
- title: `File upload missing ${missingChecks.join(" and ")}`,
60
- description: `This file upload handler using ${upload.uploadMethod} lacks ${missingChecks.join(" and ")}. Without size limits, attackers can upload extremely large files causing denial of service. Without type validation, attackers may upload malicious executables, scripts, or unexpected content types.`,
61
- severity: "high",
62
- confidence: 0.8,
63
- category: "uploads",
64
- evidence,
65
- remediation: {
66
- recommendedFix: `Add both size limits and file type validation to your upload handler.`,
67
- patch: upload.uploadMethod === "multer"
68
- ? `// For multer, add limits and fileFilter:
69
- const upload = multer({
70
- limits: {
71
- fileSize: 5 * 1024 * 1024, // 5MB limit
72
- files: 1, // Single file
73
- },
74
- fileFilter: (req, file, cb) => {
75
- const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
76
- if (allowedTypes.includes(file.mimetype)) {
77
- cb(null, true);
78
- } else {
79
- cb(new Error('Invalid file type'));
80
- }
81
- },
82
- });`
83
- : `// For Next.js formData, validate the file:
84
- const formData = await request.formData();
85
- const file = formData.get('file') as File;
86
-
87
- // Check file size
88
- const MAX_SIZE = 5 * 1024 * 1024; // 5MB
89
- if (file.size > MAX_SIZE) {
90
- return Response.json({ error: 'File too large' }, { status: 400 });
91
- }
92
-
93
- // Check file type
94
- const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
95
- if (!allowedTypes.includes(file.type)) {
96
- return Response.json({ error: 'Invalid file type' }, { status: 400 });
97
- }`,
98
- },
99
- links: {
100
- cwe: "https://cwe.mitre.org/data/definitions/434.html",
101
- owasp: "https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html",
102
- },
103
- fingerprint,
104
- });
105
- }
106
- }
107
- }
108
- return findings;
109
- }
@@ -1,11 +0,0 @@
1
- import type { Finding } from "@vibecheck/schema";
2
- import type { ScanContext } from "../types.js";
3
- /**
4
- * VC-UP-002: Upload served from public path
5
- *
6
- * Detect:
7
- * - Writing uploaded file to `public/` or `static/` directories
8
- * - Or referencing user-supplied filenames in public URL generation
9
- */
10
- export declare function scanPublicUploadPath(context: ScanContext): Promise<Finding[]>;
11
- //# sourceMappingURL=public-path.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"public-path.d.ts","sourceRoot":"","sources":["../../../src/scanners/uploads/public-path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAmFnF"}
@@ -1,87 +0,0 @@
1
- import { resolvePath } from "../../utils/file-utils.js";
2
- import { generateFingerprint, generateFindingId } from "../../utils/fingerprint.js";
3
- const RULE_ID = "VC-UP-002";
4
- /**
5
- * VC-UP-002: Upload served from public path
6
- *
7
- * Detect:
8
- * - Writing uploaded file to `public/` or `static/` directories
9
- * - Or referencing user-supplied filenames in public URL generation
10
- */
11
- export async function scanPublicUploadPath(context) {
12
- const { repoRoot, fileIndex, helpers } = context;
13
- const findings = [];
14
- for (const relPath of fileIndex.allSourceFiles) {
15
- const absPath = resolvePath(repoRoot, relPath);
16
- const sourceFile = helpers.parseFile(absPath);
17
- if (!sourceFile)
18
- continue;
19
- const publicWrites = helpers.findPublicFileWrites(sourceFile);
20
- for (const write of publicWrites) {
21
- if (!write.isPublicDir)
22
- continue;
23
- const evidence = [
24
- {
25
- file: relPath,
26
- startLine: write.line,
27
- endLine: write.line,
28
- snippet: write.snippet,
29
- label: `File written to public path${write.usesUserFilename ? " with user-supplied filename" : ""}`,
30
- },
31
- ];
32
- const fingerprint = generateFingerprint({
33
- ruleId: RULE_ID,
34
- file: relPath,
35
- symbol: "public-write",
36
- startLine: write.line,
37
- });
38
- const severity = write.usesUserFilename ? "high" : "medium";
39
- findings.push({
40
- id: generateFindingId({
41
- ruleId: RULE_ID,
42
- file: relPath,
43
- symbol: "public-write",
44
- startLine: write.line,
45
- }),
46
- ruleId: RULE_ID,
47
- title: `Uploaded file written to public directory${write.usesUserFilename ? " with user filename" : ""}`,
48
- description: `Files are being written to a publicly accessible directory (${write.writePath}).${write.usesUserFilename ? " The filename appears to come from user input, which could allow path traversal attacks or filename-based exploits." : ""} Publicly accessible uploads can be directly accessed by anyone and may be executed by the server if not properly configured.`,
49
- severity,
50
- confidence: 0.85,
51
- category: "uploads",
52
- evidence,
53
- remediation: {
54
- recommendedFix: `Store uploads outside the public directory and serve them through a controlled API endpoint. Always sanitize and generate safe filenames.`,
55
- patch: `// DON'T: Write directly to public folder
56
- // fs.writeFile(path.join('public', filename), buffer);
57
-
58
- // DO: Store outside public with generated names
59
- import { randomUUID } from 'crypto';
60
- import path from 'path';
61
-
62
- // Generate safe filename
63
- const ext = path.extname(originalFilename).toLowerCase();
64
- const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'];
65
- if (!allowedExtensions.includes(ext)) {
66
- throw new Error('Invalid file extension');
67
- }
68
-
69
- const safeFilename = \`\${randomUUID()}\${ext}\`;
70
- const uploadPath = path.join(process.cwd(), 'uploads', safeFilename);
71
-
72
- // Write to non-public directory
73
- await fs.writeFile(uploadPath, buffer);
74
-
75
- // Serve through API route:
76
- // GET /api/files/[id] -> Stream file from uploads directory`,
77
- },
78
- links: {
79
- cwe: "https://cwe.mitre.org/data/definitions/434.html",
80
- owasp: "https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html",
81
- },
82
- fingerprint,
83
- });
84
- }
85
- }
86
- return findings;
87
- }
@@ -1,14 +0,0 @@
1
- import type { Finding } from "@vibecheck/schema";
2
- import type { ScanContext } from "../types.js";
3
- /**
4
- * VC-VAL-002: Client-side-only validation
5
- *
6
- * Detects if validation libraries (zod/yup/joi) are used in frontend components
7
- * but API route handlers accept raw input without validation.
8
- *
9
- * Precision rule:
10
- * - Only flag if there exists at least one validation schema used in frontend
11
- * - AND at least one API route file exists that uses req.json without validation
12
- */
13
- export declare function scanClientSideOnlyValidation(context: ScanContext): Promise<Finding[]>;
14
- //# sourceMappingURL=client-side-only.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-side-only.d.ts","sourceRoot":"","sources":["../../../src/scanners/validation/client-side-only.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAyI3F"}
@@ -1,140 +0,0 @@
1
- import { resolvePath } from "../../utils/file-utils.js";
2
- import { generateFingerprint, generateFindingId } from "../../utils/fingerprint.js";
3
- const RULE_ID = "VC-VAL-002";
4
- /**
5
- * VC-VAL-002: Client-side-only validation
6
- *
7
- * Detects if validation libraries (zod/yup/joi) are used in frontend components
8
- * but API route handlers accept raw input without validation.
9
- *
10
- * Precision rule:
11
- * - Only flag if there exists at least one validation schema used in frontend
12
- * - AND at least one API route file exists that uses req.json without validation
13
- */
14
- export async function scanClientSideOnlyValidation(context) {
15
- const { repoRoot, fileIndex, helpers } = context;
16
- const findings = [];
17
- // Find frontend files with validation
18
- const frontendFilesWithValidation = [];
19
- for (const relPath of fileIndex.allSourceFiles) {
20
- // Check if it's a frontend file (components, pages, but NOT api routes)
21
- const isFrontend = (relPath.includes("/components/") ||
22
- relPath.includes("\\components\\") ||
23
- relPath.includes("/app/") ||
24
- relPath.includes("\\app\\")) &&
25
- !relPath.includes("/api/") &&
26
- !relPath.includes("\\api\\") &&
27
- (relPath.endsWith(".tsx") || relPath.endsWith(".jsx"));
28
- if (!isFrontend)
29
- continue;
30
- const absPath = resolvePath(repoRoot, relPath);
31
- const sourceFile = helpers.parseFile(absPath);
32
- if (!sourceFile)
33
- continue;
34
- if (helpers.hasValidationSchemas(sourceFile)) {
35
- frontendFilesWithValidation.push(relPath);
36
- }
37
- }
38
- // If no frontend validation found, skip
39
- if (frontendFilesWithValidation.length === 0) {
40
- return findings;
41
- }
42
- // Check API routes for missing server-side validation
43
- for (const relPath of fileIndex.apiRouteFiles) {
44
- const absPath = resolvePath(repoRoot, relPath);
45
- const sourceFile = helpers.parseFile(absPath);
46
- if (!sourceFile)
47
- continue;
48
- const handlers = helpers.findRouteHandlers(sourceFile);
49
- const fileHasValidation = helpers.hasValidationSchemas(sourceFile);
50
- for (const handler of handlers) {
51
- // Only check POST/PUT/PATCH handlers that accept body
52
- if (!["POST", "PUT", "PATCH"].includes(handler.method))
53
- continue;
54
- const handlerText = helpers.getNodeText(handler.functionNode);
55
- // Check if handler reads request body
56
- const readsBody = /\.json\s*\(\s*\)|\.body\b|await\s+request\.json\s*\(\s*\)/.test(handlerText);
57
- if (!readsBody)
58
- continue;
59
- // Check if handler has validation
60
- const validationUsages = helpers.findValidationUsage(handler.functionNode);
61
- const hasValidation = validationUsages.length > 0 || fileHasValidation;
62
- if (hasValidation)
63
- continue;
64
- const evidence = [
65
- {
66
- file: relPath,
67
- startLine: handler.startLine,
68
- endLine: handler.endLine,
69
- snippet: handlerText.slice(0, 200) + "...",
70
- label: `${handler.method} handler accepts request body without server-side validation`,
71
- },
72
- {
73
- file: frontendFilesWithValidation[0],
74
- startLine: 1,
75
- endLine: 1,
76
- snippet: `Validation schema found in frontend component`,
77
- label: "Frontend validation exists but server validation missing",
78
- },
79
- ];
80
- const fingerprint = generateFingerprint({
81
- ruleId: RULE_ID,
82
- file: relPath,
83
- symbol: handler.method,
84
- startLine: handler.startLine,
85
- });
86
- findings.push({
87
- id: generateFindingId({
88
- ruleId: RULE_ID,
89
- file: relPath,
90
- symbol: handler.method,
91
- startLine: handler.startLine,
92
- }),
93
- ruleId: RULE_ID,
94
- title: `Client-side-only validation on ${handler.method} endpoint`,
95
- description: `This API endpoint accepts request body data without server-side validation, while validation schemas exist in frontend components. Client-side validation can be easily bypassed - attackers can send requests directly to your API. Always validate input on the server.`,
96
- severity: "medium",
97
- confidence: 0.7,
98
- category: "validation",
99
- evidence,
100
- remediation: {
101
- recommendedFix: `Add server-side validation using the same schema. Consider sharing schemas between frontend and backend.`,
102
- patch: `// Share your Zod schema between frontend and backend:
103
- // lib/schemas/user.ts
104
- import { z } from "zod";
105
-
106
- export const createUserSchema = z.object({
107
- name: z.string().min(1),
108
- email: z.string().email(),
109
- });
110
-
111
- // In your API route:
112
- import { createUserSchema } from "@/lib/schemas/user";
113
-
114
- export async function POST(request: Request) {
115
- const body = await request.json();
116
-
117
- // Server-side validation
118
- const result = createUserSchema.safeParse(body);
119
- if (!result.success) {
120
- return Response.json(
121
- { error: result.error.flatten() },
122
- { status: 400 }
123
- );
124
- }
125
-
126
- // Use validated data
127
- const { name, email } = result.data;
128
- // ...
129
- }`,
130
- },
131
- links: {
132
- cwe: "https://cwe.mitre.org/data/definitions/20.html",
133
- owasp: "https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html",
134
- },
135
- fingerprint,
136
- });
137
- }
138
- }
139
- return findings;
140
- }
@@ -1,12 +0,0 @@
1
- import type { Finding } from "@vibecheck/schema";
2
- import type { ScanContext } from "../types.js";
3
- /**
4
- * VC-VAL-001: Validation defined but output ignored
5
- *
6
- * Detects when zod/yup/joi validation is performed but:
7
- * - The validated result is not assigned to a variable
8
- * - The validated result is assigned but never referenced
9
- * - Raw request body is used after validation
10
- */
11
- export declare function scanIgnoredValidation(context: ScanContext): Promise<Finding[]>;
12
- //# sourceMappingURL=ignored-validation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ignored-validation.d.ts","sourceRoot":"","sources":["../../../src/scanners/validation/ignored-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAqHpF"}