@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
@@ -135,14 +135,90 @@ const UNSAFE_INTERPOLATION_PATTERNS: PromptHygienePattern[] = [
135
135
  },
136
136
  ]
137
137
 
138
+ // ============================================================================
139
+ // Secret Patterns - Comprehensive provider-specific detection
140
+ // ============================================================================
141
+
138
142
  /**
139
- * B3: Secrets in prompt context patterns
143
+ * Provider-specific secret patterns with known prefixes
144
+ * These are high-confidence patterns that don't need context matching
145
+ */
146
+ const KNOWN_SECRET_PREFIXES = [
147
+ // OpenAI
148
+ { name: 'OpenAI API Key', pattern: /sk-[a-zA-Z0-9]{20,}/g, severity: 'critical' as const },
149
+ { name: 'OpenAI Project Key', pattern: /sk-proj-[a-zA-Z0-9]{48,}/g, severity: 'critical' as const },
150
+ // Anthropic
151
+ { name: 'Anthropic API Key', pattern: /sk-ant-[a-zA-Z0-9-]{20,}/g, severity: 'critical' as const },
152
+ { name: 'Anthropic Full Key', pattern: /sk-ant-api03-[a-zA-Z0-9_-]{90,}/g, severity: 'critical' as const },
153
+ // GitHub
154
+ { name: 'GitHub PAT', pattern: /ghp_[a-zA-Z0-9]{36,}/g, severity: 'critical' as const },
155
+ { name: 'GitHub OAuth', pattern: /gho_[a-zA-Z0-9]{36,}/g, severity: 'critical' as const },
156
+ { name: 'GitHub App Token', pattern: /ghu_[a-zA-Z0-9]{36,}/g, severity: 'critical' as const },
157
+ { name: 'GitHub Refresh Token', pattern: /ghr_[a-zA-Z0-9]{36,}/g, severity: 'critical' as const },
158
+ { name: 'GitHub Fine-grained PAT', pattern: /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/g, severity: 'critical' as const },
159
+ // Stripe
160
+ { name: 'Stripe Live Secret', pattern: /sk_live_[a-zA-Z0-9]{24,}/g, severity: 'critical' as const },
161
+ { name: 'Stripe Test Secret', pattern: /sk_test_[a-zA-Z0-9]{24,}/g, severity: 'medium' as const },
162
+ { name: 'Stripe Restricted Key', pattern: /rk_live_[a-zA-Z0-9]{24,}/g, severity: 'critical' as const },
163
+ // AWS
164
+ { name: 'AWS Access Key', pattern: /AKIA[0-9A-Z]{16}/g, severity: 'critical' as const },
165
+ { name: 'AWS Session Token', pattern: /ASIA[0-9A-Z]{16}/g, severity: 'critical' as const },
166
+ // Google
167
+ { name: 'Google API Key', pattern: /AIza[0-9A-Za-z-_]{35}/g, severity: 'high' as const },
168
+ // Slack
169
+ { name: 'Slack Bot Token', pattern: /xoxb-[0-9a-zA-Z-]{50,}/g, severity: 'critical' as const },
170
+ { name: 'Slack User Token', pattern: /xoxp-[0-9a-zA-Z-]{50,}/g, severity: 'critical' as const },
171
+ { name: 'Slack App Token', pattern: /xoxa-[0-9a-zA-Z-]{50,}/g, severity: 'critical' as const },
172
+ { name: 'Slack Legacy Token', pattern: /xox[baprs]-[0-9a-zA-Z]{10,}/g, severity: 'critical' as const },
173
+ // Twilio
174
+ { name: 'Twilio API Key', pattern: /SK[a-f0-9]{32}/g, severity: 'critical' as const },
175
+ { name: 'Twilio Account SID', pattern: /AC[a-f0-9]{32}/g, severity: 'high' as const },
176
+ // SendGrid
177
+ { name: 'SendGrid API Key', pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g, severity: 'critical' as const },
178
+ // Mailgun
179
+ { name: 'Mailgun API Key', pattern: /key-[a-zA-Z0-9]{32}/g, severity: 'critical' as const },
180
+ // NPM/PyPI
181
+ { name: 'NPM Token', pattern: /npm_[a-zA-Z0-9]{36}/g, severity: 'critical' as const },
182
+ { name: 'PyPI Token', pattern: /pypi-[a-zA-Z0-9]{32,}/g, severity: 'critical' as const },
183
+ // Vercel/Netlify
184
+ { name: 'Vercel Token', pattern: /vercel_[a-zA-Z0-9]{24,}/g, severity: 'critical' as const },
185
+ { name: 'Netlify Token', pattern: /nfp_[a-zA-Z0-9]{40,}/g, severity: 'critical' as const },
186
+ // Square
187
+ { name: 'Square Access Token', pattern: /sq0csp-[a-zA-Z0-9-_]{43}/g, severity: 'critical' as const },
188
+ { name: 'Square OAuth Secret', pattern: /sq0csp-[a-zA-Z0-9-_]{40,}/g, severity: 'critical' as const },
189
+ // Shopify
190
+ { name: 'Shopify Access Token', pattern: /shpat_[a-fA-F0-9]{32}/g, severity: 'critical' as const },
191
+ { name: 'Shopify Private App', pattern: /shppa_[a-fA-F0-9]{32}/g, severity: 'critical' as const },
192
+ // Datadog
193
+ { name: 'Datadog API Key', pattern: /dd[a-z]{1}[a-f0-9]{39}/g, severity: 'critical' as const },
194
+ // HuggingFace
195
+ { name: 'HuggingFace Token', pattern: /hf_[a-zA-Z0-9]{34,}/g, severity: 'critical' as const },
196
+ // Replicate
197
+ { name: 'Replicate API Token', pattern: /r8_[a-zA-Z0-9]{37}/g, severity: 'critical' as const },
198
+ // OpenRouter
199
+ { name: 'OpenRouter Key', pattern: /sk-or-v1-[a-zA-Z0-9]{64}/g, severity: 'critical' as const },
200
+ // Cohere
201
+ { name: 'Cohere API Key', pattern: /[a-zA-Z0-9]{40}(?=.*cohere)/gi, severity: 'high' as const },
202
+ // Private Keys
203
+ { name: 'Private Key', pattern: /-----BEGIN\s+(?:RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/g, severity: 'critical' as const },
204
+ // JWT Tokens (full format)
205
+ { name: 'JWT Token', pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g, severity: 'high' as const },
206
+ // Database URLs with credentials
207
+ { name: 'Database URL', pattern: /(mongodb|postgres|mysql|redis|amqp)(\+srv)?:\/\/[^:]+:[^@\s]+@[^\s"']+/gi, severity: 'critical' as const },
208
+ // Webhook URLs (often contain secrets)
209
+ { name: 'Slack Webhook', pattern: /https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]+\/B[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+/g, severity: 'high' as const },
210
+ { name: 'Discord Webhook', pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/g, severity: 'high' as const },
211
+ ]
212
+
213
+ /**
214
+ * B3: Secrets in prompt context patterns (original context-aware patterns)
215
+ * Note: Using [^\n;]* instead of [^;]* to prevent matching across lines
140
216
  */
141
217
  const SECRETS_IN_PROMPTS_PATTERNS: PromptHygienePattern[] = [
142
- // API keys in message content
218
+ // API keys in message content (same line only)
143
219
  {
144
220
  name: 'API key in prompt content',
145
- pattern: /(?:messages|prompt|system|content)\s*[=:][^;]*(?:sk-[a-zA-Z0-9]{20,}|api[_-]?key\s*[:=]\s*['"][^'"]{16,}['"])/gi,
221
+ pattern: /(?:messages|prompt|system|content)\s*[=:][^\n;]*(?:sk-[a-zA-Z0-9]{20,}|api[_-]?key\s*[:=]\s*['"][^'"]{16,}['"])/gi,
146
222
  severity: 'critical',
147
223
  description: 'API key appears to be hardcoded in prompt content. Keys in prompts may be logged, cached, or sent to model providers.',
148
224
  suggestedFix: 'Never include API keys in prompts. Use environment variables and keep them server-side only.',
@@ -150,7 +226,7 @@ const SECRETS_IN_PROMPTS_PATTERNS: PromptHygienePattern[] = [
150
226
  // AWS keys in prompts
151
227
  {
152
228
  name: 'AWS credentials in prompt',
153
- pattern: /(?:messages|prompt|system|content)\s*[=:][^;]*(?:AKIA[A-Z0-9]{16}|aws[_-]?(?:secret|access)[_-]?key)/gi,
229
+ pattern: /(?:messages|prompt|system|content)\s*[=:][^\n;]*(?:AKIA[A-Z0-9]{16}|aws[_-]?(?:secret|access)[_-]?key)/gi,
154
230
  severity: 'critical',
155
231
  description: 'AWS credentials detected in prompt content.',
156
232
  suggestedFix: 'Remove credentials from prompts. Use IAM roles or environment variables instead.',
@@ -158,7 +234,7 @@ const SECRETS_IN_PROMPTS_PATTERNS: PromptHygienePattern[] = [
158
234
  // Database URLs with credentials
159
235
  {
160
236
  name: 'Database credentials in prompt',
161
- pattern: /(?:messages|prompt|system|content).*(?:mongodb|postgres|mysql|redis):\/\/[^:]+:[^@]+@/gi,
237
+ pattern: /(?:messages|prompt|system|content)[^\n]*(?:mongodb|postgres|mysql|redis):\/\/[^:]+:[^@]+@/gi,
162
238
  severity: 'critical',
163
239
  description: 'Database connection string with credentials in prompt. This exposes database access.',
164
240
  suggestedFix: 'Never include connection strings in prompts. Reference data by ID instead.',
@@ -166,7 +242,7 @@ const SECRETS_IN_PROMPTS_PATTERNS: PromptHygienePattern[] = [
166
242
  // Passwords in prompt context
167
243
  {
168
244
  name: 'Password in prompt content',
169
- pattern: /(?:messages|prompt|content)\s*[=:].*(?:password|passwd|pwd)\s*[:=]\s*['"`][^'"`]{8,}/gi,
245
+ pattern: /(?:messages|prompt|content)\s*[=:][^\n]*(?:password|passwd|pwd)\s*[:=]\s*['"`][^'"`]{8,}/gi,
170
246
  severity: 'high',
171
247
  description: 'Password appears in prompt content. This may be logged or exposed to model providers.',
172
248
  suggestedFix: 'Remove passwords from prompts. Use authentication tokens or session references instead.',
@@ -174,7 +250,7 @@ const SECRETS_IN_PROMPTS_PATTERNS: PromptHygienePattern[] = [
174
250
  // Private keys
175
251
  {
176
252
  name: 'Private key in prompt',
177
- pattern: /(?:messages|prompt|content).*(?:-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----)/gi,
253
+ pattern: /(?:messages|prompt|content)[^\n]*(?:-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----)/gi,
178
254
  severity: 'critical',
179
255
  description: 'Private key material detected in prompt context.',
180
256
  suggestedFix: 'Never include private keys in prompts. Sign data server-side instead.',
@@ -182,13 +258,342 @@ const SECRETS_IN_PROMPTS_PATTERNS: PromptHygienePattern[] = [
182
258
  // Generic token patterns
183
259
  {
184
260
  name: 'Access token in prompt',
185
- pattern: /(?:messages|prompt|content)\s*[=:].*(?:access[_-]?token|auth[_-]?token|bearer)\s*[:=]\s*['"`][a-zA-Z0-9_.-]{20,}/gi,
261
+ pattern: /(?:messages|prompt|content)\s*[=:][^\n]*(?:access[_-]?token|auth[_-]?token|bearer)\s*[:=]\s*['"`][a-zA-Z0-9_.-]{20,}/gi,
186
262
  severity: 'high',
187
263
  description: 'Access token detected in prompt content. Tokens in prompts risk exposure.',
188
264
  suggestedFix: 'Do not include tokens in prompts. Pass token context through secure server-side channels.',
189
265
  },
190
266
  ]
191
267
 
268
+ // ============================================================================
269
+ // Variable Flow Detection - Secrets flowing into prompts
270
+ // ============================================================================
271
+
272
+ /**
273
+ * Patterns for detecting secret variable declarations
274
+ */
275
+ const SECRET_VARIABLE_PATTERNS = [
276
+ // Direct assignment patterns
277
+ /(?:const|let|var)\s+(\w*(?:key|token|secret|password|credential|apiKey|authToken|accessToken)\w*)\s*=\s*['"`]([^'"`]{16,})['"`]/gi,
278
+ // Object property patterns
279
+ /(\w*(?:key|token|secret|password|credential|apiKey|authToken|accessToken)\w*)\s*:\s*['"`]([^'"`]{16,})['"`]/gi,
280
+ ]
281
+
282
+ /**
283
+ * Patterns for detecting prompt variable usage
284
+ */
285
+ const PROMPT_USAGE_PATTERNS = [
286
+ // Template literal interpolation
287
+ /`[^`]*\$\{(\w+)\}[^`]*`/g,
288
+ // String concatenation
289
+ /\+\s*(\w+)\s*(?:\+|$)/g,
290
+ // f-string interpolation (Python)
291
+ /f['"][^'"]*\{(\w+)\}[^'"]*['"]/g,
292
+ // Format string
293
+ /\.format\s*\([^)]*(\w+)[^)]*\)/g,
294
+ ]
295
+
296
+ /**
297
+ * Check if a variable name suggests it contains a secret
298
+ */
299
+ function isSecretVariableName(varName: string): boolean {
300
+ const secretIndicators = [
301
+ /api[_-]?key/i,
302
+ /secret[_-]?key/i,
303
+ /access[_-]?token/i,
304
+ /auth[_-]?token/i,
305
+ /password/i,
306
+ /credential/i,
307
+ /private[_-]?key/i,
308
+ /bearer/i,
309
+ /jwt/i,
310
+ /oauth/i,
311
+ /^sk_/i,
312
+ /^pk_/i,
313
+ /token$/i,
314
+ /key$/i,
315
+ /secret$/i,
316
+ ]
317
+ return secretIndicators.some(p => p.test(varName))
318
+ }
319
+
320
+ /**
321
+ * Detect secrets flowing from variables into prompts (variable indirection)
322
+ */
323
+ function detectSecretVariableFlow(
324
+ content: string,
325
+ filePath: string,
326
+ isTestFile: boolean
327
+ ): Vulnerability[] {
328
+ const vulnerabilities: Vulnerability[] = []
329
+ const lines = content.split('\n')
330
+
331
+ // First pass: collect all secret variable declarations
332
+ const secretVariables = new Map<string, { line: number; value: string }>()
333
+
334
+ for (let i = 0; i < lines.length; i++) {
335
+ const line = lines[i]
336
+ if (isComment(line)) continue
337
+
338
+ for (const pattern of SECRET_VARIABLE_PATTERNS) {
339
+ const regex = new RegExp(pattern.source, pattern.flags)
340
+ let match
341
+ while ((match = regex.exec(line)) !== null) {
342
+ const varName = match[1]
343
+ const value = match[2]
344
+
345
+ // Check if variable name suggests it's a secret
346
+ if (isSecretVariableName(varName)) {
347
+ secretVariables.set(varName, { line: i + 1, value })
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ // Second pass: find where these variables flow into prompts
354
+ const promptContextPatterns = [
355
+ /(?:system|prompt|message|content)\s*[:=]/i,
356
+ /role:\s*['"`](?:system|user|assistant)['"`]/i,
357
+ /\.chat\.completions?\.create/i,
358
+ /\.messages\.create/i,
359
+ /messages\s*:\s*\[/i,
360
+ ]
361
+
362
+ for (let i = 0; i < lines.length; i++) {
363
+ const line = lines[i]
364
+ if (isComment(line)) continue
365
+
366
+ // Check if this line or nearby lines are in prompt context
367
+ const contextWindow = lines.slice(Math.max(0, i - 5), Math.min(lines.length, i + 5)).join('\n')
368
+ const isPromptContext = promptContextPatterns.some(p => p.test(contextWindow))
369
+
370
+ if (!isPromptContext) continue
371
+
372
+ // Check for template interpolation of secret variables
373
+ const templateMatch = line.match(/\$\{(\w+)\}/)
374
+ if (templateMatch) {
375
+ const varName = templateMatch[1]
376
+ if (secretVariables.has(varName)) {
377
+ const secretInfo = secretVariables.get(varName)!
378
+ let severity: VulnerabilitySeverity = 'high'
379
+ let description = `Secret variable '${varName}' (defined at line ${secretInfo.line}) is interpolated into LLM prompt. This exposes the secret to the model provider.`
380
+
381
+ if (isTestFile) {
382
+ severity = 'low'
383
+ description += ' (in test file)'
384
+ }
385
+
386
+ vulnerabilities.push({
387
+ id: `secret-flow-${filePath}-${i + 1}-${varName}`,
388
+ filePath,
389
+ lineNumber: i + 1,
390
+ lineContent: line.trim(),
391
+ severity,
392
+ category: 'hardcoded_secret',
393
+ title: `Secret variable '${varName}' in prompt`,
394
+ description,
395
+ suggestedFix: `Remove the secret from the prompt. If the AI needs to use an API, make the call server-side instead of passing credentials to the model.`,
396
+ confidence: 'medium',
397
+ layer: 2,
398
+ requiresAIValidation: true,
399
+ })
400
+ }
401
+ }
402
+
403
+ // Check for string concatenation with secret variables
404
+ for (const [varName] of secretVariables) {
405
+ if (line.includes(`+ ${varName}`) || line.includes(`${varName} +`) || line.includes(`+ ${varName} +`)) {
406
+ const secretInfo = secretVariables.get(varName)!
407
+ let severity: VulnerabilitySeverity = 'high'
408
+ let description = `Secret variable '${varName}' (defined at line ${secretInfo.line}) is concatenated into prompt. This exposes the secret to the model provider.`
409
+
410
+ if (isTestFile) {
411
+ severity = 'low'
412
+ description += ' (in test file)'
413
+ }
414
+
415
+ vulnerabilities.push({
416
+ id: `secret-concat-${filePath}-${i + 1}-${varName}`,
417
+ filePath,
418
+ lineNumber: i + 1,
419
+ lineContent: line.trim(),
420
+ severity,
421
+ category: 'hardcoded_secret',
422
+ title: `Secret variable '${varName}' concatenated in prompt`,
423
+ description,
424
+ suggestedFix: `Remove the secret from the prompt. If the AI needs to use an API, make the call server-side.`,
425
+ confidence: 'medium',
426
+ layer: 2,
427
+ requiresAIValidation: true,
428
+ })
429
+ }
430
+ }
431
+ }
432
+
433
+ return vulnerabilities
434
+ }
435
+
436
+ // ============================================================================
437
+ // Phase 2: Indirect Prompt Injection Detection
438
+ // ============================================================================
439
+
440
+ /**
441
+ * Check if content filtering/sanitization is present for external content
442
+ */
443
+ function hasContentFiltering(content: string, lineNumber: number): boolean {
444
+ const lines = content.split('\n')
445
+ const contextStart = Math.max(0, lineNumber - 20)
446
+ const contextEnd = Math.min(lines.length, lineNumber + 10)
447
+ const context = lines.slice(contextStart, contextEnd).join('\n')
448
+
449
+ const filteringPatterns = [
450
+ /filterContent|sanitizeContent|cleanContent/i,
451
+ /sanitizeContext|filterContext/i,
452
+ /contentModeration|moderateContent/i,
453
+ /stripInstructions|removeInstructions/i,
454
+ /escapePrompt|sanitizePrompt/i,
455
+ /validateInput|inputValidation/i,
456
+ ]
457
+
458
+ return filteringPatterns.some(p => p.test(context))
459
+ }
460
+
461
+ /**
462
+ * Check if proper delimiters are used for external content
463
+ */
464
+ function hasExternalContentDelimiters(content: string, lineNumber: number): boolean {
465
+ const lines = content.split('\n')
466
+ const contextStart = Math.max(0, lineNumber - 15)
467
+ const contextEnd = Math.min(lines.length, lineNumber + 15)
468
+ const context = lines.slice(contextStart, contextEnd).join('\n')
469
+
470
+ const delimiterPatterns = [
471
+ /<context>|<\/context>/i,
472
+ /<document>|<\/document>/i,
473
+ /<retrieved>|<\/retrieved>/i,
474
+ /<external>|<\/external>/i,
475
+ /```[^`]*context|context[^`]*```/i,
476
+ /---\s*(?:context|document|retrieved)/i,
477
+ /\[CONTEXT\]|\[\/CONTEXT\]/i,
478
+ /\[DOCUMENT\]|\[\/DOCUMENT\]/i,
479
+ ]
480
+
481
+ return delimiterPatterns.some(p => p.test(context))
482
+ }
483
+
484
+ /**
485
+ * Indirect prompt injection patterns - external content flowing to LLM context
486
+ */
487
+ const INDIRECT_INJECTION_PATTERNS: PromptHygienePattern[] = [
488
+ // ========== External Fetch to Prompt ==========
489
+ {
490
+ name: 'Fetched content in prompt',
491
+ // Pattern looks for: fetch() -> then/await -> result flows into messages/content
492
+ // Use word boundary \b to avoid matching function names like "validatedFetch"
493
+ // The pattern looks for: actual fetch call -> await/then -> use in LLM messages
494
+ pattern: /\bfetch\s*\(\s*[^)]+\)[\s\S]{0,80}(?:\.then|\.json)[\s\S]{0,150}(?:role:\s*['"`](?:system|user)['"`]|messages\s*:\s*\[)/gi,
495
+ severity: 'high',
496
+ description: 'Content fetched from external URL flows into LLM prompt. Malicious websites can embed instructions that hijack the model\'s behavior (indirect prompt injection).',
497
+ suggestedFix: 'Wrap external content with clear delimiters: <external_content>...</external_content>. Implement content filtering to strip instruction-like patterns.',
498
+ checkDelimiters: true,
499
+ },
500
+ {
501
+ name: 'HTTP response in system prompt',
502
+ pattern: /(?:axios|fetch|got|request)[\s\S]{0,150}(?:system|systemPrompt|instructions)\s*[:=+]/gi,
503
+ severity: 'high',
504
+ description: 'HTTP response content used in system prompt. External data in system prompts is especially dangerous as it can override model instructions.',
505
+ suggestedFix: 'Never put external content in system prompts. Use user messages with clear delimiters for context. Implement content sanitization.',
506
+ checkDelimiters: true,
507
+ },
508
+
509
+ // ========== RAG Vector Store to Prompt ==========
510
+ {
511
+ name: 'Vector store results in system message',
512
+ pattern: /(?:vectorStore|similaritySearch|query|search|retrieve)[\s\S]{0,200}role:\s*['"`]system['"`]/gi,
513
+ severity: 'high',
514
+ description: 'Vector store search results injected into system message. Poisoned documents in the corpus can hijack model behavior.',
515
+ suggestedFix: 'Place retrieved content in user messages, not system. Use delimiters: <retrieved_context>...</retrieved_context>. Implement document sanitization before indexing.',
516
+ checkDelimiters: true,
517
+ },
518
+ {
519
+ name: 'RAG retrieval directly in context',
520
+ pattern: /(?:retriever\.invoke|retrieve|getRelevantDocuments)\s*\([^)]*\)[\s\S]{0,150}(?:context|prompt|messages)/gi,
521
+ severity: 'high',
522
+ description: 'Retrieved documents flow directly into LLM context. Adversarial documents can contain prompt injection payloads.',
523
+ suggestedFix: 'Sanitize retrieved content before including in prompt. Use XML tags to clearly separate context from instructions.',
524
+ checkDelimiters: true,
525
+ },
526
+
527
+ // ========== Document Loading to LLM ==========
528
+ {
529
+ name: 'Loaded documents in LLM chain',
530
+ pattern: /(?:loadDocuments|DirectoryLoader|TextLoader|PDFLoader)[\s\S]{0,200}(?:chain|llm|invoke|call)/gi,
531
+ severity: 'high',
532
+ description: 'Documents loaded from files flow into LLM chain. Malicious files (PDFs, docs) can contain hidden prompt injection text.',
533
+ suggestedFix: 'Scan loaded documents for instruction-like patterns. Use separate document processing pipeline with content filtering.',
534
+ checkDelimiters: true,
535
+ },
536
+ {
537
+ name: 'Document content interpolated',
538
+ pattern: /\$\{.*(?:document|doc|file|page)(?:Content|Text|Data).*\}[\s\S]{0,50}(?:prompt|messages|llm)/gi,
539
+ severity: 'medium',
540
+ description: 'Document content interpolated into LLM prompt. Documents may contain adversarial instructions.',
541
+ suggestedFix: 'Wrap document content with delimiters: ```document\\n${content}\\n```. Implement text sanitization.',
542
+ checkDelimiters: true,
543
+ },
544
+
545
+ // ========== Web Scraping to Prompt ==========
546
+ {
547
+ name: 'Scraped content in prompt',
548
+ pattern: /(?:scrape|crawl|spider|puppeteer|playwright|cheerio)[\s\S]{0,200}(?:prompt|messages|context|content\s*:)/gi,
549
+ severity: 'high',
550
+ description: 'Web-scraped content flows into LLM prompt. Malicious websites can embed instructions in their HTML content.',
551
+ suggestedFix: 'Sanitize scraped content to remove instruction-like patterns. Use delimiters: <scraped_content url="...">...</scraped_content>',
552
+ checkDelimiters: true,
553
+ },
554
+ {
555
+ name: 'HTML content in LLM context',
556
+ // Pattern: Reading HTML (.innerHTML) and then using it in prompt/messages
557
+ // NOT: Writing LLM output TO innerHTML (that's output handling, different category)
558
+ // Look for: getting innerHTML value -> flowing to prompt context
559
+ pattern: /(?:\.innerHTML\s*[;,]|\.html\s*\(\s*\))[\s\S]{0,150}(?:role:\s*['"`](?:system|user)['"`]|messages\s*:\s*\[)/gi,
560
+ severity: 'medium',
561
+ description: 'HTML content from web pages used in LLM context. Web pages can contain hidden prompt injection in metadata, comments, or invisible text.',
562
+ suggestedFix: 'Extract only relevant text content. Filter out scripts, comments, and metadata. Use content sanitization.',
563
+ checkDelimiters: true,
564
+ },
565
+
566
+ // ========== Email/Message Content to Prompt ==========
567
+ {
568
+ name: 'Email content in prompt',
569
+ pattern: /(?:email|message|inbox)(?:Content|Body|Text)[\s\S]{0,150}(?:prompt|messages|llm|analyze)/gi,
570
+ severity: 'medium',
571
+ description: 'Email or message content flows into LLM prompt. Attackers can craft emails with embedded prompt injection.',
572
+ suggestedFix: 'Sanitize email content before LLM processing. Remove potentially malicious patterns. Use clear delimiters.',
573
+ checkDelimiters: true,
574
+ },
575
+
576
+ // ========== Database Content to Prompt ==========
577
+ {
578
+ name: 'Database record in system prompt',
579
+ pattern: /(?:findOne|findById|query|select)[\s\S]{0,150}(?:system|systemPrompt|instructions)\s*[:=]/gi,
580
+ severity: 'medium',
581
+ description: 'Database content used in system prompt. If users can modify database records, they can inject malicious instructions.',
582
+ suggestedFix: 'Keep system prompts static. Place database content in user messages with delimiters. Validate data before use.',
583
+ checkDelimiters: true,
584
+ },
585
+
586
+ // ========== Generic External Data Patterns ==========
587
+ {
588
+ name: 'External data concatenation',
589
+ pattern: /(?:externalData|fetchedContent|scrapedData|retrievedText)\s*\+[\s\S]{0,50}(?:prompt|system|instructions)/gi,
590
+ severity: 'medium',
591
+ description: 'External data concatenated with prompt content without clear separation.',
592
+ suggestedFix: 'Use structured prompts with XML/markdown delimiters to separate instructions from external content.',
593
+ checkDelimiters: true,
594
+ },
595
+ ]
596
+
192
597
  /**
193
598
  * Missing boundary patterns - prompts without clear user/system separation
194
599
  */
@@ -320,7 +725,7 @@ export function detectAIPromptHygiene(
320
725
  }
321
726
  }
322
727
 
323
- // Scan for secrets in prompts (B3)
728
+ // Scan for secrets in prompts (B3) - Original context-aware patterns
324
729
  for (const pattern of SECRETS_IN_PROMPTS_PATTERNS) {
325
730
  const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
326
731
  let match
@@ -336,6 +741,13 @@ export function detectAIPromptHygiene(
336
741
  const isEnvRef = /process\.env|import\.meta\.env|os\.environ|getenv/i.test(lineContent)
337
742
  if (isEnvRef) continue
338
743
 
744
+ // Skip test variable names
745
+ if (/(?:const|let|var)\s+(?:TEST|MOCK|EXAMPLE|DUMMY|FAKE|SAMPLE)[_A-Z0-9]*\s*=/i.test(lineContent)) continue
746
+ if (/(?:const|let|var)\s+\w*(?:test|mock|example|dummy|fake|sample)\w*\s*=/i.test(lineContent)) continue
747
+
748
+ // Skip placeholder/example values in the line
749
+ if (/example|sample|demo|placeholder|your[_-]?api[_-]?key/i.test(lineContent)) continue
750
+
339
751
  let severity = pattern.severity
340
752
  let description = pattern.description
341
753
 
@@ -362,6 +774,76 @@ export function detectAIPromptHygiene(
362
774
  }
363
775
  }
364
776
 
777
+ // ========== NEW: Direct secret detection with known prefixes ==========
778
+ // Scan for any known secret patterns anywhere in prompt-related code
779
+ const seenSecretLines = new Set<number>() // Avoid duplicates
780
+
781
+ for (const secretDef of KNOWN_SECRET_PREFIXES) {
782
+ const regex = new RegExp(secretDef.pattern.source, secretDef.pattern.flags)
783
+ let match
784
+
785
+ while ((match = regex.exec(content)) !== null) {
786
+ const lineNumber = content.substring(0, match.index).split('\n').length
787
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
788
+
789
+ // Skip if already reported on this line
790
+ const lineKey = `${lineNumber}-${secretDef.name}`
791
+ if (seenSecretLines.has(lineNumber)) continue
792
+ seenSecretLines.add(lineNumber)
793
+
794
+ // Skip comments
795
+ if (isComment(lineContent)) continue
796
+
797
+ // Skip env var references
798
+ if (/process\.env|import\.meta\.env|os\.environ|getenv/i.test(lineContent)) continue
799
+
800
+ // Skip obvious placeholders/examples in the value
801
+ const matchValue = match[0]
802
+ if (/example|sample|demo|dummy|fake|mock|your[_-]|placeholder/i.test(matchValue)) continue
803
+ if (/example|sample|demo|placeholder/i.test(lineContent)) continue
804
+
805
+ // Skip values that contain "test" right after the prefix (e.g., sk-test..., ghp_test...)
806
+ // These are clearly test/development keys, not production secrets
807
+ if (/^(sk-|ghp_|gho_|sk_live_|sk_test_|xoxb-|SG\.)test/i.test(matchValue)) continue
808
+ if (/[-_]test[-_0-9]/i.test(matchValue)) continue
809
+
810
+ // Skip test variable names (e.g., TEST_API_KEY, MOCK_SECRET)
811
+ if (/(?:const|let|var)\s+(?:TEST|MOCK|EXAMPLE|DUMMY|FAKE|SAMPLE)[_A-Z0-9]*\s*=/i.test(lineContent)) continue
812
+
813
+ // Skip if variable name contains test/mock/example (broader check)
814
+ if (/(?:const|let|var)\s+\w*(?:test|mock|example|dummy|fake|sample)\w*\s*=/i.test(lineContent)) continue
815
+
816
+ let severity: VulnerabilitySeverity = secretDef.severity
817
+ let description = `${secretDef.name} detected in LLM-related code. This secret may be exposed to the model provider, logged, or cached.`
818
+
819
+ // Downgrade test files
820
+ if (isTestFile) {
821
+ severity = severity === 'critical' ? 'medium' : 'low'
822
+ description += ' (in test file)'
823
+ }
824
+
825
+ vulnerabilities.push({
826
+ id: `ai-direct-secret-${filePath}-${lineNumber}-${secretDef.name.replace(/\s+/g, '-')}`,
827
+ filePath,
828
+ lineNumber,
829
+ lineContent,
830
+ severity,
831
+ category: 'hardcoded_secret',
832
+ title: `${secretDef.name} in LLM context`,
833
+ description,
834
+ suggestedFix: 'Remove the hardcoded secret. Use environment variables server-side. Never expose secrets to LLM prompts.',
835
+ confidence: 'high',
836
+ layer: 2,
837
+ requiresAIValidation: false,
838
+ })
839
+ }
840
+ }
841
+
842
+ // ========== NEW: Variable flow detection ==========
843
+ // Detect secrets flowing from variables into prompts
844
+ const flowVulns = detectSecretVariableFlow(content, filePath, isTestFile)
845
+ vulnerabilities.push(...flowVulns)
846
+
365
847
  // Scan for missing boundary patterns (B1 continued)
366
848
  for (const pattern of MISSING_BOUNDARY_PATTERNS) {
367
849
  const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
@@ -404,6 +886,62 @@ export function detectAIPromptHygiene(
404
886
  }
405
887
  }
406
888
 
889
+ // Scan for indirect prompt injection patterns (Phase 2)
890
+ for (const pattern of INDIRECT_INJECTION_PATTERNS) {
891
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
892
+ let match
893
+
894
+ while ((match = regex.exec(content)) !== null) {
895
+ const lineNumber = content.substring(0, match.index).split('\n').length
896
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
897
+
898
+ // Skip comments
899
+ if (isComment(lineContent)) continue
900
+
901
+ let severity = pattern.severity
902
+ let description = pattern.description
903
+
904
+ // Check for content filtering/sanitization
905
+ const hasFiltering = hasContentFiltering(content, lineNumber)
906
+ const hasDelimiters = hasExternalContentDelimiters(content, lineNumber)
907
+
908
+ if (hasFiltering && hasDelimiters) {
909
+ // Both mitigations present - fully mitigated
910
+ severity = 'info'
911
+ description += ' (Content filtering and delimiters detected - mitigated.)'
912
+ } else if (hasFiltering) {
913
+ // Partial mitigation - filtering present
914
+ severity = severity === 'high' ? 'medium' : 'low'
915
+ description += ' (Content filtering detected.)'
916
+ } else if (hasDelimiters) {
917
+ // Partial mitigation - delimiters present
918
+ severity = severity === 'high' ? 'medium' : 'low'
919
+ description += ' (External content delimiters detected.)'
920
+ }
921
+
922
+ // Downgrade test files
923
+ if (isTestFile) {
924
+ severity = 'info'
925
+ description += ' (in test file)'
926
+ }
927
+
928
+ vulnerabilities.push({
929
+ id: `ai-indirect-injection-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
930
+ filePath,
931
+ lineNumber,
932
+ lineContent,
933
+ severity,
934
+ category: 'ai_prompt_injection',
935
+ title: pattern.name + ' (Indirect Injection)',
936
+ description,
937
+ suggestedFix: pattern.suggestedFix,
938
+ confidence: severity === 'info' ? 'low' : 'medium',
939
+ layer: 2,
940
+ requiresAIValidation: severity !== 'info',
941
+ })
942
+ }
943
+ }
944
+
407
945
  return vulnerabilities
408
946
  }
409
947