@oculum/scanner 1.0.9 → 1.0.11

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 (365) hide show
  1. package/dist/baseline/diff.d.ts +32 -0
  2. package/dist/baseline/diff.d.ts.map +1 -0
  3. package/dist/baseline/diff.js +119 -0
  4. package/dist/baseline/diff.js.map +1 -0
  5. package/dist/baseline/index.d.ts +9 -0
  6. package/dist/baseline/index.d.ts.map +1 -0
  7. package/dist/baseline/index.js +19 -0
  8. package/dist/baseline/index.js.map +1 -0
  9. package/dist/baseline/manager.d.ts +67 -0
  10. package/dist/baseline/manager.d.ts.map +1 -0
  11. package/dist/baseline/manager.js +180 -0
  12. package/dist/baseline/manager.js.map +1 -0
  13. package/dist/baseline/types.d.ts +91 -0
  14. package/dist/baseline/types.d.ts.map +1 -0
  15. package/dist/baseline/types.js +12 -0
  16. package/dist/baseline/types.js.map +1 -0
  17. package/dist/formatters/cli-terminal.d.ts +38 -0
  18. package/dist/formatters/cli-terminal.d.ts.map +1 -1
  19. package/dist/formatters/cli-terminal.js +365 -42
  20. package/dist/formatters/cli-terminal.js.map +1 -1
  21. package/dist/formatters/github-comment.d.ts +1 -1
  22. package/dist/formatters/github-comment.d.ts.map +1 -1
  23. package/dist/formatters/github-comment.js +75 -11
  24. package/dist/formatters/github-comment.js.map +1 -1
  25. package/dist/formatters/index.d.ts +1 -1
  26. package/dist/formatters/index.d.ts.map +1 -1
  27. package/dist/formatters/index.js +4 -1
  28. package/dist/formatters/index.js.map +1 -1
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +155 -16
  32. package/dist/index.js.map +1 -1
  33. package/dist/layer1/config-audit.d.ts.map +1 -1
  34. package/dist/layer1/config-audit.js +20 -3
  35. package/dist/layer1/config-audit.js.map +1 -1
  36. package/dist/layer1/config-mcp-audit.d.ts +20 -0
  37. package/dist/layer1/config-mcp-audit.d.ts.map +1 -0
  38. package/dist/layer1/config-mcp-audit.js +239 -0
  39. package/dist/layer1/config-mcp-audit.js.map +1 -0
  40. package/dist/layer1/index.d.ts +1 -0
  41. package/dist/layer1/index.d.ts.map +1 -1
  42. package/dist/layer1/index.js +9 -1
  43. package/dist/layer1/index.js.map +1 -1
  44. package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
  45. package/dist/layer2/ai-agent-tools.js +303 -0
  46. package/dist/layer2/ai-agent-tools.js.map +1 -1
  47. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
  48. package/dist/layer2/ai-endpoint-protection.js +17 -3
  49. package/dist/layer2/ai-endpoint-protection.js.map +1 -1
  50. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
  51. package/dist/layer2/ai-execution-sinks.js +462 -12
  52. package/dist/layer2/ai-execution-sinks.js.map +1 -1
  53. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
  54. package/dist/layer2/ai-fingerprinting.js +3 -0
  55. package/dist/layer2/ai-fingerprinting.js.map +1 -1
  56. package/dist/layer2/ai-mcp-security.d.ts +17 -0
  57. package/dist/layer2/ai-mcp-security.d.ts.map +1 -0
  58. package/dist/layer2/ai-mcp-security.js +679 -0
  59. package/dist/layer2/ai-mcp-security.js.map +1 -0
  60. package/dist/layer2/ai-package-hallucination.d.ts +19 -0
  61. package/dist/layer2/ai-package-hallucination.d.ts.map +1 -0
  62. package/dist/layer2/ai-package-hallucination.js +696 -0
  63. package/dist/layer2/ai-package-hallucination.js.map +1 -0
  64. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
  65. package/dist/layer2/ai-prompt-hygiene.js +495 -9
  66. package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
  67. package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
  68. package/dist/layer2/ai-rag-safety.js +372 -1
  69. package/dist/layer2/ai-rag-safety.js.map +1 -1
  70. package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
  71. package/dist/layer2/auth-antipatterns.js +4 -0
  72. package/dist/layer2/auth-antipatterns.js.map +1 -1
  73. package/dist/layer2/byok-patterns.d.ts.map +1 -1
  74. package/dist/layer2/byok-patterns.js +3 -0
  75. package/dist/layer2/byok-patterns.js.map +1 -1
  76. package/dist/layer2/dangerous-functions/child-process.d.ts +16 -0
  77. package/dist/layer2/dangerous-functions/child-process.d.ts.map +1 -0
  78. package/dist/layer2/dangerous-functions/child-process.js +74 -0
  79. package/dist/layer2/dangerous-functions/child-process.js.map +1 -0
  80. package/dist/layer2/dangerous-functions/dom-xss.d.ts +29 -0
  81. package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -0
  82. package/dist/layer2/dangerous-functions/dom-xss.js +179 -0
  83. package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -0
  84. package/dist/layer2/dangerous-functions/index.d.ts +13 -0
  85. package/dist/layer2/dangerous-functions/index.d.ts.map +1 -0
  86. package/dist/layer2/dangerous-functions/index.js +621 -0
  87. package/dist/layer2/dangerous-functions/index.js.map +1 -0
  88. package/dist/layer2/dangerous-functions/json-parse.d.ts +31 -0
  89. package/dist/layer2/dangerous-functions/json-parse.d.ts.map +1 -0
  90. package/dist/layer2/dangerous-functions/json-parse.js +319 -0
  91. package/dist/layer2/dangerous-functions/json-parse.js.map +1 -0
  92. package/dist/layer2/dangerous-functions/math-random.d.ts +61 -0
  93. package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -0
  94. package/dist/layer2/dangerous-functions/math-random.js +459 -0
  95. package/dist/layer2/dangerous-functions/math-random.js.map +1 -0
  96. package/dist/layer2/dangerous-functions/patterns.d.ts +21 -0
  97. package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -0
  98. package/dist/layer2/dangerous-functions/patterns.js +161 -0
  99. package/dist/layer2/dangerous-functions/patterns.js.map +1 -0
  100. package/dist/layer2/dangerous-functions/request-validation.d.ts +13 -0
  101. package/dist/layer2/dangerous-functions/request-validation.d.ts.map +1 -0
  102. package/dist/layer2/dangerous-functions/request-validation.js +119 -0
  103. package/dist/layer2/dangerous-functions/request-validation.js.map +1 -0
  104. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +23 -0
  105. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -0
  106. package/dist/layer2/dangerous-functions/utils/control-flow.js +149 -0
  107. package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -0
  108. package/dist/layer2/dangerous-functions/utils/helpers.d.ts +31 -0
  109. package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -0
  110. package/dist/layer2/dangerous-functions/utils/helpers.js +124 -0
  111. package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -0
  112. package/dist/layer2/dangerous-functions/utils/index.d.ts +9 -0
  113. package/dist/layer2/dangerous-functions/utils/index.d.ts.map +1 -0
  114. package/dist/layer2/dangerous-functions/utils/index.js +23 -0
  115. package/dist/layer2/dangerous-functions/utils/index.js.map +1 -0
  116. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts +22 -0
  117. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -0
  118. package/dist/layer2/dangerous-functions/utils/schema-validation.js +89 -0
  119. package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -0
  120. package/dist/layer2/data-exposure.d.ts.map +1 -1
  121. package/dist/layer2/data-exposure.js +3 -0
  122. package/dist/layer2/data-exposure.js.map +1 -1
  123. package/dist/layer2/framework-checks.d.ts.map +1 -1
  124. package/dist/layer2/framework-checks.js +3 -0
  125. package/dist/layer2/framework-checks.js.map +1 -1
  126. package/dist/layer2/index.d.ts +3 -0
  127. package/dist/layer2/index.d.ts.map +1 -1
  128. package/dist/layer2/index.js +61 -2
  129. package/dist/layer2/index.js.map +1 -1
  130. package/dist/layer2/logic-gates.d.ts.map +1 -1
  131. package/dist/layer2/logic-gates.js +4 -0
  132. package/dist/layer2/logic-gates.js.map +1 -1
  133. package/dist/layer2/model-supply-chain.d.ts +20 -0
  134. package/dist/layer2/model-supply-chain.d.ts.map +1 -0
  135. package/dist/layer2/model-supply-chain.js +376 -0
  136. package/dist/layer2/model-supply-chain.js.map +1 -0
  137. package/dist/layer2/risky-imports.d.ts.map +1 -1
  138. package/dist/layer2/risky-imports.js +4 -0
  139. package/dist/layer2/risky-imports.js.map +1 -1
  140. package/dist/layer2/variables.d.ts.map +1 -1
  141. package/dist/layer2/variables.js +4 -0
  142. package/dist/layer2/variables.js.map +1 -1
  143. package/dist/layer3/anthropic/auto-dismiss.d.ts +24 -0
  144. package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -0
  145. package/dist/layer3/anthropic/auto-dismiss.js +188 -0
  146. package/dist/layer3/anthropic/auto-dismiss.js.map +1 -0
  147. package/dist/layer3/anthropic/clients.d.ts +44 -0
  148. package/dist/layer3/anthropic/clients.d.ts.map +1 -0
  149. package/dist/layer3/anthropic/clients.js +81 -0
  150. package/dist/layer3/anthropic/clients.js.map +1 -0
  151. package/dist/layer3/anthropic/index.d.ts +41 -0
  152. package/dist/layer3/anthropic/index.d.ts.map +1 -0
  153. package/dist/layer3/anthropic/index.js +141 -0
  154. package/dist/layer3/anthropic/index.js.map +1 -0
  155. package/dist/layer3/anthropic/prompts/index.d.ts +8 -0
  156. package/dist/layer3/anthropic/prompts/index.d.ts.map +1 -0
  157. package/dist/layer3/anthropic/prompts/index.js +14 -0
  158. package/dist/layer3/anthropic/prompts/index.js.map +1 -0
  159. package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts +15 -0
  160. package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts.map +1 -0
  161. package/dist/layer3/anthropic/prompts/semantic-analysis.js +169 -0
  162. package/dist/layer3/anthropic/prompts/semantic-analysis.js.map +1 -0
  163. package/dist/layer3/anthropic/prompts/validation.d.ts +12 -0
  164. package/dist/layer3/anthropic/prompts/validation.d.ts.map +1 -0
  165. package/dist/layer3/anthropic/prompts/validation.js +421 -0
  166. package/dist/layer3/anthropic/prompts/validation.js.map +1 -0
  167. package/dist/layer3/anthropic/providers/anthropic.d.ts +21 -0
  168. package/dist/layer3/anthropic/providers/anthropic.d.ts.map +1 -0
  169. package/dist/layer3/anthropic/providers/anthropic.js +266 -0
  170. package/dist/layer3/anthropic/providers/anthropic.js.map +1 -0
  171. package/dist/layer3/anthropic/providers/index.d.ts +8 -0
  172. package/dist/layer3/anthropic/providers/index.d.ts.map +1 -0
  173. package/dist/layer3/anthropic/providers/index.js +15 -0
  174. package/dist/layer3/anthropic/providers/index.js.map +1 -0
  175. package/dist/layer3/anthropic/providers/openai.d.ts +18 -0
  176. package/dist/layer3/anthropic/providers/openai.d.ts.map +1 -0
  177. package/dist/layer3/anthropic/providers/openai.js +340 -0
  178. package/dist/layer3/anthropic/providers/openai.js.map +1 -0
  179. package/dist/layer3/anthropic/request-builder.d.ts +20 -0
  180. package/dist/layer3/anthropic/request-builder.d.ts.map +1 -0
  181. package/dist/layer3/anthropic/request-builder.js +134 -0
  182. package/dist/layer3/anthropic/request-builder.js.map +1 -0
  183. package/dist/layer3/anthropic/types.d.ts +88 -0
  184. package/dist/layer3/anthropic/types.d.ts.map +1 -0
  185. package/dist/layer3/anthropic/types.js +38 -0
  186. package/dist/layer3/anthropic/types.js.map +1 -0
  187. package/dist/layer3/anthropic/utils/index.d.ts +9 -0
  188. package/dist/layer3/anthropic/utils/index.d.ts.map +1 -0
  189. package/dist/layer3/anthropic/utils/index.js +24 -0
  190. package/dist/layer3/anthropic/utils/index.js.map +1 -0
  191. package/dist/layer3/anthropic/utils/path-helpers.d.ts +21 -0
  192. package/dist/layer3/anthropic/utils/path-helpers.d.ts.map +1 -0
  193. package/dist/layer3/anthropic/utils/path-helpers.js +69 -0
  194. package/dist/layer3/anthropic/utils/path-helpers.js.map +1 -0
  195. package/dist/layer3/anthropic/utils/response-parser.d.ts +40 -0
  196. package/dist/layer3/anthropic/utils/response-parser.d.ts.map +1 -0
  197. package/dist/layer3/anthropic/utils/response-parser.js +285 -0
  198. package/dist/layer3/anthropic/utils/response-parser.js.map +1 -0
  199. package/dist/layer3/anthropic/utils/retry.d.ts +15 -0
  200. package/dist/layer3/anthropic/utils/retry.d.ts.map +1 -0
  201. package/dist/layer3/anthropic/utils/retry.js +62 -0
  202. package/dist/layer3/anthropic/utils/retry.js.map +1 -0
  203. package/dist/layer3/index.d.ts +1 -0
  204. package/dist/layer3/index.d.ts.map +1 -1
  205. package/dist/layer3/index.js +16 -6
  206. package/dist/layer3/index.js.map +1 -1
  207. package/dist/layer3/osv-check.d.ts +75 -0
  208. package/dist/layer3/osv-check.d.ts.map +1 -0
  209. package/dist/layer3/osv-check.js +308 -0
  210. package/dist/layer3/osv-check.js.map +1 -0
  211. package/dist/rules/framework-fixes.d.ts +48 -0
  212. package/dist/rules/framework-fixes.d.ts.map +1 -0
  213. package/dist/rules/framework-fixes.js +439 -0
  214. package/dist/rules/framework-fixes.js.map +1 -0
  215. package/dist/rules/index.d.ts +8 -0
  216. package/dist/rules/index.d.ts.map +1 -0
  217. package/dist/rules/index.js +18 -0
  218. package/dist/rules/index.js.map +1 -0
  219. package/dist/rules/metadata.d.ts +43 -0
  220. package/dist/rules/metadata.d.ts.map +1 -0
  221. package/dist/rules/metadata.js +734 -0
  222. package/dist/rules/metadata.js.map +1 -0
  223. package/dist/suppression/config-loader.d.ts +74 -0
  224. package/dist/suppression/config-loader.d.ts.map +1 -0
  225. package/dist/suppression/config-loader.js +424 -0
  226. package/dist/suppression/config-loader.js.map +1 -0
  227. package/dist/suppression/hash.d.ts +48 -0
  228. package/dist/suppression/hash.d.ts.map +1 -0
  229. package/dist/suppression/hash.js +88 -0
  230. package/dist/suppression/hash.js.map +1 -0
  231. package/dist/suppression/index.d.ts +11 -0
  232. package/dist/suppression/index.d.ts.map +1 -0
  233. package/dist/suppression/index.js +39 -0
  234. package/dist/suppression/index.js.map +1 -0
  235. package/dist/suppression/inline-parser.d.ts +39 -0
  236. package/dist/suppression/inline-parser.d.ts.map +1 -0
  237. package/dist/suppression/inline-parser.js +218 -0
  238. package/dist/suppression/inline-parser.js.map +1 -0
  239. package/dist/suppression/manager.d.ts +94 -0
  240. package/dist/suppression/manager.d.ts.map +1 -0
  241. package/dist/suppression/manager.js +292 -0
  242. package/dist/suppression/manager.js.map +1 -0
  243. package/dist/suppression/types.d.ts +151 -0
  244. package/dist/suppression/types.d.ts.map +1 -0
  245. package/dist/suppression/types.js +28 -0
  246. package/dist/suppression/types.js.map +1 -0
  247. package/dist/tiers.d.ts +1 -1
  248. package/dist/tiers.d.ts.map +1 -1
  249. package/dist/tiers.js +27 -0
  250. package/dist/tiers.js.map +1 -1
  251. package/dist/types.d.ts +62 -1
  252. package/dist/types.d.ts.map +1 -1
  253. package/dist/types.js.map +1 -1
  254. package/dist/utils/context-helpers.d.ts +4 -0
  255. package/dist/utils/context-helpers.d.ts.map +1 -1
  256. package/dist/utils/context-helpers.js +13 -9
  257. package/dist/utils/context-helpers.js.map +1 -1
  258. package/package.json +4 -2
  259. package/src/__tests__/benchmark/fixtures/layer1/mcp-config-audit.json +31 -0
  260. package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +1489 -82
  261. package/src/__tests__/benchmark/fixtures/layer2/ai-mcp-security.ts +495 -0
  262. package/src/__tests__/benchmark/fixtures/layer2/ai-package-hallucination.ts +255 -0
  263. package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +300 -1
  264. package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +139 -0
  265. package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +7 -0
  266. package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +63 -0
  267. package/src/__tests__/benchmark/fixtures/layer2/excessive-agency.ts +221 -0
  268. package/src/__tests__/benchmark/fixtures/layer2/index.ts +18 -0
  269. package/src/__tests__/benchmark/fixtures/layer2/model-supply-chain.ts +204 -0
  270. package/src/__tests__/benchmark/fixtures/layer2/phase1-enhancements.ts +157 -0
  271. package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +758 -0
  272. package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +503 -0
  273. package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +321 -0
  274. package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +439 -0
  275. package/src/baseline/__tests__/diff.test.ts +261 -0
  276. package/src/baseline/__tests__/manager.test.ts +225 -0
  277. package/src/baseline/diff.ts +135 -0
  278. package/src/baseline/index.ts +29 -0
  279. package/src/baseline/manager.ts +230 -0
  280. package/src/baseline/types.ts +97 -0
  281. package/src/formatters/cli-terminal.ts +444 -41
  282. package/src/formatters/github-comment.ts +79 -11
  283. package/src/formatters/index.ts +4 -0
  284. package/src/index.ts +197 -14
  285. package/src/layer1/config-audit.ts +24 -3
  286. package/src/layer1/config-mcp-audit.ts +276 -0
  287. package/src/layer1/index.ts +16 -6
  288. package/src/layer2/ai-agent-tools.ts +336 -0
  289. package/src/layer2/ai-endpoint-protection.ts +16 -3
  290. package/src/layer2/ai-execution-sinks.ts +516 -12
  291. package/src/layer2/ai-fingerprinting.ts +5 -1
  292. package/src/layer2/ai-mcp-security.ts +730 -0
  293. package/src/layer2/ai-package-hallucination.ts +791 -0
  294. package/src/layer2/ai-prompt-hygiene.ts +547 -9
  295. package/src/layer2/ai-rag-safety.ts +382 -3
  296. package/src/layer2/auth-antipatterns.ts +5 -0
  297. package/src/layer2/byok-patterns.ts +5 -1
  298. package/src/layer2/dangerous-functions/child-process.ts +98 -0
  299. package/src/layer2/dangerous-functions/dom-xss.ts +220 -0
  300. package/src/layer2/dangerous-functions/index.ts +949 -0
  301. package/src/layer2/dangerous-functions/json-parse.ts +385 -0
  302. package/src/layer2/dangerous-functions/math-random.ts +537 -0
  303. package/src/layer2/dangerous-functions/patterns.ts +174 -0
  304. package/src/layer2/dangerous-functions/request-validation.ts +145 -0
  305. package/src/layer2/dangerous-functions/utils/control-flow.ts +162 -0
  306. package/src/layer2/dangerous-functions/utils/helpers.ts +170 -0
  307. package/src/layer2/dangerous-functions/utils/index.ts +25 -0
  308. package/src/layer2/dangerous-functions/utils/schema-validation.ts +91 -0
  309. package/src/layer2/data-exposure.ts +5 -1
  310. package/src/layer2/framework-checks.ts +5 -0
  311. package/src/layer2/index.ts +63 -1
  312. package/src/layer2/logic-gates.ts +5 -0
  313. package/src/layer2/model-supply-chain.ts +456 -0
  314. package/src/layer2/risky-imports.ts +5 -0
  315. package/src/layer2/variables.ts +5 -0
  316. package/src/layer3/__tests__/osv-check.test.ts +384 -0
  317. package/src/layer3/anthropic/auto-dismiss.ts +212 -0
  318. package/src/layer3/anthropic/clients.ts +84 -0
  319. package/src/layer3/anthropic/index.ts +170 -0
  320. package/src/layer3/anthropic/prompts/index.ts +14 -0
  321. package/src/layer3/anthropic/prompts/semantic-analysis.ts +173 -0
  322. package/src/layer3/anthropic/prompts/validation.ts +419 -0
  323. package/src/layer3/anthropic/providers/anthropic.ts +310 -0
  324. package/src/layer3/anthropic/providers/index.ts +8 -0
  325. package/src/layer3/anthropic/providers/openai.ts +384 -0
  326. package/src/layer3/anthropic/request-builder.ts +150 -0
  327. package/src/layer3/anthropic/types.ts +148 -0
  328. package/src/layer3/anthropic/utils/index.ts +26 -0
  329. package/src/layer3/anthropic/utils/path-helpers.ts +68 -0
  330. package/src/layer3/anthropic/utils/response-parser.ts +322 -0
  331. package/src/layer3/anthropic/utils/retry.ts +75 -0
  332. package/src/layer3/index.ts +18 -5
  333. package/src/layer3/osv-check.ts +420 -0
  334. package/src/rules/__tests__/framework-fixes.test.ts +689 -0
  335. package/src/rules/__tests__/metadata.test.ts +218 -0
  336. package/src/rules/framework-fixes.ts +470 -0
  337. package/src/rules/index.ts +21 -0
  338. package/src/rules/metadata.ts +831 -0
  339. package/src/suppression/__tests__/config-loader.test.ts +382 -0
  340. package/src/suppression/__tests__/hash.test.ts +166 -0
  341. package/src/suppression/__tests__/inline-parser.test.ts +212 -0
  342. package/src/suppression/__tests__/manager.test.ts +415 -0
  343. package/src/suppression/config-loader.ts +462 -0
  344. package/src/suppression/hash.ts +95 -0
  345. package/src/suppression/index.ts +51 -0
  346. package/src/suppression/inline-parser.ts +273 -0
  347. package/src/suppression/manager.ts +379 -0
  348. package/src/suppression/types.ts +174 -0
  349. package/src/tiers.ts +36 -0
  350. package/src/types.ts +90 -0
  351. package/src/utils/context-helpers.ts +13 -9
  352. package/dist/layer2/dangerous-functions.d.ts +0 -7
  353. package/dist/layer2/dangerous-functions.d.ts.map +0 -1
  354. package/dist/layer2/dangerous-functions.js +0 -1701
  355. package/dist/layer2/dangerous-functions.js.map +0 -1
  356. package/dist/layer3/anthropic.d.ts +0 -87
  357. package/dist/layer3/anthropic.d.ts.map +0 -1
  358. package/dist/layer3/anthropic.js +0 -1948
  359. package/dist/layer3/anthropic.js.map +0 -1
  360. package/dist/layer3/openai.d.ts +0 -25
  361. package/dist/layer3/openai.d.ts.map +0 -1
  362. package/dist/layer3/openai.js +0 -238
  363. package/dist/layer3/openai.js.map +0 -1
  364. package/src/layer2/dangerous-functions.ts +0 -1940
  365. package/src/layer3/anthropic.ts +0 -2257
