@oculum/scanner 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/dist/formatters/cli-terminal.d.ts +27 -0
  2. package/dist/formatters/cli-terminal.d.ts.map +1 -0
  3. package/dist/formatters/cli-terminal.js +412 -0
  4. package/dist/formatters/cli-terminal.js.map +1 -0
  5. package/dist/formatters/github-comment.d.ts +41 -0
  6. package/dist/formatters/github-comment.d.ts.map +1 -0
  7. package/dist/formatters/github-comment.js +306 -0
  8. package/dist/formatters/github-comment.js.map +1 -0
  9. package/dist/formatters/grouping.d.ts +52 -0
  10. package/dist/formatters/grouping.d.ts.map +1 -0
  11. package/dist/formatters/grouping.js +152 -0
  12. package/dist/formatters/grouping.js.map +1 -0
  13. package/dist/formatters/index.d.ts +9 -0
  14. package/dist/formatters/index.d.ts.map +1 -0
  15. package/dist/formatters/index.js +35 -0
  16. package/dist/formatters/index.js.map +1 -0
  17. package/dist/formatters/vscode-diagnostic.d.ts +103 -0
  18. package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
  19. package/dist/formatters/vscode-diagnostic.js +151 -0
  20. package/dist/formatters/vscode-diagnostic.js.map +1 -0
  21. package/dist/index.d.ts +52 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +648 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/layer1/comments.d.ts +8 -0
  26. package/dist/layer1/comments.d.ts.map +1 -0
  27. package/dist/layer1/comments.js +203 -0
  28. package/dist/layer1/comments.js.map +1 -0
  29. package/dist/layer1/config-audit.d.ts +8 -0
  30. package/dist/layer1/config-audit.d.ts.map +1 -0
  31. package/dist/layer1/config-audit.js +252 -0
  32. package/dist/layer1/config-audit.js.map +1 -0
  33. package/dist/layer1/entropy.d.ts +8 -0
  34. package/dist/layer1/entropy.d.ts.map +1 -0
  35. package/dist/layer1/entropy.js +500 -0
  36. package/dist/layer1/entropy.js.map +1 -0
  37. package/dist/layer1/file-flags.d.ts +7 -0
  38. package/dist/layer1/file-flags.d.ts.map +1 -0
  39. package/dist/layer1/file-flags.js +112 -0
  40. package/dist/layer1/file-flags.js.map +1 -0
  41. package/dist/layer1/index.d.ts +36 -0
  42. package/dist/layer1/index.d.ts.map +1 -0
  43. package/dist/layer1/index.js +132 -0
  44. package/dist/layer1/index.js.map +1 -0
  45. package/dist/layer1/patterns.d.ts +8 -0
  46. package/dist/layer1/patterns.d.ts.map +1 -0
  47. package/dist/layer1/patterns.js +482 -0
  48. package/dist/layer1/patterns.js.map +1 -0
  49. package/dist/layer1/urls.d.ts +8 -0
  50. package/dist/layer1/urls.d.ts.map +1 -0
  51. package/dist/layer1/urls.js +296 -0
  52. package/dist/layer1/urls.js.map +1 -0
  53. package/dist/layer1/weak-crypto.d.ts +7 -0
  54. package/dist/layer1/weak-crypto.d.ts.map +1 -0
  55. package/dist/layer1/weak-crypto.js +291 -0
  56. package/dist/layer1/weak-crypto.js.map +1 -0
  57. package/dist/layer2/ai-agent-tools.d.ts +19 -0
  58. package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
  59. package/dist/layer2/ai-agent-tools.js +528 -0
  60. package/dist/layer2/ai-agent-tools.js.map +1 -0
  61. package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
  62. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
  63. package/dist/layer2/ai-endpoint-protection.js +332 -0
  64. package/dist/layer2/ai-endpoint-protection.js.map +1 -0
  65. package/dist/layer2/ai-execution-sinks.d.ts +18 -0
  66. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
  67. package/dist/layer2/ai-execution-sinks.js +496 -0
  68. package/dist/layer2/ai-execution-sinks.js.map +1 -0
  69. package/dist/layer2/ai-fingerprinting.d.ts +7 -0
  70. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
  71. package/dist/layer2/ai-fingerprinting.js +654 -0
  72. package/dist/layer2/ai-fingerprinting.js.map +1 -0
  73. package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
  74. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
  75. package/dist/layer2/ai-prompt-hygiene.js +356 -0
  76. package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
  77. package/dist/layer2/ai-rag-safety.d.ts +21 -0
  78. package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
  79. package/dist/layer2/ai-rag-safety.js +459 -0
  80. package/dist/layer2/ai-rag-safety.js.map +1 -0
  81. package/dist/layer2/ai-schema-validation.d.ts +25 -0
  82. package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
  83. package/dist/layer2/ai-schema-validation.js +375 -0
  84. package/dist/layer2/ai-schema-validation.js.map +1 -0
  85. package/dist/layer2/auth-antipatterns.d.ts +20 -0
  86. package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
  87. package/dist/layer2/auth-antipatterns.js +333 -0
  88. package/dist/layer2/auth-antipatterns.js.map +1 -0
  89. package/dist/layer2/byok-patterns.d.ts +12 -0
  90. package/dist/layer2/byok-patterns.d.ts.map +1 -0
  91. package/dist/layer2/byok-patterns.js +299 -0
  92. package/dist/layer2/byok-patterns.js.map +1 -0
  93. package/dist/layer2/dangerous-functions.d.ts +7 -0
  94. package/dist/layer2/dangerous-functions.d.ts.map +1 -0
  95. package/dist/layer2/dangerous-functions.js +1375 -0
  96. package/dist/layer2/dangerous-functions.js.map +1 -0
  97. package/dist/layer2/data-exposure.d.ts +16 -0
  98. package/dist/layer2/data-exposure.d.ts.map +1 -0
  99. package/dist/layer2/data-exposure.js +279 -0
  100. package/dist/layer2/data-exposure.js.map +1 -0
  101. package/dist/layer2/framework-checks.d.ts +7 -0
  102. package/dist/layer2/framework-checks.d.ts.map +1 -0
  103. package/dist/layer2/framework-checks.js +388 -0
  104. package/dist/layer2/framework-checks.js.map +1 -0
  105. package/dist/layer2/index.d.ts +58 -0
  106. package/dist/layer2/index.d.ts.map +1 -0
  107. package/dist/layer2/index.js +380 -0
  108. package/dist/layer2/index.js.map +1 -0
  109. package/dist/layer2/logic-gates.d.ts +7 -0
  110. package/dist/layer2/logic-gates.d.ts.map +1 -0
  111. package/dist/layer2/logic-gates.js +182 -0
  112. package/dist/layer2/logic-gates.js.map +1 -0
  113. package/dist/layer2/risky-imports.d.ts +7 -0
  114. package/dist/layer2/risky-imports.d.ts.map +1 -0
  115. package/dist/layer2/risky-imports.js +161 -0
  116. package/dist/layer2/risky-imports.js.map +1 -0
  117. package/dist/layer2/variables.d.ts +8 -0
  118. package/dist/layer2/variables.d.ts.map +1 -0
  119. package/dist/layer2/variables.js +152 -0
  120. package/dist/layer2/variables.js.map +1 -0
  121. package/dist/layer3/anthropic.d.ts +83 -0
  122. package/dist/layer3/anthropic.d.ts.map +1 -0
  123. package/dist/layer3/anthropic.js +1745 -0
  124. package/dist/layer3/anthropic.js.map +1 -0
  125. package/dist/layer3/index.d.ts +24 -0
  126. package/dist/layer3/index.d.ts.map +1 -0
  127. package/dist/layer3/index.js +119 -0
  128. package/dist/layer3/index.js.map +1 -0
  129. package/dist/layer3/openai.d.ts +25 -0
  130. package/dist/layer3/openai.d.ts.map +1 -0
  131. package/dist/layer3/openai.js +238 -0
  132. package/dist/layer3/openai.js.map +1 -0
  133. package/dist/layer3/package-check.d.ts +63 -0
  134. package/dist/layer3/package-check.d.ts.map +1 -0
  135. package/dist/layer3/package-check.js +508 -0
  136. package/dist/layer3/package-check.js.map +1 -0
  137. package/dist/modes/incremental.d.ts +66 -0
  138. package/dist/modes/incremental.d.ts.map +1 -0
  139. package/dist/modes/incremental.js +200 -0
  140. package/dist/modes/incremental.js.map +1 -0
  141. package/dist/tiers.d.ts +125 -0
  142. package/dist/tiers.d.ts.map +1 -0
  143. package/dist/tiers.js +234 -0
  144. package/dist/tiers.js.map +1 -0
  145. package/dist/types.d.ts +175 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +50 -0
  148. package/dist/types.js.map +1 -0
  149. package/dist/utils/auth-helper-detector.d.ts +56 -0
  150. package/dist/utils/auth-helper-detector.d.ts.map +1 -0
  151. package/dist/utils/auth-helper-detector.js +360 -0
  152. package/dist/utils/auth-helper-detector.js.map +1 -0
  153. package/dist/utils/context-helpers.d.ts +96 -0
  154. package/dist/utils/context-helpers.d.ts.map +1 -0
  155. package/dist/utils/context-helpers.js +493 -0
  156. package/dist/utils/context-helpers.js.map +1 -0
  157. package/dist/utils/diff-detector.d.ts +53 -0
  158. package/dist/utils/diff-detector.d.ts.map +1 -0
  159. package/dist/utils/diff-detector.js +104 -0
  160. package/dist/utils/diff-detector.js.map +1 -0
  161. package/dist/utils/diff-parser.d.ts +80 -0
  162. package/dist/utils/diff-parser.d.ts.map +1 -0
  163. package/dist/utils/diff-parser.js +202 -0
  164. package/dist/utils/diff-parser.js.map +1 -0
  165. package/dist/utils/imported-auth-detector.d.ts +37 -0
  166. package/dist/utils/imported-auth-detector.d.ts.map +1 -0
  167. package/dist/utils/imported-auth-detector.js +251 -0
  168. package/dist/utils/imported-auth-detector.js.map +1 -0
  169. package/dist/utils/middleware-detector.d.ts +55 -0
  170. package/dist/utils/middleware-detector.d.ts.map +1 -0
  171. package/dist/utils/middleware-detector.js +260 -0
  172. package/dist/utils/middleware-detector.js.map +1 -0
  173. package/dist/utils/oauth-flow-detector.d.ts +41 -0
  174. package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
  175. package/dist/utils/oauth-flow-detector.js +202 -0
  176. package/dist/utils/oauth-flow-detector.js.map +1 -0
  177. package/dist/utils/path-exclusions.d.ts +55 -0
  178. package/dist/utils/path-exclusions.d.ts.map +1 -0
  179. package/dist/utils/path-exclusions.js +222 -0
  180. package/dist/utils/path-exclusions.js.map +1 -0
  181. package/dist/utils/project-context-builder.d.ts +119 -0
  182. package/dist/utils/project-context-builder.d.ts.map +1 -0
  183. package/dist/utils/project-context-builder.js +534 -0
  184. package/dist/utils/project-context-builder.js.map +1 -0
  185. package/dist/utils/registry-clients.d.ts +93 -0
  186. package/dist/utils/registry-clients.d.ts.map +1 -0
  187. package/dist/utils/registry-clients.js +273 -0
  188. package/dist/utils/registry-clients.js.map +1 -0
  189. package/dist/utils/trpc-analyzer.d.ts +78 -0
  190. package/dist/utils/trpc-analyzer.d.ts.map +1 -0
  191. package/dist/utils/trpc-analyzer.js +297 -0
  192. package/dist/utils/trpc-analyzer.js.map +1 -0
  193. package/package.json +45 -0
  194. package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
  195. package/src/__tests__/benchmark/fixtures/index.ts +68 -0
  196. package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
  197. package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
  198. package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
  199. package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
  200. package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
  201. package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
  202. package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
  203. package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
  204. package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
  205. package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
  206. package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
  207. package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
  208. package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
  209. package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
  210. package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
  211. package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
  212. package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
  213. package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
  214. package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
  215. package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
  216. package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
  217. package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
  218. package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
  219. package/src/__tests__/benchmark/index.ts +29 -0
  220. package/src/__tests__/benchmark/run-benchmark.ts +144 -0
  221. package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
  222. package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
  223. package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
  224. package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
  225. package/src/__tests__/benchmark/types.ts +144 -0
  226. package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
  227. package/src/__tests__/regression/known-false-positives.test.ts +467 -0
  228. package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
  229. package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
  230. package/src/__tests__/validation/analyze-results.ts +542 -0
  231. package/src/__tests__/validation/extract-for-triage.ts +146 -0
  232. package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
  233. package/src/__tests__/validation/run-validation.ts +364 -0
  234. package/src/__tests__/validation/triage-template.md +132 -0
  235. package/src/formatters/cli-terminal.ts +446 -0
  236. package/src/formatters/github-comment.ts +382 -0
  237. package/src/formatters/grouping.ts +190 -0
  238. package/src/formatters/index.ts +47 -0
  239. package/src/formatters/vscode-diagnostic.ts +243 -0
  240. package/src/index.ts +823 -0
  241. package/src/layer1/comments.ts +218 -0
  242. package/src/layer1/config-audit.ts +289 -0
  243. package/src/layer1/entropy.ts +583 -0
  244. package/src/layer1/file-flags.ts +127 -0
  245. package/src/layer1/index.ts +181 -0
  246. package/src/layer1/patterns.ts +516 -0
  247. package/src/layer1/urls.ts +334 -0
  248. package/src/layer1/weak-crypto.ts +328 -0
  249. package/src/layer2/ai-agent-tools.ts +601 -0
  250. package/src/layer2/ai-endpoint-protection.ts +387 -0
  251. package/src/layer2/ai-execution-sinks.ts +580 -0
  252. package/src/layer2/ai-fingerprinting.ts +758 -0
  253. package/src/layer2/ai-prompt-hygiene.ts +411 -0
  254. package/src/layer2/ai-rag-safety.ts +511 -0
  255. package/src/layer2/ai-schema-validation.ts +421 -0
  256. package/src/layer2/auth-antipatterns.ts +394 -0
  257. package/src/layer2/byok-patterns.ts +336 -0
  258. package/src/layer2/dangerous-functions.ts +1563 -0
  259. package/src/layer2/data-exposure.ts +315 -0
  260. package/src/layer2/framework-checks.ts +433 -0
  261. package/src/layer2/index.ts +473 -0
  262. package/src/layer2/logic-gates.ts +206 -0
  263. package/src/layer2/risky-imports.ts +186 -0
  264. package/src/layer2/variables.ts +166 -0
  265. package/src/layer3/anthropic.ts +2030 -0
  266. package/src/layer3/index.ts +130 -0
  267. package/src/layer3/package-check.ts +604 -0
  268. package/src/modes/incremental.ts +293 -0
  269. package/src/tiers.ts +318 -0
  270. package/src/types.ts +284 -0
  271. package/src/utils/auth-helper-detector.ts +443 -0
  272. package/src/utils/context-helpers.ts +535 -0
  273. package/src/utils/diff-detector.ts +135 -0
  274. package/src/utils/diff-parser.ts +272 -0
  275. package/src/utils/imported-auth-detector.ts +320 -0
  276. package/src/utils/middleware-detector.ts +333 -0
  277. package/src/utils/oauth-flow-detector.ts +246 -0
  278. package/src/utils/path-exclusions.ts +266 -0
  279. package/src/utils/project-context-builder.ts +707 -0
  280. package/src/utils/registry-clients.ts +351 -0
  281. package/src/utils/trpc-analyzer.ts +382 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Security Benchmark Test Runner
