@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,473 @@
1
+ /**
2
+ * Layer 2: Structural Scan
3
+ * Contextual analysis using variable heuristics, logic gate detection,
4
+ * dangerous functions, risky imports, auth anti-patterns, framework checks,
5
+ * and AI code fingerprinting
6
+ */
7
+
8
+ import type { Vulnerability, ScanFile } from '../types'
9
+ import type { MiddlewareAuthConfig } from '../utils/middleware-detector'
10
+ import { detectAuthHelpers, type AuthHelperContext } from '../utils/auth-helper-detector'
11
+ import type { FileAuthImports } from '../utils/imported-auth-detector'
12
+ import {
13
+ filterFindingsByPath,
14
+ type ExclusionConfig,
15
+ } from '../utils/path-exclusions'
16
+ import { detectSensitiveVariables } from './variables'
17
+ import { detectLogicGates } from './logic-gates'
18
+ import { detectDangerousFunctions } from './dangerous-functions'
19
+ import { detectRiskyImports } from './risky-imports'
20
+ import { detectAuthAntipatterns } from './auth-antipatterns'
21
+ import { detectFrameworkIssues } from './framework-checks'
22
+ import { detectAIFingerprints } from './ai-fingerprinting'
23
+ import { detectDataExposure } from './data-exposure'
24
+ import { detectBYOKPatterns } from './byok-patterns'
25
+ // Story B: AI-specific detection modules
26
+ import { detectAIPromptHygiene } from './ai-prompt-hygiene'
27
+ import { detectAIExecutionSinks } from './ai-execution-sinks'
28
+ import { detectAIAgentTools } from './ai-agent-tools'
29
+ // M5: New AI-era detectors
30
+ import { detectRAGSafetyIssues } from './ai-rag-safety'
31
+ import { detectAIEndpointProtection } from './ai-endpoint-protection'
32
+ import { detectAISchemaValidation } from './ai-schema-validation'
33
+ // Tier system imports
34
+ import {
35
+ type TierStats,
36
+ computeTierStats,
37
+ formatTierStats,
38
+ getLayer2DetectorTier,
39
+ type Layer2DetectorName,
40
+ } from '../tiers'
41
+
42
+ export interface Layer2Options {
43
+ middlewareConfig?: MiddlewareAuthConfig
44
+ authHelperContext?: AuthHelperContext
45
+ fileAuthImports?: Map<string, FileAuthImports>
46
+ /** Whether to exclude test files from scanning (default: true) */
47
+ excludeTestFiles?: boolean
48
+ /** Whether to exclude seed files from scanning (default: true) */
49
+ excludeSeedFiles?: boolean
50
+ /** Custom exclusion patterns (glob format) */
51
+ customExclusions?: string[]
52
+ }
53
+
54
+ export interface Layer2Stats {
55
+ /** Raw finding counts per detector (before dedupe) */
56
+ raw: Record<string, number>
57
+ /** Deduped finding counts per category */
58
+ deduped: Record<string, number>
59
+ /** Severity distribution of deduped findings */
60
+ bySeverity: Record<string, number>
61
+ /** Tier breakdown of deduped findings */
62
+ tiers: TierStats
63
+ /** Findings suppressed by path exclusions */
64
+ suppressedByPath: number
65
+ }
66
+
67
+ export interface Layer2Result {
68
+ vulnerabilities: Vulnerability[]
69
+ filesScanned: number
70
+ duration: number
71
+ /** Heuristic breakdown stats for noise analysis */
72
+ stats: Layer2Stats
73
+ }
74
+
75
+ export async function runLayer2Scan(
76
+ files: ScanFile[],
77
+ options: Layer2Options = {}
78
+ ): Promise<Layer2Result> {
79
+ const startTime = Date.now()
80
+ const vulnerabilities: Vulnerability[] = []
81
+ const stats = {
82
+ variables: 0,
83
+ logicGates: 0,
84
+ dangerousFunctions: 0,
85
+ riskyImports: 0,
86
+ authAntipatterns: 0,
87
+ frameworkIssues: 0,
88
+ aiFingerprints: 0,
89
+ dataExposure: 0,
90
+ byokPatterns: 0,
91
+ promptHygiene: 0,
92
+ executionSinks: 0,
93
+ agentTools: 0,
94
+ // M5: New AI-era detectors
95
+ ragSafety: 0,
96
+ endpointProtection: 0,
97
+ schemaValidation: 0,
98
+ }
99
+
100
+ // Detect auth helpers once for all files (if not already provided)
101
+ const authHelperContext = options.authHelperContext || detectAuthHelpers(files)
102
+
103
+ for (const file of files) {
104
+ // Only scan code files for Layer 2 (skip configs, etc.)
105
+ if (isCodeFile(file.path)) {
106
+ // Existing scanners
107
+ const variableFindings = detectSensitiveVariables(file.content, file.path)
108
+ const logicFindings = detectLogicGates(file.content, file.path)
109
+
110
+ // New Layer 2 scanners
111
+ const dangerousFuncFindings = detectDangerousFunctions(file.content, file.path)
112
+ const riskyImportFindings = detectRiskyImports(file.content, file.path)
113
+ const authFindings = detectAuthAntipatterns(file.content, file.path, {
114
+ middlewareConfig: options.middlewareConfig,
115
+ authHelpers: authHelperContext,
116
+ fileAuthImports: options.fileAuthImports,
117
+ })
118
+ const frameworkFindings = detectFrameworkIssues(file.content, file.path)
119
+ const aiFindings = detectAIFingerprints(file.content, file.path)
120
+ const dataExposureFindings = detectDataExposure(file.content, file.path)
121
+ const byokFindings = detectBYOKPatterns(file.content, file.path, options.middlewareConfig)
122
+
123
+ // Story B: AI-specific detection (prompt hygiene, execution sinks, agent tools)
124
+ const promptHygieneFindings = detectAIPromptHygiene(file.content, file.path)
125
+ const executionSinkFindings = detectAIExecutionSinks(file.content, file.path)
126
+ const agentToolFindings = detectAIAgentTools(file.content, file.path)
127
+
128
+ // M5: New AI-era detectors
129
+ const ragSafetyFindings = detectRAGSafetyIssues(file.content, file.path)
130
+ const endpointProtectionFindings = detectAIEndpointProtection(file.content, file.path, {
131
+ middlewareConfig: options.middlewareConfig,
132
+ })
133
+ const schemaValidationFindings = detectAISchemaValidation(file.content, file.path)
134
+
135
+ stats.variables += variableFindings.length
136
+ stats.logicGates += logicFindings.length
137
+ stats.dangerousFunctions += dangerousFuncFindings.length
138
+ stats.riskyImports += riskyImportFindings.length
139
+ stats.authAntipatterns += authFindings.length
140
+ stats.frameworkIssues += frameworkFindings.length
141
+ stats.aiFingerprints += aiFindings.length
142
+ stats.dataExposure += dataExposureFindings.length
143
+ stats.byokPatterns += byokFindings.length
144
+ stats.promptHygiene += promptHygieneFindings.length
145
+ stats.executionSinks += executionSinkFindings.length
146
+ stats.agentTools += agentToolFindings.length
147
+ stats.ragSafety += ragSafetyFindings.length
148
+ stats.endpointProtection += endpointProtectionFindings.length
149
+ stats.schemaValidation += schemaValidationFindings.length
150
+
151
+ vulnerabilities.push(
152
+ ...variableFindings,
153
+ ...logicFindings,
154
+ ...dangerousFuncFindings,
155
+ ...riskyImportFindings,
156
+ ...authFindings,
157
+ ...frameworkFindings,
158
+ ...aiFindings,
159
+ ...dataExposureFindings,
160
+ ...byokFindings,
161
+ ...promptHygieneFindings,
162
+ ...executionSinkFindings,
163
+ ...agentToolFindings,
164
+ ...ragSafetyFindings,
165
+ ...endpointProtectionFindings,
166
+ ...schemaValidationFindings
167
+ )
168
+ }
169
+ }
170
+
171
+ // Deduplicate findings
172
+ const dedupedVulnerabilities = deduplicateFindings(vulnerabilities)
173
+
174
+ // Apply path exclusions (test files, seed files, etc.)
175
+ // By default, exclude test and seed files unless explicitly disabled
176
+ const excludeTestFiles = options.excludeTestFiles !== false
177
+ const excludeSeedFiles = options.excludeSeedFiles !== false
178
+
179
+ // Build exclusion config based on options
180
+ const exclusionConfig: Partial<ExclusionConfig> = {}
181
+ if (!excludeTestFiles) {
182
+ exclusionConfig.testPatterns = []
183
+ }
184
+ if (!excludeSeedFiles) {
185
+ exclusionConfig.seedPatterns = []
186
+ }
187
+ if (options.customExclusions) {
188
+ // Add custom exclusions to all pattern types
189
+ exclusionConfig.testPatterns = [
190
+ ...(exclusionConfig.testPatterns || []),
191
+ ...options.customExclusions,
192
+ ]
193
+ }
194
+
195
+ const { kept: uniqueVulnerabilities, suppressed } = filterFindingsByPath(
196
+ dedupedVulnerabilities,
197
+ Object.keys(exclusionConfig).length > 0 ? exclusionConfig : undefined
198
+ )
199
+
200
+ // Log suppressed findings
201
+ if (suppressed.length > 0) {
202
+ console.log(`[Layer 2] Suppressed ${suppressed.length} findings in test/seed/example files:`)
203
+ const byReason = new Map<string, number>()
204
+ for (const { reason } of suppressed) {
205
+ byReason.set(reason || 'unknown', (byReason.get(reason || 'unknown') || 0) + 1)
206
+ }
207
+ for (const [reason, count] of byReason) {
208
+ console.log(` - ${reason}: ${count}`)
209
+ }
210
+ }
211
+
212
+ // Build raw stats map for logging
213
+ const rawStats: Record<string, number> = {
214
+ sensitive_variables: stats.variables,
215
+ logic_gates: stats.logicGates,
216
+ dangerous_functions: stats.dangerousFunctions,
217
+ risky_imports: stats.riskyImports,
218
+ auth_antipatterns: stats.authAntipatterns,
219
+ framework_issues: stats.frameworkIssues,
220
+ ai_fingerprints: stats.aiFingerprints,
221
+ data_exposure: stats.dataExposure,
222
+ byok_patterns: stats.byokPatterns,
223
+ ai_prompt_hygiene: stats.promptHygiene,
224
+ ai_execution_sinks: stats.executionSinks,
225
+ ai_agent_tools: stats.agentTools,
226
+ // M5: New AI-era detectors
227
+ ai_rag_safety: stats.ragSafety,
228
+ ai_endpoint_protection: stats.endpointProtection,
229
+ ai_schema_validation: stats.schemaValidation,
230
+ }
231
+
232
+ // Compute deduped counts per category
233
+ const dedupedStats: Record<string, number> = {}
234
+ for (const vuln of uniqueVulnerabilities) {
235
+ const cat = vuln.category
236
+ dedupedStats[cat] = (dedupedStats[cat] || 0) + 1
237
+ }
238
+
239
+ // Compute severity distribution
240
+ const severityStats: Record<string, number> = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }
241
+ for (const vuln of uniqueVulnerabilities) {
242
+ severityStats[vuln.severity] = (severityStats[vuln.severity] || 0) + 1
243
+ }
244
+
245
+ // Compute tier breakdown (all Layer 2 findings have layer: 2)
246
+ const tierStats = computeTierStats(
247
+ uniqueVulnerabilities.map(v => ({ category: v.category, layer: 2 as const }))
248
+ )
249
+
250
+ // Map raw stats keys to detector names for tier lookup
251
+ const detectorNameMap: Record<string, Layer2DetectorName> = {
252
+ sensitive_variables: 'variables',
253
+ logic_gates: 'logic_gates',
254
+ dangerous_functions: 'dangerous_functions',
255
+ risky_imports: 'risky_imports',
256
+ auth_antipatterns: 'auth_antipatterns',
257
+ framework_issues: 'framework_checks',
258
+ ai_fingerprints: 'ai_fingerprinting',
259
+ data_exposure: 'data_exposure',
260
+ byok_patterns: 'byok_patterns',
261
+ ai_prompt_hygiene: 'ai_prompt_hygiene',
262
+ ai_execution_sinks: 'ai_execution_sinks',
263
+ ai_agent_tools: 'ai_agent_tools',
264
+ // M5: New AI-era detectors
265
+ ai_rag_safety: 'ai_rag_safety',
266
+ ai_endpoint_protection: 'ai_endpoint_protection',
267
+ ai_schema_validation: 'ai_schema_validation',
268
+ }
269
+
270
+ // Log heuristic breakdown (raw findings before dedupe) with tier info
271
+ console.log('[Layer 2] Heuristic breakdown (raw findings before dedupe):')
272
+ for (const [name, count] of Object.entries(rawStats)) {
273
+ if (count > 0) {
274
+ const detectorName = detectorNameMap[name]
275
+ const tier = detectorName ? getLayer2DetectorTier(detectorName) : 'unknown'
276
+ console.log(` - ${name}: ${count} (${tier})`)
277
+ }
278
+ }
279
+ console.log(`[Layer 2] Tier breakdown (after dedupe): ${formatTierStats(tierStats)}`)
280
+
281
+ return {
282
+ vulnerabilities: uniqueVulnerabilities,
283
+ filesScanned: files.filter(f => isCodeFile(f.path)).length,
284
+ duration: Date.now() - startTime,
285
+ stats: {
286
+ raw: rawStats,
287
+ deduped: dedupedStats,
288
+ bySeverity: severityStats,
289
+ tiers: tierStats,
290
+ suppressedByPath: suppressed.length,
291
+ },
292
+ }
293
+ }
294
+
295
+ // Check if file is a code file (not config/data)
296
+ function isCodeFile(filePath: string): boolean {
297
+ const codeExtensions = [
298
+ '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
299
+ '.py', '.rb', '.php', '.go', '.java', '.cs',
300
+ '.rs', '.swift', '.kt', '.scala',
301
+ ]
302
+
303
+ return codeExtensions.some(ext => filePath.endsWith(ext))
304
+ }
305
+
306
+ // Remove duplicate findings on the same line and merge related findings
307
+ function deduplicateFindings(vulnerabilities: Vulnerability[]): Vulnerability[] {
308
+ // First pass: exact deduplication by file:line:category
309
+ const seen = new Map<string, Vulnerability>()
310
+
311
+ for (const vuln of vulnerabilities) {
312
+ const key = `${vuln.filePath}:${vuln.lineNumber}:${vuln.category}`
313
+ const existing = seen.get(key)
314
+
315
+ // Keep the higher severity finding
316
+ if (!existing || severityRank(vuln.severity) > severityRank(existing.severity)) {
317
+ seen.set(key, vuln)
318
+ }
319
+ }
320
+
321
+ // Second pass: merge findings on adjacent lines (within 3 lines) with same category
322
+ const dedupedList = Array.from(seen.values())
323
+ const merged = mergeAdjacentFindings(dedupedList)
324
+
325
+ // Third pass: subsume related categories (e.g., specific finding subsumes generic)
326
+ return subsumeRelatedFindings(merged)
327
+ }
328
+
329
+ // Merge findings on adjacent lines with the same category
330
+ function mergeAdjacentFindings(vulnerabilities: Vulnerability[]): Vulnerability[] {
331
+ if (vulnerabilities.length <= 1) return vulnerabilities
332
+
333
+ // Group by file and category
334
+ const groups = new Map<string, Vulnerability[]>()
335
+ for (const vuln of vulnerabilities) {
336
+ const key = `${vuln.filePath}:${vuln.category}`
337
+ const group = groups.get(key) || []
338
+ group.push(vuln)
339
+ groups.set(key, group)
340
+ }
341
+
342
+ const result: Vulnerability[] = []
343
+
344
+ for (const [, group] of groups) {
345
+ if (group.length === 1) {
346
+ result.push(group[0])
347
+ continue
348
+ }
349
+
350
+ // Sort by line number
351
+ group.sort((a, b) => a.lineNumber - b.lineNumber)
352
+
353
+ // Merge adjacent findings (within 3 lines)
354
+ let current = group[0]
355
+ let mergedCount = 1
356
+
357
+ for (let i = 1; i < group.length; i++) {
358
+ const next = group[i]
359
+ if (next.lineNumber - current.lineNumber <= 3) {
360
+ // Merge: keep higher severity, note the merge
361
+ mergedCount++
362
+ if (severityRank(next.severity) > severityRank(current.severity)) {
363
+ current = {
364
+ ...next,
365
+ title: current.title,
366
+ description: current.description,
367
+ }
368
+ }
369
+ } else {
370
+ // Not adjacent - emit current and start new
371
+ if (mergedCount > 1) {
372
+ current = {
373
+ ...current,
374
+ title: `${current.title} (${mergedCount} occurrences)`,
375
+ }
376
+ }
377
+ result.push(current)
378
+ current = next
379
+ mergedCount = 1
380
+ }
381
+ }
382
+
383
+ // Emit last
384
+ if (mergedCount > 1) {
385
+ current = {
386
+ ...current,
387
+ title: `${current.title} (${mergedCount} occurrences)`,
388
+ }
389
+ }
390
+ result.push(current)
391
+ }
392
+
393
+ return result
394
+ }
395
+
396
+ // Subsume related findings where a more specific finding covers a generic one
397
+ function subsumeRelatedFindings(vulnerabilities: Vulnerability[]): Vulnerability[] {
398
+ // Define subsumption rules: specific category subsumes generic
399
+ const subsumptionRules: Record<string, string[]> = {
400
+ // SQL injection subsumes generic dangerous function
401
+ sql_injection: ['dangerous_function'],
402
+ // Command injection subsumes generic dangerous function
403
+ command_injection: ['dangerous_function'],
404
+ // XSS subsumes generic dangerous function
405
+ xss: ['dangerous_function'],
406
+ // Specific auth issues subsume generic missing auth
407
+ missing_auth: ['auth_antipattern'],
408
+ }
409
+
410
+ // Group by file and line
411
+ const byLocation = new Map<string, Vulnerability[]>()
412
+ for (const vuln of vulnerabilities) {
413
+ const key = `${vuln.filePath}:${vuln.lineNumber}`
414
+ const group = byLocation.get(key) || []
415
+ group.push(vuln)
416
+ byLocation.set(key, group)
417
+ }
418
+
419
+ const result: Vulnerability[] = []
420
+
421
+ for (const [, group] of byLocation) {
422
+ if (group.length === 1) {
423
+ result.push(group[0])
424
+ continue
425
+ }
426
+
427
+ // Check for subsumption
428
+ const toKeep = new Set(group)
429
+ for (const vuln of group) {
430
+ const subsumes = subsumptionRules[vuln.category]
431
+ if (subsumes) {
432
+ for (const other of group) {
433
+ if (subsumes.includes(other.category) && other !== vuln) {
434
+ toKeep.delete(other)
435
+ }
436
+ }
437
+ }
438
+ }
439
+
440
+ result.push(...toKeep)
441
+ }
442
+
443
+ return result
444
+ }
445
+
446
+ function severityRank(severity: string): number {
447
+ const ranks: Record<string, number> = {
448
+ critical: 4,
449
+ high: 3,
450
+ medium: 2,
451
+ low: 1,
452
+ info: 0,
453
+ }
454
+ return ranks[severity] || 0
455
+ }
456
+
457
+ export { detectSensitiveVariables } from './variables'
458
+ export { detectLogicGates } from './logic-gates'
459
+ export { detectDangerousFunctions } from './dangerous-functions'
460
+ export { detectRiskyImports } from './risky-imports'
461
+ export { detectAuthAntipatterns } from './auth-antipatterns'
462
+ export { detectFrameworkIssues } from './framework-checks'
463
+ export { detectAIFingerprints } from './ai-fingerprinting'
464
+ export { detectDataExposure } from './data-exposure'
465
+ export { detectBYOKPatterns } from './byok-patterns'
466
+ // Story B: AI-specific detectors
467
+ export { detectAIPromptHygiene } from './ai-prompt-hygiene'
468
+ export { detectAIExecutionSinks } from './ai-execution-sinks'
469
+ export { detectAIAgentTools } from './ai-agent-tools'
470
+ // M5: New AI-era detectors
471
+ export { detectRAGSafetyIssues } from './ai-rag-safety'
472
+ export { detectAIEndpointProtection } from './ai-endpoint-protection'
473
+ export { detectAISchemaValidation } from './ai-schema-validation'
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Layer 2: Logic Gates Detection
3
+ * Identifies security bypass patterns and dangerous logic flows
4
+ */
5
+
6
+ import type { Vulnerability } from '../types'
7
+
8
+ interface LogicPattern {
9
+ name: string
10
+ pattern: RegExp
11
+ severity: 'critical' | 'high' | 'medium' | 'low'
12
+ description: string
13
+ suggestedFix: string
14
+ }
15
+
16
+ // Patterns for security bypass logic
17
+ const LOGIC_PATTERNS: LogicPattern[] = [
18
+ // Development mode bypasses
19
+ {
20
+ name: 'Development mode security bypass',
21
+ pattern: /if\s*\(\s*process\.env\.NODE_ENV\s*[!=]==?\s*['"]production['"]\s*\)\s*(return\s+true|return\s*;|continue|break)/gi,
22
+ severity: 'high',
23
+ description: 'Security check bypassed in non-production environments',
24
+ suggestedFix: 'Remove development-only bypasses or ensure they cannot be triggered in production',
25
+ },
26
+ {
27
+ name: 'Development mode auth skip',
28
+ pattern: /if\s*\(\s*process\.env\.NODE_ENV\s*[!=]==?\s*['"]development['"]\s*\)/gi,
29
+ severity: 'medium',
30
+ description: 'Code path that only runs in development - verify no security implications',
31
+ suggestedFix: 'Ensure this development-only code does not bypass security controls',
32
+ },
33
+ // Auth bypasses
34
+ {
35
+ name: 'Authentication bypass pattern',
36
+ pattern: /if\s*\(\s*(true|1|!false)\s*\)\s*{\s*(return|next|resolve)/gi,
37
+ severity: 'critical',
38
+ description: 'Hardcoded truthy condition may bypass authentication',
39
+ suggestedFix: 'Remove hardcoded bypass and implement proper authentication check',
40
+ },
41
+ {
42
+ name: 'Commented auth check',
43
+ pattern: /\/\/\s*(if|await|return).*auth|\/\/.*verify.*token|\/\/.*check.*permission/gi,
44
+ severity: 'high',
45
+ description: 'Commented out authentication/authorization code detected',
46
+ suggestedFix: 'Remove commented code or restore the security check',
47
+ },
48
+ // Skip validation patterns
49
+ {
50
+ name: 'Validation skip',
51
+ pattern: /skipValidation\s*[=:]\s*true|validate\s*[=:]\s*false|noValidate\s*[=:]\s*true/gi,
52
+ severity: 'high',
53
+ description: 'Input validation explicitly disabled',
54
+ suggestedFix: 'Enable validation or ensure this is intentional and documented',
55
+ },
56
+ // Debug/test bypasses
57
+ {
58
+ name: 'Debug bypass',
59
+ pattern: /if\s*\(\s*(DEBUG|TEST|SKIP_AUTH|BYPASS|DISABLE_AUTH)\s*\)/gi,
60
+ severity: 'high',
61
+ description: 'Debug/test flag may bypass security controls',
62
+ suggestedFix: 'Remove debug bypasses before deploying to production',
63
+ },
64
+ // Unsafe defaults
65
+ {
66
+ name: 'Unsafe default allow',
67
+ pattern: /default\s*:\s*(return\s+true|allow|permit|grant)/gi,
68
+ severity: 'medium',
69
+ description: 'Default case allows access - should default to deny',
70
+ suggestedFix: 'Change default behavior to deny access (fail-safe defaults)',
71
+ },
72
+ // Empty catch blocks
73
+ {
74
+ name: 'Empty error handler',
75
+ pattern: /catch\s*\([^)]*\)\s*{\s*(\/\/.*)?}/gi,
76
+ severity: 'medium',
77
+ description: 'Empty catch block may hide security errors',
78
+ suggestedFix: 'Log the error or handle it appropriately',
79
+ },
80
+ // Disabled security features
81
+ {
82
+ name: 'Disabled CSRF protection',
83
+ pattern: /csrf\s*[=:]\s*false|disableCsrf|csrfProtection\s*[=:]\s*false/gi,
84
+ severity: 'high',
85
+ description: 'CSRF protection explicitly disabled',
86
+ suggestedFix: 'Enable CSRF protection for state-changing requests',
87
+ },
88
+ {
89
+ name: 'Disabled SSL verification',
90
+ pattern: /rejectUnauthorized\s*[=:]\s*false|verify\s*[=:]\s*false|ssl\s*[=:]\s*false|NODE_TLS_REJECT_UNAUTHORIZED/gi,
91
+ severity: 'critical',
92
+ description: 'SSL/TLS certificate verification disabled',
93
+ suggestedFix: 'Enable SSL verification to prevent man-in-the-middle attacks',
94
+ },
95
+ // Insecure comparisons
96
+ {
97
+ name: 'Timing attack vulnerable comparison',
98
+ pattern: /===?\s*['"][^'"]{20,}['"]|password\s*===?\s*|token\s*===?\s*|secret\s*===?\s*/gi,
99
+ severity: 'medium',
100
+ description: 'Direct string comparison may be vulnerable to timing attacks',
101
+ suggestedFix: 'Use constant-time comparison for secrets (e.g., crypto.timingSafeEqual)',
102
+ },
103
+ // Unsafe redirects
104
+ {
105
+ name: 'Open redirect vulnerability',
106
+ pattern: /redirect\s*\(\s*(req\.(query|params|body)\.|request\.|url\.)/gi,
107
+ severity: 'high',
108
+ description: 'Redirect URL from user input may allow open redirect attacks',
109
+ suggestedFix: 'Validate redirect URLs against an allowlist of trusted domains',
110
+ },
111
+ // Admin/superuser bypasses
112
+ {
113
+ name: 'Admin bypass pattern',
114
+ pattern: /if\s*\(\s*(isAdmin|isSuperUser|isRoot|role\s*===?\s*['"]admin['"])\s*\)\s*(return|continue|break)/gi,
115
+ severity: 'medium',
116
+ description: 'Admin role bypasses normal security checks',
117
+ suggestedFix: 'Ensure admin bypass is intentional and properly audited',
118
+ },
119
+ ]
120
+
121
+ // Check if line is a comment
122
+ function isComment(line: string): boolean {
123
+ const trimmed = line.trim()
124
+ return (
125
+ trimmed.startsWith('//') ||
126
+ trimmed.startsWith('#') ||
127
+ trimmed.startsWith('*') ||
128
+ trimmed.startsWith('/*')
129
+ )
130
+ }
131
+
132
+ export function detectLogicGates(
133
+ content: string,
134
+ filePath: string
135
+ ): Vulnerability[] {
136
+ const vulnerabilities: Vulnerability[] = []
137
+ const lines = content.split('\n')
138
+
139
+ // Check each line against patterns
140
+ lines.forEach((line, index) => {
141
+ // Don't skip comments for the "commented auth check" pattern
142
+ const shouldSkipComments = !line.trim().startsWith('//')
143
+
144
+ for (const logicPattern of LOGIC_PATTERNS) {
145
+ // Skip comment lines for most patterns
146
+ if (shouldSkipComments && isComment(line) &&
147
+ logicPattern.name !== 'Commented auth check') {
148
+ continue
149
+ }
150
+
151
+ const regex = new RegExp(logicPattern.pattern.source, logicPattern.pattern.flags)
152
+
153
+ if (regex.test(line)) {
154
+ vulnerabilities.push({
155
+ id: `logic-${filePath}-${index + 1}-${logicPattern.name}`,
156
+ filePath,
157
+ lineNumber: index + 1,
158
+ lineContent: line.trim(),
159
+ severity: logicPattern.severity,
160
+ category: 'security_bypass',
161
+ title: logicPattern.name,
162
+ description: logicPattern.description,
163
+ suggestedFix: logicPattern.suggestedFix,
164
+ confidence: 'medium',
165
+ layer: 2,
166
+ })
167
+ break // Only report once per line
168
+ }
169
+ }
170
+ })
171
+
172
+ // Multi-line pattern detection (for more complex patterns)
173
+ const multiLineFindings = detectMultiLinePatterns(content, filePath)
174
+ vulnerabilities.push(...multiLineFindings)
175
+
176
+ return vulnerabilities
177
+ }
178
+
179
+ // Detect patterns that span multiple lines
180
+ function detectMultiLinePatterns(content: string, filePath: string): Vulnerability[] {
181
+ const vulnerabilities: Vulnerability[] = []
182
+ const lines = content.split('\n')
183
+
184
+ // Detect try-catch with empty or minimal error handling
185
+ const tryCatchPattern = /try\s*{[\s\S]*?}\s*catch\s*\([^)]*\)\s*{\s*(\n\s*)*(\/\/[^\n]*)?\s*}/g
186
+ let match
187
+
188
+ while ((match = tryCatchPattern.exec(content)) !== null) {
189
+ const lineNumber = content.substring(0, match.index).split('\n').length
190
+ vulnerabilities.push({
191
+ id: `logic-multiline-${filePath}-${lineNumber}`,
192
+ filePath,
193
+ lineNumber,
194
+ lineContent: lines[lineNumber - 1]?.trim() || 'try {',
195
+ severity: 'medium',
196
+ category: 'security_bypass',
197
+ title: 'Silent error handling',
198
+ description: 'Try-catch block with minimal error handling may hide security issues',
199
+ suggestedFix: 'Log errors appropriately and handle them based on type',
200
+ confidence: 'low',
201
+ layer: 2,
202
+ })
203
+ }
204
+
205
+ return vulnerabilities
206
+ }