@@ -1,6 +1,21 @@
1
1
  /**
2
2
  * AI Execution Sinks Test Fixtures
3
3
  * Tests for detecting unsafe execution of LLM-generated code
4
+ *
5
+ * OWASP Reference: LLM02 - Insecure Output Handling
6
+ * Attack Chain: Prompt Injection -> LLM generates malicious output -> Dangerous Sink -> RCE/SQLi/XSS/SSRF
7
+ *
8
+ * Test Groups:
9
+ * 1. Code Execution Sinks (eval, Function, vm)
10
+ * 2. Shell Command Sinks (exec, spawn, child_process)
11
+ * 3. SQL Injection Sinks (query, execute, raw)
12
+ * 4. DOM/XSS Sinks (innerHTML, dangerouslySetInnerHTML)
13
+ * 5. Network/SSRF Sinks (fetch, axios, HTTP clients)
14
+ * 6. Redirect Sinks (res.redirect, window.location)
15
+ * 7. Header Injection Sinks (setHeader, cookie)
16
+ * 8. File System Sinks (readFile, writeFile, path.join)
17
+ * 9. Dynamic Import Sinks (import(), require())
18
+ * 10. Python-Specific Sinks (eval, exec, pickle, subprocess)
4
19
  */
5
20
 
6
21
  import type { TestGroup } from '../../types'
