@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,418 @@
1
+ /**
2
+ * AI Endpoint Protection Test Fixtures
3
+ * Tests for detecting unprotected AI/LLM endpoints
4
+ */
5
+
6
+ import type { TestGroup } from '../../types'
7
+
8
+ export const aiEndpointProtectionTests: TestGroup = {
9
+ name: 'AI Endpoint Protection',
10
+ tier: 'A',
11
+ layer: 2,
12
+ description: 'Detection of AI endpoints without proper authentication or rate limiting',
13
+
14
+ truePositives: [
15
+ {
16
+ name: 'AI Endpoints - True Positives',
17
+ expectFindings: true,
18
+ expectedCategories: ['ai_endpoint_unprotected'],
19
+ description: 'Unprotected AI endpoints that MUST be detected',
20
+ file: {
21
+ path: 'src/app/api/chat/route.ts',
22
+ content: `
23
+ import { OpenAI } from 'openai'
24
+ import { NextResponse } from 'next/server'
25
+
26
+ const openai = new OpenAI()
27
+
28
+ // Next.js App Router - NO AUTH, NO RATE LIMIT - HIGH
29
+ export async function POST(request: Request) {
30
+ const { messages } = await request.json()
31
+
32
+ const completion = await openai.chat.completions.create({
33
+ model: 'gpt-4',
34
+ messages,
35
+ })
36
+
37
+ return NextResponse.json({ result: completion.choices[0].message })
38
+ }
39
+ `,
40
+ language: 'typescript',
41
+ size: 400,
42
+ },
43
+ },
44
+ {
45
+ name: 'Express AI Route - No Protection',
46
+ expectFindings: true,
47
+ expectedCategories: ['ai_endpoint_unprotected'],
48
+ description: 'Express route with AI calls but no auth or rate limiting',
49
+ file: {
50
+ path: 'src/routes/api/generate.ts',
51
+ content: `
52
+ import express from 'express'
53
+ import Anthropic from '@anthropic-ai/sdk'
54
+
55
+ const router = express.Router()
56
+ const anthropic = new Anthropic()
57
+
58
+ // Express route - NO AUTH, NO RATE LIMIT - HIGH
59
+ router.post('/generate', async (req, res) => {
60
+ const { prompt } = req.body
61
+
62
+ const message = await anthropic.messages.create({
63
+ model: 'claude-3-opus-20240229',
64
+ max_tokens: 1024,
65
+ messages: [{ role: 'user', content: prompt }],
66
+ })
67
+
68
+ res.json({ response: message.content })
69
+ })
70
+
71
+ export default router
72
+ `,
73
+ language: 'typescript',
74
+ size: 450,
75
+ },
76
+ },
77
+ {
78
+ name: 'Next.js Pages API - No Protection',
79
+ expectFindings: true,
80
+ expectedCategories: ['ai_endpoint_unprotected'],
81
+ description: 'Next.js Pages API route with AI calls but no protection',
82
+ file: {
83
+ path: 'src/pages/api/completion.ts',
84
+ content: `
85
+ import type { NextApiRequest, NextApiResponse } from 'next'
86
+ import OpenAI from 'openai'
87
+
88
+ const openai = new OpenAI()
89
+
90
+ // Next.js Pages API - NO AUTH - HIGH
91
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
92
+ if (req.method !== 'POST') {
93
+ return res.status(405).json({ error: 'Method not allowed' })
94
+ }
95
+
96
+ const { prompt } = req.body
97
+
98
+ const response = await openai.chat.completions.create({
99
+ model: 'gpt-3.5-turbo',
100
+ messages: [{ role: 'user', content: prompt }],
101
+ })
102
+
103
+ res.json({ result: response.choices[0].message.content })
104
+ }
105
+ `,
106
+ language: 'typescript',
107
+ size: 500,
108
+ },
109
+ },
110
+ {
111
+ name: 'Vercel AI SDK - No Protection',
112
+ expectFindings: true,
113
+ expectedCategories: ['ai_endpoint_unprotected'],
114
+ description: 'Vercel AI SDK endpoint without auth or rate limiting',
115
+ file: {
116
+ path: 'src/app/api/stream/route.ts',
117
+ content: `
118
+ import { streamText } from 'ai'
119
+ import { openai } from '@ai-sdk/openai'
120
+
121
+ // Vercel AI SDK - NO AUTH, NO RATE LIMIT - HIGH
122
+ export async function POST(req: Request) {
123
+ const { messages } = await req.json()
124
+
125
+ const result = await streamText({
126
+ model: openai('gpt-4'),
127
+ messages,
128
+ })
129
+
130
+ return result.toDataStreamResponse()
131
+ }
132
+ `,
133
+ language: 'typescript',
134
+ size: 300,
135
+ },
136
+ },
137
+ {
138
+ name: 'Auth Only - Missing Rate Limit',
139
+ expectFindings: true,
140
+ expectedCategories: ['ai_endpoint_unprotected'],
141
+ description: 'AI endpoint with auth but missing rate limiting - LOW severity',
142
+ file: {
143
+ path: 'src/app/api/ask/route.ts',
144
+ content: `
145
+ import { OpenAI } from 'openai'
146
+ import { NextResponse } from 'next/server'
147
+ import { getServerSession } from 'next-auth'
148
+
149
+ const openai = new OpenAI()
150
+
151
+ // Has auth but no rate limiting - LOW
152
+ export async function POST(request: Request) {
153
+ const session = await getServerSession()
154
+ if (!session) {
155
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
156
+ }
157
+
158
+ const { prompt } = await request.json()
159
+
160
+ const completion = await openai.chat.completions.create({
161
+ model: 'gpt-4',
162
+ messages: [{ role: 'user', content: prompt }],
163
+ })
164
+
165
+ return NextResponse.json({ result: completion.choices[0].message })
166
+ }
167
+ `,
168
+ language: 'typescript',
169
+ size: 500,
170
+ },
171
+ },
172
+ ],
173
+
174
+ falseNegatives: [
175
+ {
176
+ name: 'AI Endpoints - False Negatives',
177
+ expectFindings: false,
178
+ description: 'Properly protected AI endpoints that should NOT be flagged',
179
+ allowedInfoFindings: [
180
+ {
181
+ category: 'ai_endpoint_unprotected',
182
+ maxCount: 2,
183
+ reason: 'Protected endpoints may still generate info-level findings for documentation',
184
+ },
185
+ {
186
+ category: 'dangerous_function',
187
+ maxCount: 1,
188
+ reason: 'Request body parsing may trigger info-level schema validation suggestions',
189
+ },
190
+ ],
191
+ file: {
192
+ path: 'src/app/api/protected-chat/route.ts',
193
+ content: `
194
+ import { OpenAI } from 'openai'
195
+ import { NextResponse } from 'next/server'
196
+ import { getServerSession } from 'next-auth'
197
+ import { rateLimit } from '@/lib/rate-limit'
198
+
199
+ const openai = new OpenAI()
200
+ const limiter = rateLimit({ limit: 10, window: 60 })
201
+
202
+ // Has both auth AND rate limiting - SAFE
203
+ export async function POST(request: Request) {
204
+ // Auth check
205
+ const session = await getServerSession()
206
+ if (!session) {
207
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
208
+ }
209
+
210
+ // Rate limit check
211
+ const { success } = await limiter.check(session.user.id)
212
+ if (!success) {
213
+ return NextResponse.json({ error: 'Too Many Requests' }, { status: 429 })
214
+ }
215
+
216
+ const { prompt } = await request.json()
217
+
218
+ const completion = await openai.chat.completions.create({
219
+ model: 'gpt-4',
220
+ messages: [{ role: 'user', content: prompt }],
221
+ })
222
+
223
+ return NextResponse.json({ result: completion.choices[0].message })
224
+ }
225
+ `,
226
+ language: 'typescript',
227
+ size: 700,
228
+ },
229
+ },
230
+ {
231
+ name: 'BYOK Endpoint - User API Key',
232
+ expectFindings: false,
233
+ description: 'Bring Your Own Key endpoint should be downgraded',
234
+ allowedInfoFindings: [
235
+ {
236
+ category: 'ai_endpoint_unprotected',
237
+ maxCount: 1,
238
+ reason: 'BYOK endpoints have lower risk but may still generate info findings',
239
+ },
240
+ {
241
+ category: 'dangerous_function',
242
+ maxCount: 1,
243
+ reason: 'Request body parsing may trigger info-level schema validation suggestions',
244
+ },
245
+ {
246
+ category: 'missing_auth',
247
+ maxCount: 1,
248
+ reason: 'BYOK pattern detected as form of implicit auth at low severity',
249
+ },
250
+ {
251
+ category: 'ai_pattern',
252
+ maxCount: 1,
253
+ reason: 'BYOK detection at low severity when transient usage',
254
+ },
255
+ ],
256
+ file: {
257
+ path: 'src/app/api/byok-chat/route.ts',
258
+ content: `
259
+ import OpenAI from 'openai'
260
+ import { NextResponse } from 'next/server'
261
+
262
+ // BYOK endpoint - user provides their own key - LOWER RISK
263
+ export async function POST(request: Request) {
264
+ const { prompt, userApiKey } = await request.json()
265
+
266
+ if (!userApiKey) {
267
+ return NextResponse.json({ error: 'API key required' }, { status: 400 })
268
+ }
269
+
270
+ // User's own key - they pay for their own usage
271
+ const openai = new OpenAI({ apiKey: userApiKey })
272
+
273
+ const completion = await openai.chat.completions.create({
274
+ model: 'gpt-4',
275
+ messages: [{ role: 'user', content: prompt }],
276
+ })
277
+
278
+ return NextResponse.json({ result: completion.choices[0].message })
279
+ }
280
+ `,
281
+ language: 'typescript',
282
+ size: 550,
283
+ },
284
+ },
285
+ {
286
+ name: 'Internal Admin Route',
287
+ expectFindings: false,
288
+ description: 'Internal/admin routes should be downgraded',
289
+ allowedInfoFindings: [
290
+ {
291
+ category: 'ai_endpoint_unprotected',
292
+ maxCount: 1,
293
+ reason: 'Internal routes are treated as lower risk',
294
+ },
295
+ {
296
+ category: 'dangerous_function',
297
+ maxCount: 1,
298
+ reason: 'Request body parsing may trigger info-level schema validation suggestions',
299
+ },
300
+ {
301
+ category: 'missing_auth',
302
+ maxCount: 1,
303
+ reason: 'Internal secret check detected at low severity',
304
+ },
305
+ ],
306
+ file: {
307
+ path: 'src/app/api/internal/ai-admin/route.ts',
308
+ content: `
309
+ import { OpenAI } from 'openai'
310
+ import { NextResponse } from 'next/server'
311
+
312
+ const openai = new OpenAI()
313
+
314
+ // Internal route - network-level protection assumed - INFO
315
+ export async function POST(request: Request) {
316
+ // Check internal secret
317
+ const internalSecret = request.headers.get('x-internal-secret')
318
+ if (internalSecret !== process.env.INTERNAL_SECRET) {
319
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
320
+ }
321
+
322
+ const { prompt } = await request.json()
323
+
324
+ const completion = await openai.chat.completions.create({
325
+ model: 'gpt-4',
326
+ messages: [{ role: 'user', content: prompt }],
327
+ })
328
+
329
+ return NextResponse.json({ result: completion.choices[0].message })
330
+ }
331
+ `,
332
+ language: 'typescript',
333
+ size: 550,
334
+ },
335
+ },
336
+ {
337
+ name: 'Express with Middleware',
338
+ expectFindings: false,
339
+ description: 'Express route with auth and rate limit middleware',
340
+ allowedInfoFindings: [
341
+ {
342
+ category: 'ai_endpoint_unprotected',
343
+ maxCount: 1,
344
+ reason: 'Protected routes may generate info findings',
345
+ },
346
+ ],
347
+ file: {
348
+ path: 'src/routes/api/protected-generate.ts',
349
+ content: `
350
+ import express from 'express'
351
+ import { OpenAI } from 'openai'
352
+ import { authMiddleware } from '@/middleware/auth'
353
+ import { rateLimiter } from '@/middleware/rate-limit'
354
+
355
+ const router = express.Router()
356
+ const openai = new OpenAI()
357
+
358
+ // Express with proper middleware chain - SAFE
359
+ router.post('/generate', authMiddleware, rateLimiter, async (req, res) => {
360
+ const { prompt } = req.body
361
+ const userId = req.user.id
362
+
363
+ const completion = await openai.chat.completions.create({
364
+ model: 'gpt-4',
365
+ messages: [{ role: 'user', content: prompt }],
366
+ })
367
+
368
+ res.json({ result: completion.choices[0].message })
369
+ })
370
+
371
+ export default router
372
+ `,
373
+ language: 'typescript',
374
+ size: 550,
375
+ },
376
+ },
377
+ {
378
+ name: 'Fastify with Hooks',
379
+ expectFindings: false,
380
+ description: 'Fastify route with preHandler hooks for auth and rate limiting',
381
+ allowedInfoFindings: [
382
+ {
383
+ category: 'ai_endpoint_unprotected',
384
+ maxCount: 1,
385
+ reason: 'Protected routes may generate info findings',
386
+ },
387
+ ],
388
+ file: {
389
+ path: 'src/routes/api/fastify-chat.ts',
390
+ content: `
391
+ import Anthropic from '@anthropic-ai/sdk'
392
+ import { authenticate, rateLimit } from '@/hooks'
393
+
394
+ const anthropic = new Anthropic()
395
+
396
+ // Fastify with preHandler hooks - SAFE
397
+ fastify.post('/chat', {
398
+ preHandler: [authenticate, rateLimit],
399
+ handler: async (request, reply) => {
400
+ const { prompt } = request.body
401
+ const userId = request.user.id
402
+
403
+ const message = await anthropic.messages.create({
404
+ model: 'claude-3-opus-20240229',
405
+ max_tokens: 1024,
406
+ messages: [{ role: 'user', content: prompt }],
407
+ })
408
+
409
+ return { response: message.content }
410
+ }
411
+ })
412
+ `,
413
+ language: 'typescript',
414
+ size: 500,
415
+ },
416
+ },
417
+ ],
418
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * AI Execution Sinks Test Fixtures
3
+ * Tests for detecting unsafe execution of LLM-generated code
4
+ */
5
+
6
+ import type { TestGroup } from '../../types'
7
+
8
+ export const aiExecutionSinksTests: TestGroup = {
9
+ name: 'AI Execution Sinks',
10
+ tier: 'A',
11
+ layer: 2,
12
+ description: 'Detection of unsafe execution of LLM-generated code, SQL, and commands',
13
+
14
+ truePositives: [
15
+ {
16
+ name: 'AI Execution Sinks - True Positives',
17
+ expectFindings: true,
18
+ expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
19
+ description: 'AI execution sink patterns that MUST be detected',
20
+ file: {
21
+ path: 'src/api/ai/code-executor.ts',
22
+ content: `
23
+ import OpenAI from 'openai'
24
+ import { exec } from 'child_process'
25
+
26
+ const openai = new OpenAI()
27
+
28
+ // LLM output to eval() - CRITICAL
29
+ export async function executeAICode(prompt: string) {
30
+ const response = await openai.chat.completions.create({
31
+ model: 'gpt-4',
32
+ messages: [{ role: 'user', content: prompt }]
33
+ })
34
+
35
+ const code = response.choices[0].message.content
36
+ return eval(code) // CRITICAL: AI output executed directly!
37
+ }
38
+
39
+ // LLM output to Function() - CRITICAL
40
+ export async function createAIFunction(prompt: string) {
41
+ const response = await openai.chat.completions.create({
42
+ model: 'gpt-4',
43
+ messages: [{ role: 'user', content: \`Generate a JS function: \${prompt}\` }]
44
+ })
45
+
46
+ return new Function(response.choices[0].message.content)
47
+ }
48
+
49
+ // LLM output to exec() - CRITICAL (Command Injection)
50
+ export async function executeAICommand(task: string) {
51
+ const response = await openai.chat.completions.create({
52
+ model: 'gpt-4',
53
+ messages: [{ role: 'user', content: \`Generate a shell command to: \${task}\` }]
54
+ })
55
+
56
+ const command = response.choices[0].message.content
57
+ exec(command, (err, stdout) => console.log(stdout)) // CRITICAL!
58
+ }
59
+
60
+ // LLM output to SQL query - CRITICAL (SQL Injection)
61
+ export async function executeAIQuery(userQuestion: string) {
62
+ const response = await openai.chat.completions.create({
63
+ model: 'gpt-4',
64
+ messages: [{
65
+ role: 'system',
66
+ content: 'Convert natural language to SQL'
67
+ }, {
68
+ role: 'user',
69
+ content: userQuestion
70
+ }]
71
+ })
72
+
73
+ const sql = response.choices[0].message.content
74
+ return db.query(sql) // CRITICAL: AI-generated SQL executed directly!
75
+ }
76
+
77
+ // LLM output to innerHTML - HIGH (XSS)
78
+ export async function renderAIContent(prompt: string) {
79
+ const response = await openai.chat.completions.create({
80
+ model: 'gpt-4',
81
+ messages: [{ role: 'user', content: prompt }]
82
+ })
83
+
84
+ document.getElementById('content').innerHTML = response.choices[0].message.content
85
+ }
86
+
87
+ // LLM output to file system write - HIGH
88
+ export async function generateAndSaveFile(prompt: string) {
89
+ const response = await openai.chat.completions.create({
90
+ model: 'gpt-4',
91
+ messages: [{ role: 'user', content: prompt }]
92
+ })
93
+
94
+ const filename = response.choices[0].message.content.split('\\n')[0]
95
+ const content = response.choices[0].message.content.split('\\n').slice(1).join('\\n')
96
+ fs.writeFileSync(filename, content) // AI controls filename!
97
+ }
98
+ `,
99
+ language: 'typescript',
100
+ size: 2200,
101
+ },
102
+ },
103
+ ],
104
+
105
+ falseNegatives: [
106
+ {
107
+ name: 'AI Execution Sinks - False Negatives',
108
+ expectFindings: false,
109
+ description: 'Safe AI output handling patterns that should NOT be flagged',
110
+ allowedInfoFindings: [
111
+ {
112
+ category: 'dangerous_function',
113
+ maxCount: 2,
114
+ reason: 'SQL query with whitelist validation and sandboxed VM execution generate info-level findings',
115
+ },
116
+ {
117
+ category: 'suspicious_package',
118
+ maxCount: 1,
119
+ reason: 'vm2 is a security sandbox package, info-level only',
120
+ },
121
+ {
122
+ category: 'ai_pattern',
123
+ maxCount: 2,
124
+ reason: 'AI console.log debugging is info-level operational finding',
125
+ },
126
+ ],
127
+ file: {
128
+ path: 'src/api/ai/safe-executor.ts',
129
+ content: `
130
+ import OpenAI from 'openai'
131
+ import { VM } from 'vm2'
132
+
133
+ const openai = new OpenAI()
134
+
135
+ // LLM output for display only - SAFE
136
+ export async function getAIResponse(prompt: string) {
137
+ const response = await openai.chat.completions.create({
138
+ model: 'gpt-4',
139
+ messages: [{ role: 'user', content: prompt }]
140
+ })
141
+
142
+ // Only used for display, not execution
143
+ console.log(response.choices[0].message.content)
144
+ return { message: response.choices[0].message.content }
145
+ }
146
+
147
+ // LLM output with sandboxed execution - SAFE (Medium at most)
148
+ export async function executeSandboxed(prompt: string) {
149
+ const response = await openai.chat.completions.create({
150
+ model: 'gpt-4',
151
+ messages: [{ role: 'user', content: prompt }]
152
+ })
153
+
154
+ const vm = new VM({
155
+ timeout: 1000,
156
+ sandbox: {}
157
+ })
158
+ return vm.run(response.choices[0].message.content)
159
+ }
160
+
161
+ // LLM output with parameterized SQL - SAFE
162
+ export async function safeAIQuery(userQuestion: string) {
163
+ const response = await openai.chat.completions.create({
164
+ model: 'gpt-4',
165
+ messages: [{
166
+ role: 'system',
167
+ content: 'Return ONLY column names for the SELECT clause, comma-separated'
168
+ }, {
169
+ role: 'user',
170
+ content: userQuestion
171
+ }]
172
+ })
173
+
174
+ const columns = response.choices[0].message.content
175
+ // Whitelist validation
176
+ const allowedColumns = ['id', 'name', 'email', 'created_at']
177
+ const requestedColumns = columns.split(',').map(c => c.trim())
178
+ const safeColumns = requestedColumns.filter(c => allowedColumns.includes(c))
179
+
180
+ // Parameterized query with validated columns
181
+ return db.query(\`SELECT \${safeColumns.join(', ')} FROM users WHERE id = $1\`, [userId])
182
+ }
183
+ `,
184
+ language: 'typescript',
185
+ size: 1500,
186
+ },
187
+ },
188
+ ],
189
+ }