3
+ *
4
+ * Comprehensive security benchmark suite for the Oculum scanner, covering:
5
+ * - Traditional OWASP-style vulnerabilities
6
+ * - AI-specific security issues (prompt injection, unsafe execution, BYOK, etc.)
7
+ * - False positive scenarios (patterns that should NOT be flagged)
8
+ *
9
+ * Run with: npx tsx src/lib/scanner/__tests__/benchmark/run-benchmark.ts
10
+ */
11
+
12
+ import {
13
+ allTestGroups,
14
+ falsePositiveFixtures,
15
+ highEntropyTests,
16
+ } from './fixtures'
17
+ import {
18
+ runTestFixture,
19
+ printTestResult,
20
+ computeSummary,
21
+ printSummary,
22
+ printMetrics,
23
+ } from './utils/test-runner'
24
+ import type { TestResult } from './types'
25
+
26
+ async function main() {
27
+ console.log('='.repeat(80))
28
+ console.log('OCULUM SECURITY BENCHMARK TEST SUITE')
29
+ console.log('='.repeat(80))
30
+ console.log('')
31
+
32
+ const results: TestResult[] = []
33
+
34
+ // ========================================
35
+ // LAYER 1 TESTS
36
+ // ========================================
37
+
38
+ console.log('\n' + '─'.repeat(80))
39
+ console.log('LAYER 1: SURFACE SCAN TESTS')
40
+ console.log('─'.repeat(80))
41
+
42
+ const layer1Groups = allTestGroups.filter(g => g.layer === 1)
43
+
44
+ for (const group of layer1Groups) {
45
+ console.log(`\nšŸ“Œ TIER ${group.tier} (${group.tier === 'A' ? 'Core' : 'AI-Assisted'}): ${group.name}`)
46
+
47
+ if (group.truePositives.length > 0) {
48
+ console.log(' Testing TRUE POSITIVES...')
49
+ for (const fixture of group.truePositives) {
50
+ const result = await runTestFixture(fixture)
51
+ results.push(result)
52
+ printTestResult(result)
53
+ }
54
+ }
55
+
56
+ if (group.falseNegatives.length > 0) {
57
+ console.log(' Testing FALSE NEGATIVES (should NOT flag)...')
58
+ for (const fixture of group.falseNegatives) {
59
+ const result = await runTestFixture(fixture)
60
+ results.push(result)
61
+ printTestResult(result)
62
+ }
63
+ }
64
+ }
65
+
66
+ // High Entropy (Tier B)
67
+ console.log(`\nšŸ“Œ TIER B (AI-Assisted): ${highEntropyTests.name}`)
68
+ console.log(' Testing TRUE POSITIVES...')
69
+ for (const fixture of highEntropyTests.truePositives) {
70
+ const result = await runTestFixture(fixture)
71
+ results.push(result)
72
+ printTestResult(result)
73
+ }
74
+
75
+ // ========================================
76
+ // LAYER 2 TESTS
77
+ // ========================================
78
+
79
+ console.log('\n' + '─'.repeat(80))
80
+ console.log('LAYER 2: STRUCTURAL SCAN TESTS')
81
+ console.log('─'.repeat(80))
82
+
83
+ const layer2Groups = allTestGroups.filter(g => g.layer === 2)
84
+
85
+ for (const group of layer2Groups) {
86
+ console.log(`\nšŸ“Œ TIER ${group.tier} (${group.tier === 'A' ? 'Core' : 'AI-Assisted'}): ${group.name}`)
87
+
88
+ if (group.truePositives.length > 0) {
89
+ console.log(' Testing TRUE POSITIVES...')
90
+ for (const fixture of group.truePositives) {
91
+ const result = await runTestFixture(fixture)
92
+ results.push(result)
93
+ printTestResult(result)
94
+ }
95
+ }
96
+
97
+ if (group.falseNegatives.length > 0) {
98
+ console.log(' Testing FALSE NEGATIVES (should NOT flag)...')
99
+ for (const fixture of group.falseNegatives) {
100
+ const result = await runTestFixture(fixture)
101
+ results.push(result)
102
+ printTestResult(result)
103
+ }
104
+ }
105
+ }
106
+
107
+ // ========================================
108
+ // FALSE POSITIVE TESTS
109
+ // ========================================
110
+
111
+ console.log('\n' + '─'.repeat(80))
112
+ console.log('FALSE POSITIVE TESTS')
113
+ console.log('─'.repeat(80))
114
+
115
+ for (const fixture of falsePositiveFixtures) {
116
+ console.log(`\nšŸ“Œ ${fixture.name}`)
117
+ const result = await runTestFixture(fixture)
118
+ results.push(result)
119
+ printTestResult(result)
120
+ }
121
+
122
+ // ========================================
123
+ // RESULTS SUMMARY
124
+ // ========================================
125
+
126
+ console.log('\n' + '='.repeat(80))
127
+ console.log('BENCHMARK RESULTS SUMMARY')
128
+ console.log('='.repeat(80))
129
+
130
+ // Compute summary with metrics
131
+ const summary = computeSummary(results)
132
+
133
+ // Print detailed metrics
134
+ printMetrics(summary.metrics)
135
+
136
+ // Print final summary
137
+ printSummary(summary)
138
+
139
+ // Exit with appropriate code
140
+ process.exit(summary.passRate === 100 ? 0 : 1)
141
+ }
142
+
143
+ // Run the test suite
144
+ main().catch(console.error)
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Scan Depth Profile Validation
3
+ * Validates that cheap/validated/deep modes work correctly
4
+ */
5
+
6
+ import { runLayer1Scan } from '../../layer1'
7
+ import { runLayer2Scan } from '../../layer2'
8
+ import { getTierForCategory, isTierVisibleAtDepth, formatTierStats, computeTierStats } from '../../tiers'
9
+ import type { ScanFile, ScanDepth, Vulnerability } from '../../types'
10
+
11
+ // Test fixture with various vulnerability types
12
+ const testFile: ScanFile = {
13
+ path: 'src/api/test-route.ts',
14
+ content: `
15
+ // This file contains patterns from different tiers for testing
16
+
17
+ // Tier A (core): Hardcoded secret - always visible
18
+ const API_KEY = "sk_live_abc123def456ghi789jkl012mno345pqr678"
19
+
20
+ // Tier A (core): Dangerous function - always visible
21
+ export function executeQuery(input: string) {
22
+ return db.query(\`SELECT * FROM users WHERE id = '\${input}'\`)
23
+ }
24
+
25
+ // Tier B (ai_assisted): AI pattern - needs validation in validated/deep modes
26
+ // TODO: This was AI generated and needs review
27
+ const placeholder = "implementation goes here"
28
+
29
+ // Tier C (experimental): Should always be hidden
30
+ const suspiciousVar = "possibly_risky"
31
+
32
+ export async function GET() {
33
+ // Missing auth pattern (Tier B)
34
+ const data = await db.query('SELECT * FROM data')
35
+ return NextResponse.json(data)
36
+ }
37
+ `,
38
+ language: 'typescript',
39
+ size: 700,
40
+ }
41
+
42
+ // Helper to classify findings by tier
43
+ function classifyFindings(findings: Vulnerability[]): {
44
+ core: Vulnerability[]
45
+ ai_assisted: Vulnerability[]
46
+ experimental: Vulnerability[]
47
+ } {
48
+ const result: {
49
+ core: Vulnerability[]
50
+ ai_assisted: Vulnerability[]
51
+ experimental: Vulnerability[]
52
+ } = { core: [], ai_assisted: [], experimental: [] }
53
+
54
+ for (const f of findings) {
55
+ const tier = getTierForCategory(f.category, f.layer)
56
+ result[tier].push(f)
57
+ }
58
+
59
+ return result
60
+ }
61
+
62
+ async function runValidation() {
63
+ console.log('=' .repeat(70))
64
+ console.log('SCAN DEPTH PROFILE VALIDATION')
65
+ console.log('=' .repeat(70))
66
+
67
+ // Run scans
68
+ console.log('\nšŸ“‹ Running Layer 1 + Layer 2 scans...')
69
+ const startScan = Date.now()
70
+ const l1 = await runLayer1Scan([testFile])
71
+ const l2 = await runLayer2Scan([testFile])
72
+ const scanDuration = Date.now() - startScan
73
+
74
+ const allFindings = [...l1.vulnerabilities, ...l2.vulnerabilities]
75
+ const classified = classifyFindings(allFindings)
76
+
77
+ console.log(`\nā±ļø Scan duration: ${scanDuration}ms`)
78
+ console.log(`\nšŸ“Š Total findings: ${allFindings.length}`)
79
+ console.log(` Tier A (core): ${classified.core.length}`)
80
+ console.log(` Tier B (ai_assisted): ${classified.ai_assisted.length}`)
81
+ console.log(` Tier C (experimental): ${classified.experimental.length}`)
82
+
83
+ // Show tier stats
84
+ const tierStats = computeTierStats(
85
+ allFindings.map(v => ({ category: v.category, layer: v.layer }))
86
+ )
87
+ console.log(`\nšŸ“ˆ ${formatTierStats(tierStats)}`)
88
+
89
+ // Test each depth mode
90
+ const depths: ScanDepth[] = ['cheap', 'validated', 'deep']
91
+
92
+ console.log('\n' + '-'.repeat(70))
93
+ console.log('DEPTH MODE BEHAVIOR')
94
+ console.log('-'.repeat(70))
95
+
96
+ for (const depth of depths) {
97
+ console.log(`\nšŸŽšļø ${depth.toUpperCase()} mode:`)
98
+
99
+ const coreVisible = isTierVisibleAtDepth('core', depth)
100
+ const aiAssistedVisible = isTierVisibleAtDepth('ai_assisted', depth)
101
+ const experimentalVisible = isTierVisibleAtDepth('experimental', depth)
102
+
103
+ console.log(` Tier A (core): ${coreVisible ? 'āœ… VISIBLE' : 'āŒ hidden'}`)
104
+ console.log(` Tier B (ai_assisted): ${aiAssistedVisible ? 'āœ… VISIBLE (via AI validation)' : 'āŒ hidden'}`)
105
+ console.log(` Tier C (experimental): ${experimentalVisible ? 'āœ… VISIBLE' : 'āŒ hidden'}`)
106
+
107
+ // Calculate what would be surfaced
108
+ let surfaced = 0
109
+ let validated = 0
110
+ let hidden = 0
111
+
112
+ if (coreVisible) surfaced += classified.core.length
113
+ else hidden += classified.core.length
114
+
115
+ if (aiAssistedVisible) validated += classified.ai_assisted.length
116
+ else hidden += classified.ai_assisted.length
117
+
118
+ hidden += classified.experimental.length // Always hidden
119
+
120
+ console.log(` → ${surfaced} surfaced directly, ${validated} to AI validation, ${hidden} hidden`)
121
+
122
+ // Show expected behavior
123
+ switch (depth) {
124
+ case 'cheap':
125
+ console.log(` → AI validation: SKIPPED`)
126
+ console.log(` → Layer 3: SKIPPED`)
127
+ console.log(` → Target time: <5s for typical PRs`)
128
+ break
129
+ case 'validated':
130
+ console.log(` → AI validation: ENABLED`)
131
+ console.log(` → Layer 3: SKIPPED`)
132
+ console.log(` → Target time: <15s with <10 AI calls`)
133
+ break
134
+ case 'deep':
135
+ console.log(` → AI validation: ENABLED`)
136
+ console.log(` → Layer 3: ENABLED`)
137
+ console.log(` → Target time: Thorough analysis`)
138
+ break
139
+ }
140
+ }
141
+
142
+ // Performance test
143
+ console.log('\n' + '-'.repeat(70))
144
+ console.log('PERFORMANCE VALIDATION')
145
+ console.log('-'.repeat(70))
146
+
147
+ // Test with 10 files
148
+ const files10 = Array.from({ length: 10 }, (_, i) => ({
149
+ ...testFile,
150
+ path: `src/api/test-route-${i}.ts`,
151
+ }))
152
+
153
+ console.log('\nā±ļø Testing with 10 files (cheap mode, no AI)...')
154
+ const start10 = Date.now()
155
+ await runLayer1Scan(files10)
156
+ await runLayer2Scan(files10)
157
+ const duration10 = Date.now() - start10
158
+ console.log(` Duration: ${duration10}ms`)
159
+ console.log(` ${duration10 < 5000 ? 'āœ…' : 'āŒ'} Target: <5000ms`)
160
+
161
+ // Test with 50 files
162
+ const files50 = Array.from({ length: 50 }, (_, i) => ({
163
+ ...testFile,
164
+ path: `src/api/test-route-${i}.ts`,
165
+ }))
166
+
167
+ console.log('\nā±ļø Testing with 50 files (cheap mode, no AI)...')
168
+ const start50 = Date.now()
169
+ await runLayer1Scan(files50)
170
+ await runLayer2Scan(files50)
171
+ const duration50 = Date.now() - start50
172
+ console.log(` Duration: ${duration50}ms`)
173
+ console.log(` ${duration50 < 10000 ? 'āœ…' : 'āŒ'} Target: <10000ms`)
174
+ console.log(` Rate: ${Math.round(50000 / duration50)} files/sec`)
175
+
176
+ // Summary
177
+ console.log('\n' + '=' .repeat(70))
178
+ console.log('VALIDATION SUMMARY')
179
+ console.log('=' .repeat(70))
180
+
181
+ const allPassed =
182
+ classified.core.length > 0 && // Should have some Tier A findings
183
+ duration10 < 5000 && // 10 files under 5s
184
+ duration50 < 10000 // 50 files under 10s
185
+
186
+ if (allPassed) {
187
+ console.log('\nāœ… ALL DEPTH PROFILE VALIDATIONS PASSED')
188
+ } else {
189
+ console.log('\nāŒ SOME VALIDATIONS FAILED')
190
+ }
191
+
192
+ console.log('\nWorkflow Profile Recommendations:')
193
+ console.log('ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”')
194
+ console.log('│ Workflow │ Default Depth│ Rationale │')
195
+ console.log('ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤')
196
+ console.log('│ GitHub PR │ cheap │ Fast feedback, high-signal findings│')
197
+ console.log('│ VS Code │ validated │ Interactive, balance depth + speed │')
198
+ console.log('│ CLI (default) │ cheap │ Fast local scans │')
199
+ console.log('│ CLI --deep │ deep │ Thorough analysis when requested │')
200
+ console.log('│ Onboarding │ deep │ Full picture on first scan │')
201
+ console.log('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜')
202
+
203
+ console.log('\n')
204
+ }
205
+
206
+ runValidation().catch(console.error)
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Real-World Testing Utility
3
+ * Tests the scanner on actual code patterns and measures performance
4
+ */
5
+
6
+ import * as fs from 'fs'
7
+ import * as path from 'path'
8
+ import { runLayer1Scan } from '../../layer1'
9
+ import { runLayer2Scan } from '../../layer2'
10
+ import { formatTierStats, computeTierStats } from '../../tiers'
11
+ import { formatTerminalOutput } from '../../formatters/cli-terminal'
12
+ import type { ScanFile, Vulnerability, ScanResult } from '../../types'
13
+
14
+ /**
15
+ * Test result for a directory scan
16
+ */
17
+ interface RealWorldTestResult {
18
+ directory: string
19
+ filesScanned: number
20
+ totalFindings: number
21
+ bySeverity: Record<string, number>
22
+ byCategory: Record<string, number>
23
+ scanDuration: number
24
+ filesPerSecond: number
25
+ topFindings: Vulnerability[]
26
+ potentialFalsePositives: Vulnerability[]
27
+ }
28
+
29
+ /**
30
+ * Scan a local directory and analyze results
31
+ */
32
+ async function scanDirectory(dirPath: string, maxFiles: number = 100): Promise<RealWorldTestResult> {
33
+ const startTime = Date.now()
34
+
35
+ // Collect files
36
+ const files: ScanFile[] = []
37
+ const extensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.java', '.rb', '.php'])
38
+
39
+ function collectFiles(dir: string, depth: number = 0) {
40
+ if (depth > 10 || files.length >= maxFiles) return
41
+
42
+ try {
43
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
44
+
45
+ for (const entry of entries) {
46
+ if (files.length >= maxFiles) break
47
+
48
+ const fullPath = path.join(dir, entry.name)
49
+
50
+ // Skip common non-source directories
51
+ if (entry.isDirectory()) {
52
+ if (['node_modules', '.git', 'dist', 'build', '.next', '__pycache__', 'venv'].includes(entry.name)) {
53
+ continue
54
+ }
55
+ collectFiles(fullPath, depth + 1)
56
+ } else if (entry.isFile()) {
57
+ const ext = path.extname(entry.name).toLowerCase()
58
+ if (extensions.has(ext)) {
59
+ try {
60
+ const content = fs.readFileSync(fullPath, 'utf-8')
61
+ if (content.length < 50000) { // Skip very large files
62
+ files.push({
63
+ path: fullPath.replace(dirPath + '/', ''),
64
+ content,
65
+ language: ext.slice(1),
66
+ size: content.length,
67
+ })
68
+ }
69
+ } catch {
70
+ // Skip unreadable files
71
+ }
72
+ }
73
+ }
74
+ }
75
+ } catch {
76
+ // Skip unreadable directories
77
+ }
78
+ }
79
+
80
+ collectFiles(dirPath)
81
+
82
+ if (files.length === 0) {
83
+ console.log(`No scannable files found in ${dirPath}`)
84
+ return {
85
+ directory: dirPath,
86
+ filesScanned: 0,
87
+ totalFindings: 0,
88
+ bySeverity: {},
89
+ byCategory: {},
90
+ scanDuration: 0,
91
+ filesPerSecond: 0,
92
+ topFindings: [],
93
+ potentialFalsePositives: [],
94
+ }
95
+ }
96
+
97
+ console.log(`Scanning ${files.length} files from ${dirPath}...`)
98
+
99
+ // Run scans
100
+ const layer1Result = await runLayer1Scan(files)
101
+ const layer2Result = await runLayer2Scan(files)
102
+
103
+ const scanDuration = Date.now() - startTime
104
+ const allFindings = [...layer1Result.vulnerabilities, ...layer2Result.vulnerabilities]
105
+
106
+ // Analyze results
107
+ const bySeverity: Record<string, number> = {
108
+ critical: 0,
109
+ high: 0,
110
+ medium: 0,
111
+ low: 0,
112
+ info: 0,
113
+ }
114
+
115
+ const byCategory: Record<string, number> = {}
116
+
117
+ for (const finding of allFindings) {
118
+ bySeverity[finding.severity] = (bySeverity[finding.severity] || 0) + 1
119
+ byCategory[finding.category] = (byCategory[finding.category] || 0) + 1
120
+ }
121
+
122
+ // Identify potential false positives (info/low in test files, or common patterns)
123
+ const potentialFalsePositives = allFindings.filter(f => {
124
+ // Test files with any findings
125
+ if (f.filePath.includes('test') || f.filePath.includes('spec')) return true
126
+ // Example/fixture files
127
+ if (f.filePath.includes('example') || f.filePath.includes('fixture')) return true
128
+ // Mock files
129
+ if (f.filePath.includes('mock') || f.filePath.includes('__mocks__')) return true
130
+ return false
131
+ })
132
+
133
+ // Sort findings by severity for top findings
134
+ const severityOrder = { critical: 5, high: 4, medium: 3, low: 2, info: 1 }
135
+ const sortedFindings = [...allFindings].sort(
136
+ (a, b) => severityOrder[b.severity] - severityOrder[a.severity]
137
+ )
138
+
139
+ return {
140
+ directory: dirPath,
141
+ filesScanned: files.length,
142
+ totalFindings: allFindings.length,
143
+ bySeverity,
144
+ byCategory,
145
+ scanDuration,
146
+ filesPerSecond: Math.round((files.length * 1000) / scanDuration),
147
+ topFindings: sortedFindings.slice(0, 10),
148
+ potentialFalsePositives,
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Print test result summary
154
+ */
155
+ function printResult(result: RealWorldTestResult) {
156
+ console.log('\n' + '='.repeat(70))
157
+ console.log(`REAL-WORLD TEST: ${result.directory}`)
158
+ console.log('='.repeat(70))
159
+
160
+ console.log(`\nšŸ“ Files scanned: ${result.filesScanned}`)
161
+ console.log(`ā±ļø Scan duration: ${result.scanDuration}ms`)
162
+ console.log(`šŸš€ Performance: ${result.filesPerSecond} files/sec`)
163
+
164
+ console.log(`\nšŸ“Š Total findings: ${result.totalFindings}`)
165
+ console.log('By severity:')
166
+ for (const [severity, count] of Object.entries(result.bySeverity)) {
167
+ if (count > 0) console.log(` ${severity}: ${count}`)
168
+ }
169
+
170
+ console.log('\nBy category:')
171
+ const sortedCategories = Object.entries(result.byCategory)
172
+ .sort((a, b) => b[1] - a[1])
173
+ .slice(0, 10)
174
+ for (const [category, count] of sortedCategories) {
175
+ console.log(` ${category}: ${count}`)
176
+ }
177
+
178
+ if (result.potentialFalsePositives.length > 0) {
179
+ console.log(`\nāš ļø Potential false positives: ${result.potentialFalsePositives.length}`)
180
+ console.log('(Findings in test/example/mock files)')
181
+ }
182
+
183
+ if (result.topFindings.length > 0) {
184
+ console.log('\nšŸ” Top findings:')
185
+ for (const finding of result.topFindings.slice(0, 5)) {
186
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.category}: ${finding.title}`)
187
+ console.log(` ${finding.filePath}:${finding.lineNumber}`)
188
+ }
189
+ }
190
+
191
+ // Assessment
192
+ console.log('\n' + '-'.repeat(70))
193
+ console.log('ASSESSMENT')
194
+ console.log('-'.repeat(70))
195
+
196
+ const fpRate = result.totalFindings > 0
197
+ ? (result.potentialFalsePositives.length / result.totalFindings * 100).toFixed(1)
198
+ : '0'
199
+
200
+ console.log(`False positive rate (test/mock files): ${fpRate}%`)
201
+ console.log(`Performance target (<5s for 100 files): ${result.scanDuration < 5000 ? 'āœ… PASS' : 'āŒ FAIL'}`)
202
+
203
+ const blockingCount = result.bySeverity.critical + result.bySeverity.high
204
+ if (blockingCount > 0) {
205
+ console.log(`\nāš ļø ${blockingCount} blocking issues found - review recommended`)
206
+ } else {
207
+ console.log('\nāœ… No blocking issues found')
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Run real-world tests
213
+ */
214
+ async function main() {
215
+ console.log('=' .repeat(70))
216
+ console.log('OCULUM REAL-WORLD TESTING')
217
+ console.log('=' .repeat(70))
218
+
219
+ // Test on the scanner's own codebase
220
+ const scannerPath = path.resolve(__dirname, '../..')
221
+
222
+ console.log('\nšŸ“‚ Testing on scanner codebase itself...')
223
+ const result = await scanDirectory(scannerPath, 50)
224
+ printResult(result)
225
+
226
+ // Summary
227
+ console.log('\n' + '='.repeat(70))
228
+ console.log('TESTING COMPLETE')
229
+ console.log('='.repeat(70))
230
+
231
+ console.log('\nTo test on other directories:')
232
+ console.log(' npx tsx src/lib/scanner/__tests__/benchmark/run-real-world-test.ts /path/to/repo')
233
+ }
234
+
235
+ // Allow testing specific directories via command line
236
+ const targetDir = process.argv[2]
237
+ if (targetDir) {
238
+ scanDirectory(path.resolve(targetDir), 100)
239
+ .then(printResult)
240
+ .catch(console.error)
241
+ } else {
242
+ main().catch(console.error)
243
+ }