@@ -10,179 +25,1571 @@ export const aiExecutionSinksTests: TestGroup = {
10
25
  tier: 'A',
11
26
  layer: 2,
12
27
  description: 'Detection of unsafe execution of LLM-generated code, SQL, and commands',
13
-
28
+
14
29
  truePositives: [
30
+ // ============================================================================
31
+ // Test Group 1: Code Execution Sinks
32
+ // ============================================================================
33
+ {
34
+ name: 'Code Execution Sinks - True Positives',
35
+ expectFindings: true,
36
+ expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
37
+ description: 'Code execution sinks that MUST be detected',
38
+ file: {
39
+ path: 'src/api/ai/code-executor.ts',
40
+ content: `
41
+ import OpenAI from 'openai'
42
+ import vm from 'vm'
43
+
44
+ const openai = new OpenAI()
45
+
46
+ // 1. eval() with LLM output - CRITICAL
47
+ export async function evalAICode(prompt: string) {
48
+ const response = await openai.chat.completions.create({
49
+ model: 'gpt-4',
50
+ messages: [{ role: 'user', content: prompt }]
51
+ })
52
+ const code = response.choices[0].message.content
53
+ return eval(code) // CRITICAL: Direct code execution
54
+ }
55
+
56
+ // 2. Function constructor - CRITICAL
57
+ export async function createAIFunction(prompt: string) {
58
+ const response = await openai.chat.completions.create({
59
+ model: 'gpt-4',
60
+ messages: [{ role: 'user', content: prompt }]
61
+ })
62
+ return new Function(response.choices[0].message.content)
63
+ }
64
+
65
+ // 3. vm.runInContext without proper sandbox - HIGH
66
+ export async function runInVMContext(prompt: string) {
67
+ const response = await openai.chat.completions.create({
68
+ model: 'gpt-4',
69
+ messages: [{ role: 'user', content: prompt }]
70
+ })
71
+ const context = vm.createContext({ console })
72
+ return vm.runInContext(response.choices[0].message.content, context)
73
+ }
74
+
75
+ // 4. setTimeout with string (indirect eval) - HIGH
76
+ export async function delayedExecution(prompt: string) {
77
+ const response = await openai.chat.completions.create({
78
+ model: 'gpt-4',
79
+ messages: [{ role: 'user', content: prompt }]
80
+ })
81
+ setTimeout(response.choices[0].message.content, 1000) // String arg = eval
82
+ }
83
+
84
+ // 5. Indirect eval via global - CRITICAL
85
+ export async function indirectEval(prompt: string) {
86
+ const response = await openai.chat.completions.create({
87
+ model: 'gpt-4',
88
+ messages: [{ role: 'user', content: prompt }]
89
+ })
90
+ globalThis.eval(response.choices[0].message.content)
91
+ }
92
+ `,
93
+ language: 'typescript',
94
+ size: 1600,
95
+ },
96
+ },
97
+
98
+ // ============================================================================
99
+ // Test Group 2: Shell Command Sinks
100
+ // ============================================================================
101
+ {
102
+ name: 'Shell Command Sinks - True Positives',
103
+ expectFindings: true,
104
+ expectedCategories: ['ai_unsafe_execution'],
105
+ description: 'Shell command sinks that MUST be detected',
106
+ file: {
107
+ path: 'src/api/ai/shell-executor.ts',
108
+ content: `
109
+ import OpenAI from 'openai'
110
+ import { exec, execSync, spawn } from 'child_process'
111
+ import execa from 'execa'
112
+
113
+ const openai = new OpenAI()
114
+
115
+ // 1. exec() with LLM command - CRITICAL
116
+ export async function executeAICommand(task: string) {
117
+ const response = await openai.chat.completions.create({
118
+ model: 'gpt-4',
119
+ messages: [{ role: 'user', content: \`Generate a shell command to: \${task}\` }]
120
+ })
121
+ const command = response.choices[0].message.content
122
+ exec(command, (err, stdout) => console.log(stdout))
123
+ }
124
+
125
+ // 2. spawn() with LLM command - CRITICAL
126
+ export async function spawnAIProcess(task: string) {
127
+ const response = await openai.chat.completions.create({
128
+ model: 'gpt-4',
129
+ messages: [{ role: 'user', content: task }]
130
+ })
131
+ const completion = response.choices[0].message
132
+ spawn(completion.content, [])
133
+ }
134
+
135
+ // 3. execSync with interpolation - CRITICAL
136
+ export async function execSyncAI(userPath: string) {
137
+ const response = await openai.chat.completions.create({
138
+ model: 'gpt-4',
139
+ messages: [{ role: 'user', content: \`Process path: \${userPath}\` }]
140
+ })
141
+ execSync(\`ls \${response.choices[0].message.content}\`)
142
+ }
143
+
144
+ // 4. child_process.exec with AI - CRITICAL
145
+ export async function childProcessExec(prompt: string) {
146
+ const response = await openai.chat.completions.create({
147
+ model: 'gpt-4',
148
+ messages: [{ role: 'user', content: prompt }]
149
+ })
150
+ const child_process = require('child_process')
151
+ child_process.exec(response.choices[0].message.content)
152
+ }
153
+
154
+ // 5. execa with LLM command - CRITICAL
155
+ export async function execaAI(task: string) {
156
+ const response = await openai.chat.completions.create({
157
+ model: 'gpt-4',
158
+ messages: [{ role: 'user', content: task }]
159
+ })
160
+ await execa(response.choices[0].message.content, [])
161
+ }
162
+ `,
163
+ language: 'typescript',
164
+ size: 1600,
165
+ },
166
+ },
167
+
168
+ // ============================================================================
169
+ // Test Group 3: SQL Injection Sinks
170
+ // ============================================================================
171
+ {
172
+ name: 'SQL Injection Sinks - True Positives',
173
+ expectFindings: true,
174
+ expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
175
+ description: 'SQL injection sinks that MUST be detected',
176
+ file: {
177
+ path: 'src/api/ai/sql-executor.ts',
178
+ content: `
179
+ import OpenAI from 'openai'
180
+ import { PrismaClient } from '@prisma/client'
181
+ import Sequelize from 'sequelize'
182
+ import knex from 'knex'
183
+
184
+ const openai = new OpenAI()
185
+ const prisma = new PrismaClient()
186
+ const sequelize = new Sequelize('database')
187
+ const db = knex({ client: 'pg' })
188
+
189
+ // 1. Raw query with LLM SQL - CRITICAL
190
+ export async function executeAIQuery(userQuestion: string) {
191
+ const response = await openai.chat.completions.create({
192
+ model: 'gpt-4',
193
+ messages: [{ role: 'system', content: 'Convert to SQL' }, { role: 'user', content: userQuestion }]
194
+ })
195
+ const sql = response.choices[0].message.content
196
+ return db.query(sql)
197
+ }
198
+
199
+ // 2. Template interpolation - CRITICAL
200
+ export async function templateSQLInjection(prompt: string) {
201
+ const response = await openai.chat.completions.create({
202
+ model: 'gpt-4',
203
+ messages: [{ role: 'user', content: prompt }]
204
+ })
205
+ return db.execute(\`SELECT * FROM \${response.choices[0].message.content}\`)
206
+ }
207
+
208
+ // 3. Prisma $queryRaw - CRITICAL
209
+ export async function prismaRawQuery(prompt: string) {
210
+ const response = await openai.chat.completions.create({
211
+ model: 'gpt-4',
212
+ messages: [{ role: 'user', content: prompt }]
213
+ })
214
+ const completion = response.choices[0].message
215
+ return prisma.$queryRaw(completion.content)
216
+ }
217
+
218
+ // 4. Sequelize literal - CRITICAL
219
+ export async function sequelizeQuery(prompt: string) {
220
+ const response = await openai.chat.completions.create({
221
+ model: 'gpt-4',
222
+ messages: [{ role: 'user', content: prompt }]
223
+ })
224
+ const generatedSql = response.choices[0].message.content
225
+ return sequelize.query(generatedSql)
226
+ }
227
+
228
+ // 5. Knex raw - CRITICAL
229
+ export async function knexRawQuery(prompt: string) {
230
+ const response = await openai.chat.completions.create({
231
+ model: 'gpt-4',
232
+ messages: [{ role: 'user', content: prompt }]
233
+ })
234
+ const aiQuery = response.choices[0].message.content
235
+ return knex.raw(aiQuery)
236
+ }
237
+ `,
238
+ language: 'typescript',
239
+ size: 1800,
240
+ },
241
+ },
242
+
243
+ // ============================================================================
244
+ // Test Group 4: DOM/XSS Sinks
245
+ // ============================================================================
246
+ {
247
+ name: 'DOM XSS Sinks - True Positives',
248
+ expectFindings: true,
249
+ expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
250
+ description: 'DOM/XSS sinks that MUST be detected',
251
+ file: {
252
+ path: 'src/components/ai/chat-renderer.tsx',
253
+ content: `
254
+ import OpenAI from 'openai'
255
+
256
+ const openai = new OpenAI()
257
+
258
+ // 1. innerHTML with LLM content - HIGH
259
+ export async function renderAIContent(prompt: string) {
260
+ const response = await openai.chat.completions.create({
261
+ model: 'gpt-4',
262
+ messages: [{ role: 'user', content: prompt }]
263
+ })
264
+ document.getElementById('content').innerHTML = response.choices[0].message.content
265
+ }
266
+
267
+ // 2. dangerouslySetInnerHTML - HIGH
268
+ export async function DangerousAIComponent({ prompt }) {
269
+ const response = await openai.chat.completions.create({
270
+ model: 'gpt-4',
271
+ messages: [{ role: 'user', content: prompt }]
272
+ })
273
+ const completion = response.choices[0].message
274
+ return <div dangerouslySetInnerHTML={{ __html: completion.content }} />
275
+ }
276
+
277
+ // 3. document.write - HIGH
278
+ export async function documentWriteAI(prompt: string) {
279
+ const response = await openai.chat.completions.create({
280
+ model: 'gpt-4',
281
+ messages: [{ role: 'user', content: prompt }]
282
+ })
283
+ document.write(response.choices[0].message.content)
284
+ }
285
+
286
+ // 4. outerHTML - HIGH
287
+ export async function outerHTMLAI(prompt: string) {
288
+ const response = await openai.chat.completions.create({
289
+ model: 'gpt-4',
290
+ messages: [{ role: 'user', content: prompt }]
291
+ })
292
+ const generated = response.choices[0].message.content
293
+ document.getElementById('container').outerHTML = generated
294
+ }
295
+
296
+ // 5. insertAdjacentHTML - HIGH
297
+ export async function insertAdjacentAI(prompt: string) {
298
+ const response = await openai.chat.completions.create({
299
+ model: 'gpt-4',
300
+ messages: [{ role: 'user', content: prompt }]
301
+ })
302
+ document.body.insertAdjacentHTML('beforeend', response.choices[0].message.content)
303
+ }
304
+ `,
305
+ language: 'typescript',
306
+ size: 1600,
307
+ },
308
+ },
309
+
310
+ // ============================================================================
311
+ // Test Group 5: Network/SSRF Sinks
312
+ // ============================================================================
313
+ {
314
+ name: 'Network SSRF Sinks - True Positives',
315
+ expectFindings: true,
316
+ expectedCategories: ['ai_unsafe_execution'],
317
+ description: 'Network/SSRF sinks that MUST be detected',
318
+ file: {
319
+ path: 'src/api/ai/network-handler.ts',
320
+ content: `
321
+ import OpenAI from 'openai'
322
+ import axios from 'axios'
323
+ import got from 'got'
324
+
325
+ const openai = new OpenAI()
326
+
327
+ // 1. fetch() with LLM URL - CRITICAL
328
+ export async function fetchAIUrl(prompt: string) {
329
+ const response = await openai.chat.completions.create({
330
+ model: 'gpt-4',
331
+ messages: [{ role: 'user', content: prompt }]
332
+ })
333
+ return fetch(response.choices[0].message.content)
334
+ }
335
+
336
+ // 2. axios with LLM URL - CRITICAL
337
+ export async function axiosAIGet(prompt: string) {
338
+ const response = await openai.chat.completions.create({
339
+ model: 'gpt-4',
340
+ messages: [{ role: 'user', content: prompt }]
341
+ })
342
+ const completion = response.choices[0].message
343
+ return axios.get(completion.content)
344
+ }
345
+
346
+ // 3. axios.post with AI URL - CRITICAL
347
+ export async function axiosAIPost(prompt: string, data: any) {
348
+ const response = await openai.chat.completions.create({
349
+ model: 'gpt-4',
350
+ messages: [{ role: 'user', content: prompt }]
351
+ })
352
+ const aiUrl = response.choices[0].message.content
353
+ return axios.post(aiUrl, data)
354
+ }
355
+
356
+ // 4. got with LLM URL - CRITICAL
357
+ export async function gotAI(prompt: string) {
358
+ const response = await openai.chat.completions.create({
359
+ model: 'gpt-4',
360
+ messages: [{ role: 'user', content: prompt }]
361
+ })
362
+ return got(response.choices[0].message.content)
363
+ }
364
+
365
+ // 5. URL in options object - CRITICAL
366
+ export async function axiosConfigAI(prompt: string) {
367
+ const response = await openai.chat.completions.create({
368
+ model: 'gpt-4',
369
+ messages: [{ role: 'user', content: prompt }]
370
+ })
371
+ const completion = response.choices[0].message
372
+ return axios({ url: completion.content, method: 'POST' })
373
+ }
374
+ `,
375
+ language: 'typescript',
376
+ size: 1500,
377
+ },
378
+ },
379
+
380
+ // ============================================================================
381
+ // Test Group 6: Redirect Sinks
382
+ // ============================================================================
383
+ {
384
+ name: 'Redirect Sinks - True Positives',
385
+ expectFindings: true,
386
+ expectedCategories: ['ai_unsafe_execution'],
387
+ description: 'Redirect sinks that MUST be detected',
388
+ file: {
389
+ path: 'src/api/ai/redirect-handler.ts',
390
+ content: `
391
+ import OpenAI from 'openai'
392
+ import { redirect } from 'next/navigation'
393
+
394
+ const openai = new OpenAI()
395
+
396
+ // 1. Server redirect with LLM URL - HIGH
397
+ export async function handleAIRedirect(req, res, prompt: string) {
398
+ const response = await openai.chat.completions.create({
399
+ model: 'gpt-4',
400
+ messages: [{ role: 'user', content: prompt }]
401
+ })
402
+ res.redirect(response.choices[0].message.content)
403
+ }
404
+
405
+ // 2. Client navigation - HIGH
406
+ export async function clientNavigateAI(prompt: string) {
407
+ const response = await openai.chat.completions.create({
408
+ model: 'gpt-4',
409
+ messages: [{ role: 'user', content: prompt }]
410
+ })
411
+ const completion = response.choices[0].message
412
+ window.location.href = completion.content
413
+ }
414
+
415
+ // 3. location.assign - HIGH
416
+ export async function locationAssignAI(prompt: string) {
417
+ const response = await openai.chat.completions.create({
418
+ model: 'gpt-4',
419
+ messages: [{ role: 'user', content: prompt }]
420
+ })
421
+ const aiUrl = response.choices[0].message.content
422
+ location.assign(aiUrl)
423
+ }
424
+
425
+ // 4. location.replace - HIGH
426
+ export async function locationReplaceAI(prompt: string) {
427
+ const response = await openai.chat.completions.create({
428
+ model: 'gpt-4',
429
+ messages: [{ role: 'user', content: prompt }]
430
+ })
431
+ const generatedUrl = response.choices[0].message.content
432
+ location.replace(generatedUrl)
433
+ }
434
+
435
+ // 5. Next.js redirect - HIGH
436
+ export async function nextRedirectAI(prompt: string) {
437
+ const response = await openai.chat.completions.create({
438
+ model: 'gpt-4',
439
+ messages: [{ role: 'user', content: prompt }]
440
+ })
441
+ redirect(response.choices[0].message.content)
442
+ }
443
+ `,
444
+ language: 'typescript',
445
+ size: 1400,
446
+ },
447
+ },
448
+
449
+ // ============================================================================
450
+ // Test Group 7: Header Injection Sinks
451
+ // ============================================================================
452
+ {
453
+ name: 'Header Injection Sinks - True Positives',
454
+ expectFindings: true,
455
+ expectedCategories: ['ai_unsafe_execution'],
456
+ description: 'Header injection sinks that MUST be detected',
457
+ file: {
458
+ path: 'src/api/ai/header-handler.ts',
459
+ content: `
460
+ import OpenAI from 'openai'
461
+
462
+ const openai = new OpenAI()
463
+
464
+ // 1. setHeader with LLM value - HIGH
465
+ export async function setAIHeader(req, res, prompt: string) {
466
+ const response = await openai.chat.completions.create({
467
+ model: 'gpt-4',
468
+ messages: [{ role: 'user', content: prompt }]
469
+ })
470
+ res.setHeader('X-Custom', response.choices[0].message.content)
471
+ }
472
+
473
+ // 2. Set-Cookie with LLM value - HIGH
474
+ export async function setCookieAI(req, res, prompt: string) {
475
+ const response = await openai.chat.completions.create({
476
+ model: 'gpt-4',
477
+ messages: [{ role: 'user', content: prompt }]
478
+ })
479
+ const completion = response.choices[0].message
480
+ res.cookie('session', completion.content)
481
+ }
482
+
483
+ // 3. Header with potential CRLF - HIGH
484
+ export async function headerCRLF(req, res, prompt: string) {
485
+ const response = await openai.chat.completions.create({
486
+ model: 'gpt-4',
487
+ messages: [{ role: 'user', content: prompt }]
488
+ })
489
+ const aiUrl = response.choices[0].message.content
490
+ res.set('Location', aiUrl) // CRLF can inject headers
491
+ }
492
+
493
+ // 4. Content-Type manipulation - HIGH
494
+ export async function contentTypeAI(req, res, prompt: string) {
495
+ const response = await openai.chat.completions.create({
496
+ model: 'gpt-4',
497
+ messages: [{ role: 'user', content: prompt }]
498
+ })
499
+ res.type(response.choices[0].message.content)
500
+ }
501
+ `,
502
+ language: 'typescript',
503
+ size: 1200,
504
+ },
505
+ },
506
+
507
+ // ============================================================================
508
+ // Test Group 8: File System Sinks
509
+ // ============================================================================
510
+ {
511
+ name: 'File System Sinks - True Positives',
512
+ expectFindings: true,
513
+ expectedCategories: ['ai_unsafe_execution'],
514
+ description: 'File system sinks that MUST be detected',
515
+ file: {
516
+ path: 'src/api/ai/file-handler.ts',
517
+ content: `
518
+ import OpenAI from 'openai'
519
+ import fs from 'fs'
520
+ import path from 'path'
521
+
522
+ const openai = new OpenAI()
523
+
524
+ // 1. Path from LLM - readFile - CRITICAL
525
+ export async function readAIFile(prompt: string) {
526
+ const response = await openai.chat.completions.create({
527
+ model: 'gpt-4',
528
+ messages: [{ role: 'user', content: prompt }]
529
+ })
530
+ return fs.readFileSync(response.choices[0].message.content, 'utf-8')
531
+ }
532
+
533
+ // 2. writeFile with LLM path - CRITICAL
534
+ export async function writeAIFile(prompt: string, data: string) {
535
+ const response = await openai.chat.completions.create({
536
+ model: 'gpt-4',
537
+ messages: [{ role: 'user', content: prompt }]
538
+ })
539
+ const completion = response.choices[0].message
540
+ fs.writeFileSync(completion.content, data)
541
+ }
542
+
543
+ // 3. Path traversal risk - path.join with LLM - HIGH
544
+ export async function pathJoinAI(prompt: string) {
545
+ const response = await openai.chat.completions.create({
546
+ model: 'gpt-4',
547
+ messages: [{ role: 'user', content: prompt }]
548
+ })
549
+ const file = path.join('/base', response.choices[0].message.content)
550
+ return fs.readFile(file, 'utf-8')
551
+ }
552
+
553
+ // 4. Unlink with LLM path - CRITICAL
554
+ export async function deleteAIFile(prompt: string) {
555
+ const response = await openai.chat.completions.create({
556
+ model: 'gpt-4',
557
+ messages: [{ role: 'user', content: prompt }]
558
+ })
559
+ const aiPath = response.choices[0].message.content
560
+ fs.unlinkSync(aiPath)
561
+ }
562
+
563
+ // 5. rmSync with LLM path - CRITICAL
564
+ export async function recursiveDeleteAI(prompt: string) {
565
+ const response = await openai.chat.completions.create({
566
+ model: 'gpt-4',
567
+ messages: [{ role: 'user', content: prompt }]
568
+ })
569
+ const generated = response.choices[0].message.content
570
+ fs.rmSync(generated, { recursive: true })
571
+ }
572
+
573
+ // 6. mkdir with LLM path - HIGH
574
+ export async function createAIDir(prompt: string) {
575
+ const response = await openai.chat.completions.create({
576
+ model: 'gpt-4',
577
+ messages: [{ role: 'user', content: prompt }]
578
+ })
579
+ fs.mkdirSync(response.choices[0].message.content)
580
+ }
581
+ `,
582
+ language: 'typescript',
583
+ size: 1800,
584
+ },
585
+ },
586
+
587
+ // ============================================================================
588
+ // Test Group 9: Dynamic Import Sinks
589
+ // ============================================================================
590
+ {
591
+ name: 'Dynamic Import Sinks - True Positives',
592
+ expectFindings: true,
593
+ expectedCategories: ['ai_unsafe_execution'],
594
+ description: 'Dynamic import sinks that MUST be detected',
595
+ file: {
596
+ path: 'src/api/ai/plugin-loader.ts',
597
+ content: `
598
+ import OpenAI from 'openai'
599
+
600
+ const openai = new OpenAI()
601
+
602
+ // 1. import() with LLM module - CRITICAL
603
+ export async function loadAIModule(prompt: string) {
604
+ const response = await openai.chat.completions.create({
605
+ model: 'gpt-4',
606
+ messages: [{ role: 'user', content: prompt }]
607
+ })
608
+ const moduleName = response.choices[0].message.content
609
+ return import(moduleName)
610
+ }
611
+
612
+ // 2. require() with LLM path - CRITICAL
613
+ export async function requireAIPlugin(prompt: string) {
614
+ const response = await openai.chat.completions.create({
615
+ model: 'gpt-4',
616
+ messages: [{ role: 'user', content: prompt }]
617
+ })
618
+ const completion = response.choices[0].message
619
+ return require(completion.content)
620
+ }
621
+
622
+ // 3. require.resolve - HIGH
623
+ export async function resolveAIModule(prompt: string) {
624
+ const response = await openai.chat.completions.create({
625
+ model: 'gpt-4',
626
+ messages: [{ role: 'user', content: prompt }]
627
+ })
628
+ const moduleName = response.choices[0].message.content
629
+ return require.resolve(moduleName)
630
+ }
631
+ `,
632
+ language: 'typescript',
633
+ size: 900,
634
+ },
635
+ },
636
+
637
+ // ============================================================================
638
+ // Test Group 10: Python-Specific Sinks
639
+ // ============================================================================
640
+ {
641
+ name: 'Python Sinks - True Positives',
642
+ expectFindings: true,
643
+ expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
644
+ description: 'Python-specific sinks that MUST be detected',
645
+ file: {
646
+ path: 'src/api/ai/python_executor.py',
647
+ content: `
648
+ import openai
649
+ import pickle
650
+ import subprocess
651
+ import os
652
+ import sqlite3
653
+
654
+ client = openai.OpenAI()
655
+
656
+ # 1. eval with LLM code - CRITICAL
657
+ def eval_ai_code(prompt: str):
658
+ response = client.chat.completions.create(
659
+ model="gpt-4",
660
+ messages=[{"role": "user", "content": prompt}]
661
+ )
662
+ code = response.choices[0].message.content
663
+ return eval(code)
664
+
665
+ # 2. exec with LLM code - CRITICAL
666
+ def exec_ai_code(prompt: str):
667
+ response = client.chat.completions.create(
668
+ model="gpt-4",
669
+ messages=[{"role": "user", "content": prompt}]
670
+ )
671
+ completion = response.choices[0].message
672
+ exec(completion.content)
673
+
674
+ # 3. pickle.loads with LLM data - CRITICAL
675
+ def unpickle_ai_data(prompt: str):
676
+ response = client.chat.completions.create(
677
+ model="gpt-4",
678
+ messages=[{"role": "user", "content": prompt}]
679
+ )
680
+ serialized = response.choices[0].message.content.encode()
681
+ return pickle.loads(serialized)
682
+
683
+ # 4. subprocess with shell=True - CRITICAL
684
+ def subprocess_ai(prompt: str):
685
+ response = client.chat.completions.create(
686
+ model="gpt-4",
687
+ messages=[{"role": "user", "content": prompt}]
688
+ )
689
+ ai_command = response.choices[0].message.content
690
+ subprocess.run(ai_command, shell=True)
691
+
692
+ # 5. os.system - CRITICAL
693
+ def os_system_ai(prompt: str):
694
+ response = client.chat.completions.create(
695
+ model="gpt-4",
696
+ messages=[{"role": "user", "content": prompt}]
697
+ )
698
+ generated_cmd = response.choices[0].message.content
699
+ os.system(generated_cmd)
700
+
701
+ # 6. SQL with f-string - CRITICAL
702
+ def sql_fstring_ai(prompt: str):
703
+ response = client.chat.completions.create(
704
+ model="gpt-4",
705
+ messages=[{"role": "user", "content": prompt}]
706
+ )
707
+ conn = sqlite3.connect("db.sqlite")
708
+ cursor = conn.cursor()
709
+ cursor.execute(f"SELECT * FROM {response.choices[0].message.content}")
710
+ `,
711
+ language: 'python',
712
+ size: 1600,
713
+ },
714
+ },
715
+ ],
716
+
717
+ falseNegatives: [
718
+ // ============================================================================
719
+ // False Negatives Group 1: Safe Code Execution
720
+ // ============================================================================
721
+ {
722
+ name: 'Safe Code Execution - False Negatives',
723
+ expectFindings: false,
724
+ description: 'Safe code execution patterns that should NOT be flagged',
725
+ allowedInfoFindings: [
726
+ {
727
+ category: 'suspicious_package',
728
+ maxCount: 2,
729
+ reason: 'vm2 and isolated-vm are security sandbox packages',
730
+ },
731
+ {
732
+ category: 'ai_pattern',
733
+ maxCount: 2,
734
+ reason: 'AI console.log debugging is info-level',
735
+ },
736
+ ],
737
+ file: {
738
+ path: 'src/api/ai/safe-code-executor.ts',
739
+ content: `
740
+ import OpenAI from 'openai'
741
+ import { VM } from 'vm2'
742
+ import ivm from 'isolated-vm'
743
+ import * as acorn from 'acorn'
744
+
745
+ const openai = new OpenAI()
746
+
747
+ // 1. Sandboxed execution with vm2 - SAFE
748
+ export async function executeSandboxed(prompt: string) {
749
+ const response = await openai.chat.completions.create({
750
+ model: 'gpt-4',
751
+ messages: [{ role: 'user', content: prompt }]
752
+ })
753
+
754
+ const vm = new VM({
755
+ timeout: 1000,
756
+ sandbox: {}
757
+ })
758
+ return vm.run(response.choices[0].message.content)
759
+ }
760
+
761
+ // 2. Sandboxed execution with isolated-vm - SAFE
762
+ export async function executeIsolated(prompt: string) {
763
+ const response = await openai.chat.completions.create({
764
+ model: 'gpt-4',
765
+ messages: [{ role: 'user', content: prompt }]
766
+ })
767
+
768
+ const isolate = new ivm.Isolate({ memoryLimit: 128 })
769
+ const context = await isolate.createContext()
770
+ const script = await isolate.compileScript(response.choices[0].message.content)
771
+ return script.run(context)
772
+ }
773
+
774
+ // 3. AST-parsed code (safe transformation) - SAFE
775
+ export async function parseCode(prompt: string) {
776
+ const response = await openai.chat.completions.create({
777
+ model: 'gpt-4',
778
+ messages: [{ role: 'user', content: prompt }]
779
+ })
780
+
781
+ // Only parsing the AST, not executing
782
+ const ast = acorn.parse(response.choices[0].message.content, { ecmaVersion: 2020 })
783
+ return analyzeAST(ast)
784
+ }
785
+
786
+ // 4. Static strings that contain 'eval' - SAFE
787
+ export function getDocumentation() {
788
+ const docs = "Don't use eval() in production code"
789
+ const warning = "The eval function is dangerous"
790
+ return { docs, warning }
791
+ }
792
+
793
+ // 5. Display only - SAFE
794
+ export async function displayAICode(prompt: string) {
795
+ const response = await openai.chat.completions.create({
796
+ model: 'gpt-4',
797
+ messages: [{ role: 'user', content: prompt }]
798
+ })
799
+
800
+ console.log('Generated code:', response.choices[0].message.content)
801
+ return { code: response.choices[0].message.content }
802
+ }
803
+
804
+ function analyzeAST(ast: any) {
805
+ return { type: ast.type, bodyLength: ast.body?.length }
806
+ }
807
+ `,
808
+ language: 'typescript',
809
+ size: 1800,
810
+ },
811
+ },
812
+
813
+ // ============================================================================
814
+ // False Negatives Group 2: Safe Shell Execution
815
+ // ============================================================================
816
+ {
817
+ name: 'Safe Shell Execution - False Negatives',
818
+ expectFindings: false,
819
+ description: 'Safe shell execution patterns that should NOT be flagged',
820
+ allowedInfoFindings: [
821
+ {
822
+ category: 'ai_pattern',
823
+ maxCount: 1,
824
+ reason: 'AI context visible but execution is safe',
825
+ },
826
+ ],
827
+ file: {
828
+ path: 'src/api/ai/safe-shell-executor.ts',
829
+ content: `
830
+ import OpenAI from 'openai'
831
+ import { execFile } from 'child_process'
832
+
833
+ const openai = new OpenAI()
834
+
835
+ // 1. Hardcoded command with AI args (allowlisted) - SAFE
836
+ export async function safeExecFile(prompt: string) {
837
+ const response = await openai.chat.completions.create({
838
+ model: 'gpt-4',
839
+ messages: [{ role: 'user', content: prompt }]
840
+ })
841
+
842
+ const allowedArgs = ['--verbose', '--dry-run', '--help']
843
+ const aiArg = response.choices[0].message.content.trim()
844
+
845
+ if (allowedArgs.includes(aiArg)) {
846
+ execFile('git', ['status', aiArg], (err, stdout) => console.log(stdout))
847
+ }
848
+ }
849
+
850
+ // 2. Command palette display (not execution) - SAFE
851
+ export async function getCommandSuggestions(prompt: string) {
852
+ const response = await openai.chat.completions.create({
853
+ model: 'gpt-4',
854
+ messages: [{ role: 'user', content: prompt }]
855
+ })
856
+
857
+ const suggestions = response.choices[0].message.content.split('\\n')
858
+ return suggestions.map(cmd => ({
859
+ id: \`run-\${cmd}\`,
860
+ label: cmd,
861
+ description: 'Suggested command'
862
+ }))
863
+ }
864
+
865
+ // 3. Sanitized command argument - SAFE
866
+ export async function sanitizedExec(prompt: string) {
867
+ const response = await openai.chat.completions.create({
868
+ model: 'gpt-4',
869
+ messages: [{ role: 'user', content: prompt }]
870
+ })
871
+
872
+ // Strict sanitization - only alphanumeric
873
+ const sanitized = response.choices[0].message.content.replace(/[^a-z0-9]/gi, '')
874
+ execFile('echo', [sanitized], (err, stdout) => console.log(stdout))
875
+ }
876
+ `,
877
+ language: 'typescript',
878
+ size: 1300,
879
+ },
880
+ },
881
+
882
+ // ============================================================================
883
+ // False Negatives Group 3: Safe SQL Queries
884
+ // ============================================================================
885
+ {
886
+ name: 'Safe SQL Queries - False Negatives',
887
+ expectFindings: false,
888
+ description: 'Safe SQL query patterns that should NOT be flagged',
889
+ allowedInfoFindings: [
890
+ {
891
+ category: 'dangerous_function',
892
+ maxCount: 1,
893
+ reason: 'Parameterized query with whitelist is safe',
894
+ },
895
+ {
896
+ category: 'ai_pattern',
897
+ maxCount: 1,
898
+ reason: 'AI context visible but query is parameterized',
899
+ },
900
+ ],
901
+ file: {
902
+ path: 'src/api/ai/safe-sql-executor.ts',
903
+ content: `
904
+ import OpenAI from 'openai'
905
+ import { PrismaClient } from '@prisma/client'
906
+
907
+ const openai = new OpenAI()
908
+ const prisma = new PrismaClient()
909
+
910
+ // 1. Parameterized query - SAFE
911
+ export async function safeParameterized(userId: string) {
912
+ // AI not involved in query construction
913
+ const result = await prisma.$queryRaw\`SELECT * FROM users WHERE id = \${userId}\`
914
+ return result
915
+ }
916
+
917
+ // 2. Allowlisted columns with AI - SAFE
918
+ export async function safeAIColumns(prompt: string) {
919
+ const response = await openai.chat.completions.create({
920
+ model: 'gpt-4',
921
+ messages: [{ role: 'user', content: prompt }]
922
+ })
923
+
924
+ const allowedColumns = ['id', 'name', 'email', 'created_at']
925
+ const requestedColumns = response.choices[0].message.content.split(',').map(c => c.trim())
926
+ const safeColumns = requestedColumns.filter(c => allowedColumns.includes(c))
927
+
928
+ if (safeColumns.length === 0) {
929
+ throw new Error('No valid columns requested')
930
+ }
931
+
932
+ // Using validated columns with parameterized WHERE
933
+ return db.query(\`SELECT \${safeColumns.join(', ')} FROM users WHERE id = $1\`, [userId])
934
+ }
935
+
936
+ // 3. ORM methods (not raw SQL) - SAFE
937
+ export async function safeORMQuery(prompt: string) {
938
+ const response = await openai.chat.completions.create({
939
+ model: 'gpt-4',
940
+ messages: [{ role: 'user', content: prompt }]
941
+ })
942
+
943
+ // ORM handles escaping
944
+ const aiName = response.choices[0].message.content
945
+ return prisma.user.findMany({
946
+ where: { name: aiName }
947
+ })
948
+ }
949
+
950
+ // 4. Schema-validated AI output - SAFE
951
+ export async function schemaValidatedQuery(prompt: string) {
952
+ const response = await openai.chat.completions.create({
953
+ model: 'gpt-4',
954
+ messages: [{ role: 'user', content: prompt }]
955
+ })
956
+
957
+ // Validate structure before use
958
+ const schema = z.object({
959
+ table: z.enum(['users', 'posts', 'comments']),
960
+ column: z.enum(['id', 'name', 'email']),
961
+ value: z.string().max(100)
962
+ })
963
+
964
+ const parsed = schema.parse(JSON.parse(response.choices[0].message.content))
965
+ return db.query(\`SELECT * FROM \${parsed.table} WHERE \${parsed.column} = $1\`, [parsed.value])
966
+ }
967
+ `,
968
+ language: 'typescript',
969
+ size: 1700,
970
+ },
971
+ },
972
+
973
+ // ============================================================================
974
+ // False Negatives Group 4: Safe DOM/XSS Handling
975
+ // ============================================================================
15
976
  {
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',
977
+ name: 'Safe DOM XSS Handling - False Negatives',
978
+ expectFindings: false,
979
+ description: 'Safe DOM/XSS handling patterns that should NOT be flagged',
980
+ allowedInfoFindings: [
981
+ {
982
+ category: 'ai_pattern',
983
+ maxCount: 2,
984
+ reason: 'AI context visible but output is sanitized',
985
+ },
986
+ ],
20
987
  file: {
21
- path: 'src/api/ai/code-executor.ts',
988
+ path: 'src/components/ai/safe-chat-renderer.tsx',
22
989
  content: `
23
990
  import OpenAI from 'openai'
24
- import { exec } from 'child_process'
991
+ import DOMPurify from 'dompurify'
992
+ import ReactMarkdown from 'react-markdown'
25
993
 
26
994
  const openai = new OpenAI()
27
995
 
28
- // LLM output to eval() - CRITICAL
29
- export async function executeAICode(prompt: string) {
996
+ // 1. DOMPurify sanitization - SAFE
997
+ export async function sanitizedInnerHTML(prompt: string) {
30
998
  const response = await openai.chat.completions.create({
31
999
  model: 'gpt-4',
32
1000
  messages: [{ role: 'user', content: prompt }]
33
1001
  })
34
1002
 
35
- const code = response.choices[0].message.content
36
- return eval(code) // CRITICAL: AI output executed directly!
1003
+ const sanitized = DOMPurify.sanitize(response.choices[0].message.content)
1004
+ document.getElementById('content').innerHTML = sanitized
37
1005
  }
38
1006
 
39
- // LLM output to Function() - CRITICAL
40
- export async function createAIFunction(prompt: string) {
1007
+ // 2. textContent (safe) - SAFE
1008
+ export async function safeTextContent(prompt: string) {
41
1009
  const response = await openai.chat.completions.create({
42
1010
  model: 'gpt-4',
43
- messages: [{ role: 'user', content: \`Generate a JS function: \${prompt}\` }]
1011
+ messages: [{ role: 'user', content: prompt }]
44
1012
  })
45
1013
 
46
- return new Function(response.choices[0].message.content)
1014
+ document.getElementById('content').textContent = response.choices[0].message.content
47
1015
  }
48
1016
 
49
- // LLM output to exec() - CRITICAL (Command Injection)
50
- export async function executeAICommand(task: string) {
1017
+ // 3. React auto-escaping - SAFE
1018
+ export async function SafeReactComponent({ prompt }) {
51
1019
  const response = await openai.chat.completions.create({
52
1020
  model: 'gpt-4',
53
- messages: [{ role: 'user', content: \`Generate a shell command to: \${task}\` }]
1021
+ messages: [{ role: 'user', content: prompt }]
54
1022
  })
55
1023
 
56
- const command = response.choices[0].message.content
57
- exec(command, (err, stdout) => console.log(stdout)) // CRITICAL!
1024
+ // React auto-escapes this
1025
+ return <div>{response.choices[0].message.content}</div>
58
1026
  }
59
1027
 
60
- // LLM output to SQL query - CRITICAL (SQL Injection)
61
- export async function executeAIQuery(userQuestion: string) {
1028
+ // 4. Markdown renderer with sanitization - SAFE
1029
+ export async function SafeMarkdownComponent({ prompt }) {
62
1030
  const response = await openai.chat.completions.create({
63
1031
  model: 'gpt-4',
64
- messages: [{
65
- role: 'system',
66
- content: 'Convert natural language to SQL'
67
- }, {
68
- role: 'user',
69
- content: userQuestion
70
- }]
1032
+ messages: [{ role: 'user', content: prompt }]
71
1033
  })
72
1034
 
73
- const sql = response.choices[0].message.content
74
- return db.query(sql) // CRITICAL: AI-generated SQL executed directly!
1035
+ return <ReactMarkdown>{response.choices[0].message.content}</ReactMarkdown>
75
1036
  }
76
1037
 
77
- // LLM output to innerHTML - HIGH (XSS)
78
- export async function renderAIContent(prompt: string) {
1038
+ // 5. Sanitized dangerouslySetInnerHTML - SAFE
1039
+ export async function SafeDangerousComponent({ prompt }) {
79
1040
  const response = await openai.chat.completions.create({
80
1041
  model: 'gpt-4',
81
1042
  messages: [{ role: 'user', content: prompt }]
82
1043
  })
83
1044
 
84
- document.getElementById('content').innerHTML = response.choices[0].message.content
1045
+ return (
1046
+ <div
1047
+ dangerouslySetInnerHTML={{
1048
+ __html: DOMPurify.sanitize(response.choices[0].message.content)
1049
+ }}
1050
+ />
1051
+ )
1052
+ }
1053
+ `,
1054
+ language: 'typescript',
1055
+ size: 1700,
1056
+ },
1057
+ },
1058
+
1059
+ // ============================================================================
1060
+ // False Negatives Group 5: Safe Network Requests
1061
+ // ============================================================================
1062
+ {
1063
+ name: 'Safe Network Requests - False Negatives',
1064
+ expectFindings: false,
1065
+ description: 'Safe network request patterns that should NOT be flagged',
1066
+ allowedInfoFindings: [
1067
+ {
1068
+ category: 'ai_pattern',
1069
+ maxCount: 1,
1070
+ reason: 'AI context visible but URL is validated',
1071
+ },
1072
+ ],
1073
+ file: {
1074
+ path: 'src/api/ai/safe-network-handler.ts',
1075
+ content: `
1076
+ import OpenAI from 'openai'
1077
+
1078
+ const openai = new OpenAI()
1079
+
1080
+ // 1. URL validation with allowlist - SAFE
1081
+ export async function validatedFetch(prompt: string) {
1082
+ const response = await openai.chat.completions.create({
1083
+ model: 'gpt-4',
1084
+ messages: [{ role: 'user', content: prompt }]
1085
+ })
1086
+
1087
+ const aiUrl = response.choices[0].message.content
1088
+ const allowedHosts = ['api.example.com', 'cdn.example.com']
1089
+
1090
+ if (allowedHosts.includes(new URL(aiUrl).host)) {
1091
+ return fetch(aiUrl)
1092
+ }
1093
+ throw new Error('Invalid host')
1094
+ }
1095
+
1096
+ // 2. Hardcoded URL with AI parameters - SAFE
1097
+ export async function safeApiCall(prompt: string) {
1098
+ const response = await openai.chat.completions.create({
1099
+ model: 'gpt-4',
1100
+ messages: [{ role: 'user', content: prompt }]
1101
+ })
1102
+
1103
+ const query = encodeURIComponent(response.choices[0].message.content)
1104
+ return fetch(\`https://api.example.com/search?q=\${query}\`)
1105
+ }
1106
+
1107
+ // 3. Internal API with validated path - SAFE
1108
+ export async function validatedInternalAPI(prompt: string) {
1109
+ const response = await openai.chat.completions.create({
1110
+ model: 'gpt-4',
1111
+ messages: [{ role: 'user', content: prompt }]
1112
+ })
1113
+
1114
+ const aiPath = response.choices[0].message.content
1115
+ // Only allow alphanumeric paths
1116
+ const safePath = aiPath.replace(/[^a-z0-9]/gi, '')
1117
+ return fetch(\`/api/internal/\${safePath}\`)
85
1118
  }
86
1119
 
87
- // LLM output to file system write - HIGH
88
- export async function generateAndSaveFile(prompt: string) {
1120
+ // 4. Blocked private IPs - SAFE
1121
+ export async function blockedPrivateIPs(prompt: string) {
89
1122
  const response = await openai.chat.completions.create({
90
1123
  model: 'gpt-4',
91
1124
  messages: [{ role: 'user', content: prompt }]
92
1125
  })
93
1126
 
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!
1127
+ const aiUrl = response.choices[0].message.content
1128
+ const url = new URL(aiUrl)
1129
+ const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0']
1130
+ const privateIpPatterns = [/^10\\./, /^172\\.(1[6-9]|2[0-9]|3[0-1])\\./, /^192\\.168\\./]
1131
+
1132
+ if (blockedHosts.includes(url.hostname) ||
1133
+ privateIpPatterns.some(p => p.test(url.hostname))) {
1134
+ throw new Error('Private IP blocked')
1135
+ }
1136
+ return fetch(aiUrl)
97
1137
  }
98
1138
  `,
99
1139
  language: 'typescript',
100
- size: 2200,
1140
+ size: 1800,
101
1141
  },
102
1142
  },
103
- ],
104
-
105
- falseNegatives: [
1143
+
1144
+ // ============================================================================
1145
+ // False Negatives Group 6: Safe Redirects
1146
+ // ============================================================================
106
1147
  {
107
- name: 'AI Execution Sinks - False Negatives',
1148
+ name: 'Safe Redirects - False Negatives',
108
1149
  expectFindings: false,
109
- description: 'Safe AI output handling patterns that should NOT be flagged',
1150
+ description: 'Safe redirect patterns that should NOT be flagged',
110
1151
  allowedInfoFindings: [
111
1152
  {
112
- category: 'dangerous_function',
113
- maxCount: 2,
114
- reason: 'SQL query with whitelist validation and sandboxed VM execution generate info-level findings',
1153
+ category: 'ai_pattern',
1154
+ maxCount: 1,
1155
+ reason: 'AI context visible but redirect is validated',
115
1156
  },
1157
+ ],
1158
+ file: {
1159
+ path: 'src/api/ai/safe-redirect-handler.ts',
1160
+ content: `
1161
+ import OpenAI from 'openai'
1162
+
1163
+ const openai = new OpenAI()
1164
+
1165
+ // 1. Relative URL validation - SAFE
1166
+ export async function safeRelativeRedirect(req, res, prompt: string) {
1167
+ const response = await openai.chat.completions.create({
1168
+ model: 'gpt-4',
1169
+ messages: [{ role: 'user', content: prompt }]
1170
+ })
1171
+
1172
+ const aiUrl = response.choices[0].message.content
1173
+ if (aiUrl.startsWith('/') && !aiUrl.startsWith('//')) {
1174
+ res.redirect(aiUrl)
1175
+ } else {
1176
+ res.status(400).send('Invalid redirect URL')
1177
+ }
1178
+ }
1179
+
1180
+ // 2. Same-origin check - SAFE
1181
+ export async function sameOriginRedirect(prompt: string) {
1182
+ const response = await openai.chat.completions.create({
1183
+ model: 'gpt-4',
1184
+ messages: [{ role: 'user', content: prompt }]
1185
+ })
1186
+
1187
+ const aiUrl = response.choices[0].message.content
1188
+ const origin = window.location.origin
1189
+ const url = new URL(aiUrl, origin)
1190
+
1191
+ if (url.origin === origin) {
1192
+ location.href = aiUrl
1193
+ }
1194
+ }
1195
+
1196
+ // 3. Allowlisted domains - SAFE
1197
+ export async function allowlistedRedirect(req, res, prompt: string) {
1198
+ const response = await openai.chat.completions.create({
1199
+ model: 'gpt-4',
1200
+ messages: [{ role: 'user', content: prompt }]
1201
+ })
1202
+
1203
+ const aiUrl = response.choices[0].message.content
1204
+ const safeDomains = ['example.com', 'trusted.org', 'partner.net']
1205
+
1206
+ try {
1207
+ const url = new URL(aiUrl)
1208
+ if (safeDomains.includes(url.host)) {
1209
+ res.redirect(aiUrl)
1210
+ } else {
1211
+ res.status(400).send('Redirect blocked')
1212
+ }
1213
+ } catch {
1214
+ res.status(400).send('Invalid URL')
1215
+ }
1216
+ }
1217
+ `,
1218
+ language: 'typescript',
1219
+ size: 1400,
1220
+ },
1221
+ },
1222
+
1223
+ // ============================================================================
1224
+ // False Negatives Group 7: Safe Header Setting
1225
+ // ============================================================================
1226
+ {
1227
+ name: 'Safe Header Setting - False Negatives',
1228
+ expectFindings: false,
1229
+ description: 'Safe header setting patterns that should NOT be flagged',
1230
+ allowedInfoFindings: [
116
1231
  {
117
- category: 'suspicious_package',
1232
+ category: 'ai_pattern',
118
1233
  maxCount: 1,
119
- reason: 'vm2 is a security sandbox package, info-level only',
1234
+ reason: 'AI context visible but headers are sanitized',
120
1235
  },
1236
+ ],
1237
+ file: {
1238
+ path: 'src/api/ai/safe-header-handler.ts',
1239
+ content: `
1240
+ import OpenAI from 'openai'
1241
+ import crypto from 'crypto'
1242
+
1243
+ const openai = new OpenAI()
1244
+
1245
+ // 1. Sanitized header value - SAFE
1246
+ export async function sanitizedHeader(req, res, prompt: string) {
1247
+ const response = await openai.chat.completions.create({
1248
+ model: 'gpt-4',
1249
+ messages: [{ role: 'user', content: prompt }]
1250
+ })
1251
+
1252
+ // Remove CRLF injection characters
1253
+ const safeValue = response.choices[0].message.content.replace(/[\\r\\n]/g, '')
1254
+ res.setHeader('X-Custom', safeValue)
1255
+ }
1256
+
1257
+ // 2. Allowlisted content types - SAFE
1258
+ export async function allowlistedContentType(req, res, prompt: string) {
1259
+ const response = await openai.chat.completions.create({
1260
+ model: 'gpt-4',
1261
+ messages: [{ role: 'user', content: prompt }]
1262
+ })
1263
+
1264
+ const allowedTypes = ['json', 'html', 'text', 'xml']
1265
+ const aiType = response.choices[0].message.content.trim().toLowerCase()
1266
+
1267
+ if (allowedTypes.includes(aiType)) {
1268
+ res.type(aiType)
1269
+ } else {
1270
+ res.type('text') // Default fallback
1271
+ }
1272
+ }
1273
+
1274
+ // 3. Server-generated tokens - SAFE
1275
+ export async function serverGeneratedCookie(req, res) {
1276
+ // Cookie value NOT from AI - generated server-side
1277
+ const sessionToken = crypto.randomUUID()
1278
+ res.cookie('session', sessionToken, {
1279
+ httpOnly: true,
1280
+ secure: true,
1281
+ sameSite: 'strict'
1282
+ })
1283
+ }
1284
+
1285
+ // 4. Validated header name and value - SAFE
1286
+ export async function validatedHeader(req, res, prompt: string) {
1287
+ const response = await openai.chat.completions.create({
1288
+ model: 'gpt-4',
1289
+ messages: [{ role: 'user', content: prompt }]
1290
+ })
1291
+
1292
+ const aiValue = response.choices[0].message.content
1293
+
1294
+ // Validate: only alphanumeric and safe characters
1295
+ if (/^[a-zA-Z0-9\\-_. ]+$/.test(aiValue) && aiValue.length <= 256) {
1296
+ res.setHeader('X-AI-Response', aiValue)
1297
+ }
1298
+ }
1299
+ `,
1300
+ language: 'typescript',
1301
+ size: 1600,
1302
+ },
1303
+ },
1304
+
1305
+ // ============================================================================
1306
+ // False Negatives Group 8: Safe File Operations
1307
+ // ============================================================================
1308
+ {
1309
+ name: 'Safe File Operations - False Negatives',
1310
+ expectFindings: false,
1311
+ description: 'Safe file operation patterns that should NOT be flagged',
1312
+ allowedInfoFindings: [
121
1313
  {
122
1314
  category: 'ai_pattern',
123
- maxCount: 2,
124
- reason: 'AI console.log debugging is info-level operational finding',
1315
+ maxCount: 1,
1316
+ reason: 'AI context visible but paths are validated',
125
1317
  },
126
1318
  ],
127
1319
  file: {
128
- path: 'src/api/ai/safe-executor.ts',
1320
+ path: 'src/api/ai/safe-file-handler.ts',
129
1321
  content: `
130
1322
  import OpenAI from 'openai'
131
- import { VM } from 'vm2'
1323
+ import fs from 'fs'
1324
+ import path from 'path'
132
1325
 
133
1326
  const openai = new OpenAI()
1327
+ const SAFE_BASE_DIR = '/app/data/user-files'
134
1328
 
135
- // LLM output for display only - SAFE
136
- export async function getAIResponse(prompt: string) {
1329
+ // 1. Path validation - SAFE
1330
+ export async function validatedFilePath(prompt: string) {
137
1331
  const response = await openai.chat.completions.create({
138
1332
  model: 'gpt-4',
139
1333
  messages: [{ role: 'user', content: prompt }]
140
1334
  })
141
1335
 
142
- // Only used for display, not execution
143
- console.log(response.choices[0].message.content)
144
- return { message: response.choices[0].message.content }
1336
+ const aiPath = response.choices[0].message.content
1337
+ const resolved = path.resolve(SAFE_BASE_DIR, aiPath)
1338
+
1339
+ // Validate path is within safe directory
1340
+ if (!resolved.startsWith(SAFE_BASE_DIR)) {
1341
+ throw new Error('Invalid path - directory traversal attempt')
1342
+ }
1343
+
1344
+ return fs.readFileSync(resolved, 'utf-8')
145
1345
  }
146
1346
 
147
- // LLM output with sandboxed execution - SAFE (Medium at most)
148
- export async function executeSandboxed(prompt: string) {
1347
+ // 2. Allowlisted extensions - SAFE
1348
+ export async function allowlistedExtensions(prompt: string, data: string) {
149
1349
  const response = await openai.chat.completions.create({
150
1350
  model: 'gpt-4',
151
1351
  messages: [{ role: 'user', content: prompt }]
152
1352
  })
153
1353
 
154
- const vm = new VM({
155
- timeout: 1000,
156
- sandbox: {}
1354
+ const aiFile = response.choices[0].message.content
1355
+ const allowedExtensions = ['.txt', '.md', '.json', '.csv']
1356
+ const ext = path.extname(aiFile).toLowerCase()
1357
+
1358
+ if (allowedExtensions.includes(ext)) {
1359
+ const safePath = path.join(SAFE_BASE_DIR, path.basename(aiFile))
1360
+ fs.writeFileSync(safePath, data)
1361
+ }
1362
+ }
1363
+
1364
+ // 3. Internal file operations (not AI-controlled) - SAFE
1365
+ export async function internalFileOps(prompt: string) {
1366
+ const response = await openai.chat.completions.create({
1367
+ model: 'gpt-4',
1368
+ messages: [{ role: 'user', content: prompt }]
157
1369
  })
158
- return vm.run(response.choices[0].message.content)
1370
+
1371
+ // File path is hardcoded, AI only provides content
1372
+ const filePath = '/app/logs/ai-responses.log'
1373
+ fs.appendFileSync(filePath, response.choices[0].message.content + '\\n')
159
1374
  }
160
1375
 
161
- // LLM output with parameterized SQL - SAFE
162
- export async function safeAIQuery(userQuestion: string) {
1376
+ // 4. Sanitized filename - SAFE
1377
+ export async function sanitizedFilename(prompt: string, data: string) {
163
1378
  const response = await openai.chat.completions.create({
164
1379
  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
- }]
1380
+ messages: [{ role: 'user', content: prompt }]
172
1381
  })
173
1382
 
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))
1383
+ // Remove all path traversal characters
1384
+ const aiFile = response.choices[0].message.content
1385
+ const sanitized = aiFile.replace(/[\\\\/\\x00-\\x1f]/g, '').replace(/\\.\\./g, '')
179
1386
 
180
- // Parameterized query with validated columns
181
- return db.query(\`SELECT \${safeColumns.join(', ')} FROM users WHERE id = $1\`, [userId])
1387
+ if (sanitized.length > 0 && sanitized.length <= 255) {
1388
+ const safePath = path.join(SAFE_BASE_DIR, sanitized)
1389
+ fs.writeFileSync(safePath, data)
1390
+ }
182
1391
  }
183
1392
  `,
184
1393
  language: 'typescript',
185
- size: 1500,
1394
+ size: 1900,
1395
+ },
1396
+ },
1397
+
1398
+ // ============================================================================
1399
+ // False Negatives Group 9: Safe Dynamic Imports
1400
+ // ============================================================================
1401
+ {
1402
+ name: 'Safe Dynamic Imports - False Negatives',
1403
+ expectFindings: false,
1404
+ description: 'Safe dynamic import patterns that should NOT be flagged',
1405
+ allowedInfoFindings: [
1406
+ {
1407
+ category: 'ai_pattern',
1408
+ maxCount: 1,
1409
+ reason: 'AI context visible but imports are allowlisted',
1410
+ },
1411
+ ],
1412
+ file: {
1413
+ path: 'src/api/ai/safe-plugin-loader.ts',
1414
+ content: `
1415
+ import OpenAI from 'openai'
1416
+
1417
+ const openai = new OpenAI()
1418
+
1419
+ // Plugin registry with allowlisted modules
1420
+ const ALLOWED_PLUGINS = {
1421
+ 'lodash': () => import('lodash'),
1422
+ 'moment': () => import('moment'),
1423
+ 'date-fns': () => import('date-fns'),
1424
+ 'uuid': () => import('uuid'),
1425
+ }
1426
+
1427
+ // 1. Allowlisted modules - SAFE
1428
+ export async function loadAllowlistedPlugin(prompt: string) {
1429
+ const response = await openai.chat.completions.create({
1430
+ model: 'gpt-4',
1431
+ messages: [{ role: 'user', content: prompt }]
1432
+ })
1433
+
1434
+ const aiModule = response.choices[0].message.content.trim()
1435
+ const loader = ALLOWED_PLUGINS[aiModule]
1436
+
1437
+ if (loader) {
1438
+ return loader()
1439
+ }
1440
+ throw new Error(\`Plugin "\${aiModule}" not in allowlist\`)
1441
+ }
1442
+
1443
+ // 2. Hardcoded with AI config - SAFE
1444
+ export async function configuredPlugin(prompt: string) {
1445
+ const response = await openai.chat.completions.create({
1446
+ model: 'gpt-4',
1447
+ messages: [{ role: 'user', content: prompt }]
1448
+ })
1449
+
1450
+ // AI only selects from predefined options
1451
+ const pluginName = response.choices[0].message.content.trim()
1452
+
1453
+ // Sanitize - only alphanumeric
1454
+ const sanitized = pluginName.replace(/[^a-z0-9-]/gi, '')
1455
+
1456
+ // Import from known safe directory
1457
+ const mod = await import(\`./plugins/\${sanitized}\`)
1458
+ return mod.default
1459
+ }
1460
+
1461
+ // 3. Import map resolution - SAFE
1462
+ export async function importMapPlugin(prompt: string) {
1463
+ const response = await openai.chat.completions.create({
1464
+ model: 'gpt-4',
1465
+ messages: [{ role: 'user', content: prompt }]
1466
+ })
1467
+
1468
+ const importMap = {
1469
+ 'math': './lib/math.js',
1470
+ 'string': './lib/string.js',
1471
+ 'array': './lib/array.js',
1472
+ }
1473
+
1474
+ const aiKey = response.choices[0].message.content.trim()
1475
+
1476
+ // AI key validated against map keys
1477
+ if (aiKey in importMap) {
1478
+ return require(importMap[aiKey])
1479
+ }
1480
+ }
1481
+ `,
1482
+ language: 'typescript',
1483
+ size: 1600,
1484
+ },
1485
+ },
1486
+
1487
+ // ============================================================================
1488
+ // False Negatives Group 10: Safe Python Operations
1489
+ // ============================================================================
1490
+ {
1491
+ name: 'Safe Python Operations - False Negatives',
1492
+ expectFindings: false,
1493
+ description: 'Safe Python patterns that should NOT be flagged',
1494
+ allowedInfoFindings: [
1495
+ {
1496
+ category: 'ai_pattern',
1497
+ maxCount: 1,
1498
+ reason: 'AI context visible but operations are safe',
1499
+ },
1500
+ ],
1501
+ file: {
1502
+ path: 'src/api/ai/safe_python_executor.py',
1503
+ content: `
1504
+ import openai
1505
+ import ast
1506
+ import yaml
1507
+ import subprocess
1508
+ import sqlite3
1509
+
1510
+ client = openai.OpenAI()
1511
+
1512
+ # 1. Parameterized SQL - SAFE
1513
+ def safe_parameterized_query(prompt: str):
1514
+ response = client.chat.completions.create(
1515
+ model="gpt-4",
1516
+ messages=[{"role": "user", "content": prompt}]
1517
+ )
1518
+
1519
+ # Parameterized query - AI provides only values
1520
+ user_id = response.choices[0].message.content
1521
+ conn = sqlite3.connect("db.sqlite")
1522
+ cursor = conn.cursor()
1523
+ cursor.execute("SELECT * FROM users WHERE id = ?", [user_id])
1524
+ return cursor.fetchall()
1525
+
1526
+ # 2. ast.literal_eval (safe subset) - SAFE
1527
+ def safe_literal_eval(prompt: str):
1528
+ response = client.chat.completions.create(
1529
+ model="gpt-4",
1530
+ messages=[{"role": "user", "content": prompt}]
1531
+ )
1532
+
1533
+ # ast.literal_eval only evaluates literals - no code execution
1534
+ data = ast.literal_eval(response.choices[0].message.content)
1535
+ return data
1536
+
1537
+ # 3. SafeLoader YAML - SAFE
1538
+ def safe_yaml_load(prompt: str):
1539
+ response = client.chat.completions.create(
1540
+ model="gpt-4",
1541
+ messages=[{"role": "user", "content": prompt}]
1542
+ )
1543
+
1544
+ content = response.choices[0].message.content
1545
+ # SafeLoader prevents code execution
1546
+ data = yaml.load(content, Loader=yaml.SafeLoader)
1547
+ return data
1548
+
1549
+ # 4. Subprocess with list args (no shell) - SAFE
1550
+ def safe_subprocess(prompt: str):
1551
+ response = client.chat.completions.create(
1552
+ model="gpt-4",
1553
+ messages=[{"role": "user", "content": prompt}]
1554
+ )
1555
+
1556
+ # shell=False with list args is safe
1557
+ # But we don't even use AI output in the command
1558
+ result = subprocess.run(['git', 'status', '--porcelain'], capture_output=True)
1559
+ return result.stdout.decode()
1560
+
1561
+ # 5. Display only - SAFE
1562
+ def display_ai_response(prompt: str):
1563
+ response = client.chat.completions.create(
1564
+ model="gpt-4",
1565
+ messages=[{"role": "user", "content": prompt}]
1566
+ )
1567
+
1568
+ # Only printing, not executing
1569
+ print("AI Response:", response.choices[0].message.content)
1570
+ return {"response": response.choices[0].message.content}
1571
+
1572
+ # 6. Validated JSON parsing - SAFE
1573
+ def safe_json_parse(prompt: str):
1574
+ import json
1575
+ from pydantic import BaseModel
1576
+
1577
+ class SafeOutput(BaseModel):
1578
+ name: str
1579
+ value: int
1580
+
1581
+ response = client.chat.completions.create(
1582
+ model="gpt-4",
1583
+ messages=[{"role": "user", "content": prompt}]
1584
+ )
1585
+
1586
+ # Schema validation with Pydantic
1587
+ data = json.loads(response.choices[0].message.content)
1588
+ validated = SafeOutput(**data)
1589
+ return validated
1590
+ `,
1591
+ language: 'python',
1592
+ size: 2100,
186
1593
  },
187
1594
  },
188
1595
  ],