@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
@@ -0,0 +1,791 @@
1
+ /**
2
+ * Layer 2: AI Package Hallucination Detection
3
+ * Detects AI-hallucinated and potentially fake package names in imports
4
+ *
5
+ * Background: USENIX research shows ~20% of AI-generated code references
6
+ * packages that don't exist, creating supply chain attack vectors. Attackers
7
+ * can register these fake package names and inject malicious code.
8
+ *
9
+ * Detection Strategy:
10
+ * 1. Known hallucinations database (verified fake packages)
11
+ * 2. Heuristic patterns (suspicious naming conventions)
12
+ * 3. Generic unscoped names that are too vague to be real
13
+ */
14
+
15
+ import type { Vulnerability, VulnerabilitySeverity } from '../types'
16
+ import {
17
+ isComment,
18
+ isTestOrMockFile,
19
+ isDocumentationFile,
20
+ isScannerOrFixtureFile,
21
+ isExampleDirectory,
22
+ } from '../utils/context-helpers'
23
+
24
+ // ============================================================================
25
+ // Known Hallucinated Package Database
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Verified fake package names from research and real-world observations
30
+ * These are packages that LLMs frequently suggest but don't exist (or are typosquats)
31
+ */
32
+ const KNOWN_HALLUCINATED_PACKAGES: Set<string> = new Set([
33
+ // JavaScript/TypeScript - from USENIX research and observation
34
+ 'react-charts', // Real: recharts, react-chartjs-2
35
+ 'mongo-client', // Real: mongodb
36
+ 'postgres-client', // Real: pg, postgres
37
+ 'fast-json', // Real: fast-json-stringify
38
+ 'node-helpers', // Doesn't exist
39
+ 'node-utils', // Doesn't exist
40
+ 'easy-utils', // Doesn't exist
41
+ 'simple-tools', // Doesn't exist
42
+ 'csv-parser-pro', // Real: csv-parser, papaparse
43
+ 'express-jwt-auth', // Real: express-jwt, passport-jwt
44
+ 'mongoose-connect', // Real: mongoose
45
+ 'redis-connect', // Real: redis, ioredis
46
+ 'graphql-tools-schema', // Real: @graphql-tools/schema
47
+ 'aws-s3-client', // Real: @aws-sdk/client-s3
48
+ 'google-cloud-storage', // Real: @google-cloud/storage (scoped)
49
+ 'firebase-auth-client', // Real: firebase, firebase-admin
50
+ 'stripe-payments', // Real: stripe
51
+ 'twilio-sms', // Real: twilio
52
+ 'sendgrid-email', // Real: @sendgrid/mail
53
+ 'mailchimp-api', // Real: @mailchimp/mailchimp_marketing
54
+ 'slack-bot', // Real: @slack/bolt, @slack/web-api
55
+ 'discord-bot', // Real: discord.js
56
+ 'telegram-bot', // Real: telegraf, node-telegram-bot-api
57
+ 'jwt-decode-verify', // Real: jsonwebtoken, jose
58
+ 'bcrypt-hash', // Real: bcrypt, bcryptjs
59
+ 'uuid-generate', // Real: uuid
60
+ 'date-formatter', // Real: date-fns, dayjs, moment
61
+ 'image-resize', // Real: sharp, jimp
62
+ 'pdf-generator', // Real: pdfkit, pdf-lib
63
+ 'excel-parser', // Real: xlsx, exceljs
64
+ 'xml-parser-pro', // Real: fast-xml-parser, xml2js
65
+ 'yaml-parser', // Real: js-yaml, yaml
66
+
67
+ // Python hallucinated packages
68
+ 'easy-flask', // Real: flask
69
+ 'simple-api', // Real: fastapi, flask
70
+ 'fast-db', // Real: sqlalchemy, databases
71
+ 'python-helpers', // Doesn't exist
72
+ 'django-helpers', // Doesn't exist
73
+ 'flask-helpers', // Doesn't exist
74
+ 'numpy-utils', // Doesn't exist
75
+ 'pandas-helpers', // Doesn't exist
76
+ 'data-parser', // Real: pandas
77
+ 'ml-utils', // Doesn't exist
78
+ 'ai-tools', // Doesn't exist
79
+ ])
80
+
81
+ /**
82
+ * Well-known legitimate packages to avoid false positives
83
+ */
84
+ const KNOWN_LEGITIMATE_PACKAGES: Set<string> = new Set([
85
+ // Core/popular JS packages
86
+ 'react', 'vue', 'angular', 'svelte', 'solid-js',
87
+ 'express', 'fastify', 'hono', 'koa', 'nest',
88
+ 'next', 'nuxt', 'remix', 'astro', 'gatsby',
89
+ 'axios', 'fetch', 'got', 'ky', 'superagent',
90
+ 'lodash', 'underscore', 'radash', 'ramda',
91
+ 'zod', 'joi', 'yup', 'ajv', 'valibot',
92
+ 'dayjs', 'date-fns', 'moment', 'luxon',
93
+ 'mongodb', 'mongoose', 'pg', 'mysql2', 'better-sqlite3',
94
+ 'prisma', 'drizzle-orm', 'typeorm', 'sequelize', 'knex',
95
+ 'redis', 'ioredis',
96
+ 'bcrypt', 'bcryptjs', 'argon2',
97
+ 'jsonwebtoken', 'jose', 'passport',
98
+ 'uuid', 'nanoid', 'cuid', 'ulid',
99
+ 'sharp', 'jimp', 'canvas',
100
+ 'pdfkit', 'pdf-lib',
101
+ 'xlsx', 'exceljs',
102
+ 'cheerio', 'puppeteer', 'playwright',
103
+ 'winston', 'pino', 'bunyan',
104
+ 'dotenv', 'config', 'convict',
105
+ 'chalk', 'picocolors', 'kleur',
106
+ 'commander', 'yargs', 'meow', 'cac',
107
+ 'inquirer', 'prompts',
108
+ 'glob', 'fast-glob', 'globby',
109
+ 'chokidar', 'nodemon',
110
+ 'esbuild', 'vite', 'webpack', 'rollup', 'parcel',
111
+ 'jest', 'vitest', 'mocha', 'ava', 'tape',
112
+ 'eslint', 'prettier', 'biome',
113
+ 'typescript', 'ts-node', 'tsx',
114
+ 'openai', 'anthropic',
115
+ 'stripe', 'paypal',
116
+ 'twilio', 'nodemailer',
117
+ 'aws-sdk',
118
+ 'firebase', 'firebase-admin',
119
+ 'supabase',
120
+ 'graphql', 'apollo-server', 'urql',
121
+ 'socket.io', 'ws',
122
+ 'bullmq', 'bee-queue',
123
+ 'csv-parser', 'papaparse',
124
+ 'fast-xml-parser', 'xml2js',
125
+ 'js-yaml', 'yaml',
126
+ // Real 'simple-' packages
127
+ 'simple-git', 'simpl-schema',
128
+ // Real 'fast-' packages
129
+ 'fast-json-stringify', 'fastify', 'fast-glob', 'fast-deep-equal', 'fast-xml-parser',
130
+
131
+ // Python packages
132
+ 'flask', 'django', 'fastapi', 'starlette', 'tornado',
133
+ 'requests', 'httpx', 'aiohttp',
134
+ 'numpy', 'pandas', 'scipy', 'matplotlib',
135
+ 'scikit-learn', 'tensorflow', 'pytorch', 'keras',
136
+ 'sqlalchemy', 'alembic', 'psycopg2', 'asyncpg',
137
+ 'celery', 'redis', 'dramatiq',
138
+ 'pydantic', 'marshmallow',
139
+ 'pytest', 'unittest', 'nose',
140
+ 'black', 'flake8', 'mypy', 'ruff',
141
+ 'boto3', 'botocore',
142
+ 'pillow', 'opencv-python',
143
+ 'beautifulsoup4', 'lxml', 'scrapy',
144
+ ])
145
+
146
+ // ============================================================================
147
+ // Typosquatting Detection
148
+ // ============================================================================
149
+
150
+ /**
151
+ * Popular packages to check for typosquatting
152
+ */
153
+ const POPULAR_PACKAGES_FOR_TYPOSQUAT: string[] = [
154
+ // JavaScript/TypeScript core
155
+ 'react', 'vue', 'angular', 'svelte', 'solid',
156
+ 'express', 'fastify', 'koa', 'hono', 'nest',
157
+ 'next', 'nuxt', 'remix', 'gatsby', 'astro',
158
+ 'lodash', 'axios', 'moment', 'dayjs', 'zod',
159
+ 'mongoose', 'sequelize', 'prisma', 'typeorm', 'knex',
160
+ 'webpack', 'rollup', 'vite', 'esbuild', 'parcel',
161
+ 'jest', 'vitest', 'mocha', 'chai', 'cypress',
162
+ 'typescript', 'eslint', 'prettier', 'babel',
163
+ 'redux', 'mobx', 'zustand', 'jotai', 'recoil',
164
+ 'tailwindcss', 'bootstrap', 'antd', 'material-ui',
165
+ 'socket', 'graphql', 'apollo', 'trpc',
166
+ // Python core
167
+ 'requests', 'flask', 'django', 'fastapi', 'tornado',
168
+ 'numpy', 'pandas', 'scipy', 'matplotlib', 'seaborn',
169
+ 'tensorflow', 'pytorch', 'keras', 'scikit-learn',
170
+ 'sqlalchemy', 'celery', 'redis', 'boto3',
171
+ 'pydantic', 'pytest', 'black', 'ruff',
172
+ ]
173
+
174
+ /**
175
+ * Common character substitutions used in typosquatting
176
+ */
177
+ const TYPOSQUAT_SUBSTITUTIONS: Array<[string, string]> = [
178
+ ['0', 'o'], ['o', '0'],
179
+ ['1', 'l'], ['l', '1'], ['1', 'i'], ['i', '1'],
180
+ ['5', 's'], ['s', '5'],
181
+ ['a', '@'], ['@', 'a'],
182
+ ['e', '3'], ['3', 'e'],
183
+ ['rn', 'm'], ['m', 'rn'],
184
+ ['vv', 'w'], ['w', 'vv'],
185
+ ['cl', 'd'], ['d', 'cl'],
186
+ ['ii', 'u'], ['u', 'ii'],
187
+ ]
188
+
189
+ /**
190
+ * Calculate Levenshtein distance between two strings
191
+ */
192
+ function levenshteinDistance(a: string, b: string): number {
193
+ const matrix: number[][] = []
194
+
195
+ for (let i = 0; i <= b.length; i++) {
196
+ matrix[i] = [i]
197
+ }
198
+ for (let j = 0; j <= a.length; j++) {
199
+ matrix[0][j] = j
200
+ }
201
+
202
+ for (let i = 1; i <= b.length; i++) {
203
+ for (let j = 1; j <= a.length; j++) {
204
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
205
+ matrix[i][j] = matrix[i - 1][j - 1]
206
+ } else {
207
+ matrix[i][j] = Math.min(
208
+ matrix[i - 1][j - 1] + 1,
209
+ matrix[i][j - 1] + 1,
210
+ matrix[i - 1][j] + 1
211
+ )
212
+ }
213
+ }
214
+ }
215
+
216
+ return matrix[b.length][a.length]
217
+ }
218
+
219
+ /**
220
+ * Check if package name has character substitutions matching a popular package
221
+ */
222
+ function hasCharacterSubstitution(packageName: string, popularPackage: string): boolean {
223
+ // Apply each substitution to the popular package and check for match
224
+ for (const [from, to] of TYPOSQUAT_SUBSTITUTIONS) {
225
+ const substituted = popularPackage.replace(new RegExp(from, 'g'), to)
226
+ if (substituted.toLowerCase() === packageName.toLowerCase() && substituted !== popularPackage) {
227
+ return true
228
+ }
229
+ }
230
+ return false
231
+ }
232
+
233
+ /**
234
+ * Check if package is a potential typosquat of a popular package
235
+ * Returns the popular package it resembles and the reason
236
+ */
237
+ function checkTyposquatting(packageName: string): { isTyposquat: boolean; similarTo?: string; reason?: string } {
238
+ const name = packageName.toLowerCase()
239
+
240
+ for (const popular of POPULAR_PACKAGES_FOR_TYPOSQUAT) {
241
+ const popularLower = popular.toLowerCase()
242
+
243
+ // Skip exact match
244
+ if (name === popularLower) continue
245
+
246
+ // Check Levenshtein distance (1-2 chars difference)
247
+ const distance = levenshteinDistance(name, popularLower)
248
+
249
+ if (distance === 1) {
250
+ return {
251
+ isTyposquat: true,
252
+ similarTo: popular,
253
+ reason: `differs by only 1 character from "${popular}"`,
254
+ }
255
+ }
256
+
257
+ if (distance === 2 && name.length >= 5 && Math.abs(name.length - popularLower.length) <= 1) {
258
+ return {
259
+ isTyposquat: true,
260
+ similarTo: popular,
261
+ reason: `very similar to "${popular}" (2 char difference)`,
262
+ }
263
+ }
264
+
265
+ // Check character substitution
266
+ if (hasCharacterSubstitution(name, popularLower)) {
267
+ return {
268
+ isTyposquat: true,
269
+ similarTo: popular,
270
+ reason: `uses character substitution similar to "${popular}" (e.g., 0↔o, 1↔l)`,
271
+ }
272
+ }
273
+
274
+ // Check for doubled characters (lodaash vs lodash)
275
+ const doubledPattern = new RegExp(`^${popularLower.split('').join('+')}+$`)
276
+ if (doubledPattern.test(name) && name !== popularLower) {
277
+ return {
278
+ isTyposquat: true,
279
+ similarTo: popular,
280
+ reason: `contains doubled characters similar to "${popular}"`,
281
+ }
282
+ }
283
+
284
+ // Check for missing vowels (rqsts vs requests)
285
+ const noVowels = popularLower.replace(/[aeiou]/g, '')
286
+ const nameNoVowels = name.replace(/[aeiou]/g, '')
287
+ if (noVowels === nameNoVowels && noVowels.length >= 4 && name !== popularLower) {
288
+ return {
289
+ isTyposquat: true,
290
+ similarTo: popular,
291
+ reason: `missing vowels similar to "${popular}"`,
292
+ }
293
+ }
294
+
295
+ // Check for common prefixes/suffixes that create confusion
296
+ if (name === `${popularLower}-js` || name === `${popularLower}js` ||
297
+ name === `node-${popularLower}` || name === `${popularLower}-node`) {
298
+ return {
299
+ isTyposquat: true,
300
+ similarTo: popular,
301
+ reason: `adds common suffix/prefix that could be confused with "${popular}"`,
302
+ }
303
+ }
304
+ }
305
+
306
+ return { isTyposquat: false }
307
+ }
308
+
309
+ // ============================================================================
310
+ // Suspicious Pattern Definitions
311
+ // ============================================================================
312
+
313
+ /**
314
+ * Unscoped generic names that are too vague to be real packages
315
+ * Real packages have specific names, not generic utility words
316
+ */
317
+ const GENERIC_UNSCOPED_NAMES: Set<string> = new Set([
318
+ 'utils',
319
+ 'helpers',
320
+ 'common',
321
+ 'tools',
322
+ 'shared',
323
+ 'lib',
324
+ 'core',
325
+ 'base',
326
+ 'main',
327
+ 'app',
328
+ 'api',
329
+ 'data',
330
+ 'models',
331
+ 'services',
332
+ 'modules',
333
+ 'components',
334
+ ])
335
+
336
+ /**
337
+ * Prefixes that are frequently hallucinated when combined with generic suffixes
338
+ */
339
+ const SUSPICIOUS_PREFIXES = [
340
+ 'easy-',
341
+ 'simple-',
342
+ 'fast-', // Note: some real packages use this, checked against allowlist
343
+ 'quick-',
344
+ 'basic-',
345
+ 'super-',
346
+ 'mega-',
347
+ 'ultra-',
348
+ 'awesome-',
349
+ 'better-',
350
+ 'node-', // Note: some real packages use this, but often hallucinated for non-core modules
351
+ 'react-', // Note: many real packages, but also many hallucinated
352
+ 'vue-', // Note: many real packages, but also many hallucinated
353
+ 'express-', // Note: many real packages, but also many hallucinated
354
+ 'python-',
355
+ 'django-', // Note: some real, but often hallucinated
356
+ 'flask-', // Note: some real, but often hallucinated
357
+ ]
358
+
359
+ /**
360
+ * Suffixes that indicate potential hallucination when combined with suspicious prefixes
361
+ */
362
+ const SUSPICIOUS_SUFFIXES = [
363
+ '-utils',
364
+ '-helpers',
365
+ '-tools',
366
+ '-lib',
367
+ '-client', // Often hallucinated for already-named services
368
+ '-sdk', // Often hallucinated
369
+ '-api',
370
+ '-wrapper',
371
+ '-connector',
372
+ '-adapter',
373
+ '-handler',
374
+ '-manager',
375
+ '-service',
376
+ '-pro',
377
+ '-plus',
378
+ '-enhanced',
379
+ ]
380
+
381
+ // ============================================================================
382
+ // Context Detection
383
+ // ============================================================================
384
+
385
+ /**
386
+ * Check if package name is scoped (@org/package)
387
+ * Scoped packages are less likely to be hallucinated (requires npm org)
388
+ */
389
+ function isScopedPackage(packageName: string): boolean {
390
+ return packageName.startsWith('@')
391
+ }
392
+
393
+ /**
394
+ * Check if this is a relative import (./path or ../path)
395
+ */
396
+ function isRelativeImport(importPath: string): boolean {
397
+ return importPath.startsWith('./') || importPath.startsWith('../') || importPath.startsWith('/')
398
+ }
399
+
400
+ /**
401
+ * Check if this is an alias import (@/, ~/, #)
402
+ */
403
+ function isAliasImport(importPath: string): boolean {
404
+ return /^[@~#]\//.test(importPath)
405
+ }
406
+
407
+ /**
408
+ * Check if this is a Node.js built-in module
409
+ */
410
+ function isNodeBuiltin(packageName: string): boolean {
411
+ const builtins = new Set([
412
+ 'fs', 'path', 'http', 'https', 'crypto', 'os', 'url', 'util', 'stream',
413
+ 'events', 'buffer', 'querystring', 'child_process', 'cluster', 'dgram',
414
+ 'dns', 'net', 'readline', 'repl', 'tls', 'tty', 'v8', 'vm', 'zlib',
415
+ 'assert', 'async_hooks', 'console', 'constants', 'domain', 'inspector',
416
+ 'module', 'perf_hooks', 'process', 'punycode', 'string_decoder',
417
+ 'timers', 'trace_events', 'worker_threads',
418
+ // Node: prefixed
419
+ 'node:fs', 'node:path', 'node:http', 'node:https', 'node:crypto',
420
+ 'node:os', 'node:url', 'node:util', 'node:stream', 'node:events',
421
+ 'node:buffer', 'node:querystring', 'node:child_process', 'node:test',
422
+ ])
423
+ return builtins.has(packageName) || packageName.startsWith('node:')
424
+ }
425
+
426
+ /**
427
+ * Check if file is a package manifest
428
+ */
429
+ function isPackageManifest(filePath: string): boolean {
430
+ const manifestFiles = [
431
+ 'package.json',
432
+ 'requirements.txt',
433
+ 'Pipfile',
434
+ 'pyproject.toml',
435
+ 'setup.py',
436
+ 'Gemfile',
437
+ 'go.mod',
438
+ 'Cargo.toml',
439
+ 'composer.json',
440
+ ]
441
+ return manifestFiles.some(f => filePath.endsWith(f))
442
+ }
443
+
444
+ /**
445
+ * Check if package name matches suspicious patterns
446
+ */
447
+ function isSuspiciousPattern(packageName: string): { suspicious: boolean; reason: string } {
448
+ // Check known hallucinated packages first
449
+ if (KNOWN_HALLUCINATED_PACKAGES.has(packageName)) {
450
+ return { suspicious: true, reason: 'Known hallucinated package from research' }
451
+ }
452
+
453
+ // Skip known legitimate packages
454
+ if (KNOWN_LEGITIMATE_PACKAGES.has(packageName)) {
455
+ return { suspicious: false, reason: '' }
456
+ }
457
+
458
+ // Check unscoped generic names
459
+ if (GENERIC_UNSCOPED_NAMES.has(packageName)) {
460
+ return { suspicious: true, reason: 'Generic unscoped name - real packages have specific names' }
461
+ }
462
+
463
+ // Check suspicious prefix + suffix combinations
464
+ for (const prefix of SUSPICIOUS_PREFIXES) {
465
+ if (packageName.startsWith(prefix)) {
466
+ // Check if it has a suspicious suffix too
467
+ for (const suffix of SUSPICIOUS_SUFFIXES) {
468
+ if (packageName.endsWith(suffix)) {
469
+ // Double suspicious - prefix AND suffix
470
+ return {
471
+ suspicious: true,
472
+ reason: `Suspicious pattern: "${prefix}" prefix with "${suffix}" suffix`,
473
+ }
474
+ }
475
+ }
476
+
477
+ // Just prefix is lower confidence
478
+ const baseName = packageName.slice(prefix.length)
479
+ if (GENERIC_UNSCOPED_NAMES.has(baseName) || baseName.length < 3) {
480
+ return {
481
+ suspicious: true,
482
+ reason: `Suspicious pattern: "${prefix}" prefix with generic name`,
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ // Check if it's just prefix + generic suffix
489
+ for (const suffix of SUSPICIOUS_SUFFIXES) {
490
+ if (packageName.endsWith(suffix)) {
491
+ const baseName = packageName.slice(0, -suffix.length)
492
+ // If the base is very short or generic, flag it
493
+ if (baseName.length <= 2 || GENERIC_UNSCOPED_NAMES.has(baseName)) {
494
+ return {
495
+ suspicious: true,
496
+ reason: `Suspicious pattern: generic name with "${suffix}" suffix`,
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ return { suspicious: false, reason: '' }
503
+ }
504
+
505
+ /**
506
+ * Extract package name from import/require path
507
+ */
508
+ function extractPackageName(importPath: string): string | null {
509
+ // Skip relative and alias imports
510
+ if (isRelativeImport(importPath) || isAliasImport(importPath)) {
511
+ return null
512
+ }
513
+
514
+ // Handle scoped packages (@org/package)
515
+ if (importPath.startsWith('@')) {
516
+ const parts = importPath.split('/')
517
+ if (parts.length >= 2) {
518
+ return `${parts[0]}/${parts[1]}`
519
+ }
520
+ return null
521
+ }
522
+
523
+ // Regular package - get the first part before any /
524
+ const parts = importPath.split('/')
525
+ return parts[0]
526
+ }
527
+
528
+ // ============================================================================
529
+ // Import Pattern Matchers
530
+ // ============================================================================
531
+
532
+ interface ImportMatch {
533
+ packageName: string
534
+ lineNumber: number
535
+ lineContent: string
536
+ }
537
+
538
+ /**
539
+ * Extract imports from JavaScript/TypeScript code
540
+ */
541
+ function extractJSImports(content: string): ImportMatch[] {
542
+ const imports: ImportMatch[] = []
543
+ const lines = content.split('\n')
544
+
545
+ // ES6 import patterns
546
+ const es6ImportRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g
547
+ // require() patterns
548
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
549
+ // Dynamic import
550
+ const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g
551
+
552
+ let match: RegExpExecArray | null
553
+
554
+ while ((match = es6ImportRegex.exec(content)) !== null) {
555
+ const lineNumber = content.substring(0, match.index).split('\n').length
556
+ const packageName = extractPackageName(match[1])
557
+ if (packageName && !isNodeBuiltin(packageName)) {
558
+ imports.push({
559
+ packageName,
560
+ lineNumber,
561
+ lineContent: lines[lineNumber - 1]?.trim() || '',
562
+ })
563
+ }
564
+ }
565
+
566
+ while ((match = requireRegex.exec(content)) !== null) {
567
+ const lineNumber = content.substring(0, match.index).split('\n').length
568
+ const packageName = extractPackageName(match[1])
569
+ if (packageName && !isNodeBuiltin(packageName)) {
570
+ imports.push({
571
+ packageName,
572
+ lineNumber,
573
+ lineContent: lines[lineNumber - 1]?.trim() || '',
574
+ })
575
+ }
576
+ }
577
+
578
+ while ((match = dynamicImportRegex.exec(content)) !== null) {
579
+ const lineNumber = content.substring(0, match.index).split('\n').length
580
+ const packageName = extractPackageName(match[1])
581
+ if (packageName && !isNodeBuiltin(packageName)) {
582
+ imports.push({
583
+ packageName,
584
+ lineNumber,
585
+ lineContent: lines[lineNumber - 1]?.trim() || '',
586
+ })
587
+ }
588
+ }
589
+
590
+ return imports
591
+ }
592
+
593
+ /**
594
+ * Extract dependencies from package.json
595
+ */
596
+ function extractPackageJsonDeps(content: string, lines: string[]): ImportMatch[] {
597
+ const imports: ImportMatch[] = []
598
+
599
+ try {
600
+ const pkg = JSON.parse(content)
601
+ const allDeps = {
602
+ ...pkg.dependencies,
603
+ ...pkg.devDependencies,
604
+ ...pkg.peerDependencies,
605
+ ...pkg.optionalDependencies,
606
+ }
607
+
608
+ for (const packageName of Object.keys(allDeps)) {
609
+ // Find the line number where this package appears
610
+ const lineIndex = lines.findIndex(line => line.includes(`"${packageName}"`))
611
+ if (lineIndex !== -1) {
612
+ imports.push({
613
+ packageName,
614
+ lineNumber: lineIndex + 1,
615
+ lineContent: lines[lineIndex].trim(),
616
+ })
617
+ }
618
+ }
619
+ } catch {
620
+ // Invalid JSON, skip
621
+ }
622
+
623
+ return imports
624
+ }
625
+
626
+ /**
627
+ * Extract dependencies from requirements.txt
628
+ */
629
+ function extractRequirementsDeps(_content: string, lines: string[]): ImportMatch[] {
630
+ const imports: ImportMatch[] = []
631
+
632
+ for (let i = 0; i < lines.length; i++) {
633
+ const line = lines[i].trim()
634
+
635
+ // Skip comments and empty lines
636
+ if (!line || line.startsWith('#') || line.startsWith('-')) continue
637
+
638
+ // Extract package name (before ==, >=, <=, ~=, etc.)
639
+ const match = line.match(/^([a-zA-Z0-9_-]+)/)
640
+ if (match) {
641
+ imports.push({
642
+ packageName: match[1].toLowerCase().replace(/_/g, '-'),
643
+ lineNumber: i + 1,
644
+ lineContent: line,
645
+ })
646
+ }
647
+ }
648
+
649
+ return imports
650
+ }
651
+
652
+ // ============================================================================
653
+ // Main Detection Function
654
+ // ============================================================================
655
+
656
+ /**
657
+ * Main detection function for AI package hallucination
658
+ */
659
+ export function detectAIPackageHallucination(
660
+ content: string,
661
+ filePath: string
662
+ ): Vulnerability[] {
663
+ const vulnerabilities: Vulnerability[] = []
664
+
665
+ // Skip non-applicable files
666
+ if (isScannerOrFixtureFile(filePath)) return vulnerabilities
667
+ if (isDocumentationFile(filePath)) return vulnerabilities
668
+
669
+ const lines = content.split('\n')
670
+ const isTestFile = isTestOrMockFile(filePath)
671
+ const isExample = isExampleDirectory(filePath)
672
+ const isManifest = isPackageManifest(filePath)
673
+
674
+ // Extract imports based on file type
675
+ let imports: ImportMatch[] = []
676
+
677
+ if (filePath.endsWith('package.json')) {
678
+ imports = extractPackageJsonDeps(content, lines)
679
+ } else if (filePath.endsWith('requirements.txt')) {
680
+ imports = extractRequirementsDeps(content, lines)
681
+ } else if (/\.(js|jsx|ts|tsx|mjs|cjs)$/.test(filePath)) {
682
+ imports = extractJSImports(content)
683
+ } else {
684
+ // Not a file we can analyze for imports
685
+ return vulnerabilities
686
+ }
687
+
688
+ // Track already-flagged packages to avoid duplicates
689
+ const flaggedPackages = new Set<string>()
690
+
691
+ for (const imp of imports) {
692
+ // Skip if we've already flagged this package
693
+ if (flaggedPackages.has(imp.packageName)) continue
694
+
695
+ // Skip scoped packages (less likely to be hallucinated)
696
+ if (isScopedPackage(imp.packageName)) continue
697
+
698
+ // Skip comments
699
+ if (isComment(imp.lineContent)) continue
700
+
701
+ // Skip known legitimate packages for typosquat check
702
+ if (KNOWN_LEGITIMATE_PACKAGES.has(imp.packageName)) continue
703
+
704
+ // Check for typosquatting first (higher priority - supply chain attack)
705
+ const typosquatResult = checkTyposquatting(imp.packageName)
706
+ if (typosquatResult.isTyposquat) {
707
+ flaggedPackages.add(imp.packageName)
708
+
709
+ let severity: VulnerabilitySeverity = 'high' // Typosquats are always high priority
710
+
711
+ // Package manifests in production are critical
712
+ if (isManifest) {
713
+ severity = 'critical'
714
+ }
715
+
716
+ // Test files and examples get downgraded
717
+ if (isTestFile || isExample) {
718
+ severity = 'low'
719
+ }
720
+
721
+ const description = `Package "${imp.packageName}" ${typosquatResult.reason}. This could be a typosquatting attack where attackers register similar package names to steal credentials or inject malicious code.`
722
+ const suggestedFix = `Verify you meant to use "${typosquatResult.similarTo}". Run "npm view ${imp.packageName}" to check if this package exists. If it doesn't, update to "${typosquatResult.similarTo}".`
723
+
724
+ vulnerabilities.push({
725
+ id: `ai-pkg-typosquat-${filePath}-${imp.lineNumber}-${imp.packageName}`,
726
+ filePath,
727
+ lineNumber: imp.lineNumber,
728
+ lineContent: imp.lineContent,
729
+ severity,
730
+ category: 'ai_package_typosquat',
731
+ title: `Potential typosquat: ${imp.packageName} (similar to ${typosquatResult.similarTo})`,
732
+ description,
733
+ suggestedFix,
734
+ confidence: 'high',
735
+ layer: 2,
736
+ requiresAIValidation: false, // Typosquats don't need AI validation - pattern is clear
737
+ })
738
+ continue // Don't also flag as hallucination
739
+ }
740
+
741
+ // Check if package is suspicious (hallucination patterns)
742
+ const { suspicious, reason } = isSuspiciousPattern(imp.packageName)
743
+
744
+ if (suspicious) {
745
+ flaggedPackages.add(imp.packageName)
746
+
747
+ // Determine severity based on context
748
+ let severity: VulnerabilitySeverity = 'medium'
749
+
750
+ // Known hallucinations are higher severity
751
+ if (KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName)) {
752
+ severity = 'high'
753
+ }
754
+
755
+ // Package manifests are higher severity (direct dependency)
756
+ if (isManifest && severity === 'medium') {
757
+ severity = 'high'
758
+ }
759
+
760
+ // Test files and examples get downgraded
761
+ if (isTestFile || isExample) {
762
+ severity = 'info'
763
+ }
764
+
765
+ const description = KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName)
766
+ ? `Package "${imp.packageName}" is a known AI-hallucinated package that doesn't exist. This creates a supply chain attack vector where attackers register the fake package name.`
767
+ : `Package "${imp.packageName}" matches suspicious hallucination patterns: ${reason}. Verify this package exists on npm/PyPI before using.`
768
+
769
+ const suggestedFix = KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName)
770
+ ? `Remove "${imp.packageName}" and use the correct package. Search npm/PyPI for the real package that provides this functionality.`
771
+ : `Verify "${imp.packageName}" exists: run "npm view ${imp.packageName}" or check https://www.npmjs.com/package/${imp.packageName}. If it doesn't exist, find the correct package name.`
772
+
773
+ vulnerabilities.push({
774
+ id: `ai-pkg-hallucination-${filePath}-${imp.lineNumber}-${imp.packageName}`,
775
+ filePath,
776
+ lineNumber: imp.lineNumber,
777
+ lineContent: imp.lineContent,
778
+ severity,
779
+ category: 'ai_package_hallucination',
780
+ title: `Potentially hallucinated package: ${imp.packageName}`,
781
+ description,
782
+ suggestedFix,
783
+ confidence: KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName) ? 'high' : 'medium',
784
+ layer: 2,
785
+ requiresAIValidation: severity !== 'info' && !KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName),
786
+ })
787
+ }
788
+ }
789
+
790
+ return vulnerabilities
791
+ }