raxe 0.4.6__py3-none-any.whl

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 (668) hide show
  1. raxe/__init__.py +101 -0
  2. raxe/application/__init__.py +48 -0
  3. raxe/application/ab_testing.py +170 -0
  4. raxe/application/analytics/__init__.py +30 -0
  5. raxe/application/analytics/achievement_service.py +444 -0
  6. raxe/application/analytics/repositories.py +172 -0
  7. raxe/application/analytics/retention_service.py +267 -0
  8. raxe/application/analytics/statistics_service.py +419 -0
  9. raxe/application/analytics/streak_service.py +283 -0
  10. raxe/application/apply_policy.py +291 -0
  11. raxe/application/eager_l2.py +503 -0
  12. raxe/application/preloader.py +353 -0
  13. raxe/application/scan_merger.py +321 -0
  14. raxe/application/scan_pipeline.py +1059 -0
  15. raxe/application/scan_pipeline_async.py +403 -0
  16. raxe/application/session_tracker.py +458 -0
  17. raxe/application/telemetry_manager.py +357 -0
  18. raxe/application/telemetry_orchestrator.py +1210 -0
  19. raxe/async_sdk/__init__.py +34 -0
  20. raxe/async_sdk/cache.py +286 -0
  21. raxe/async_sdk/client.py +556 -0
  22. raxe/async_sdk/wrappers/__init__.py +23 -0
  23. raxe/async_sdk/wrappers/openai.py +238 -0
  24. raxe/cli/__init__.py +21 -0
  25. raxe/cli/auth.py +1047 -0
  26. raxe/cli/branding.py +235 -0
  27. raxe/cli/config.py +334 -0
  28. raxe/cli/custom_rules.py +458 -0
  29. raxe/cli/doctor.py +686 -0
  30. raxe/cli/error_handler.py +665 -0
  31. raxe/cli/event.py +648 -0
  32. raxe/cli/exit_codes.py +57 -0
  33. raxe/cli/expiry_warning.py +302 -0
  34. raxe/cli/export.py +183 -0
  35. raxe/cli/history.py +247 -0
  36. raxe/cli/l2_formatter.py +872 -0
  37. raxe/cli/main.py +1137 -0
  38. raxe/cli/models.py +590 -0
  39. raxe/cli/output.py +403 -0
  40. raxe/cli/privacy.py +84 -0
  41. raxe/cli/profiler.py +262 -0
  42. raxe/cli/progress.py +379 -0
  43. raxe/cli/progress_context.py +101 -0
  44. raxe/cli/repl.py +394 -0
  45. raxe/cli/rules.py +542 -0
  46. raxe/cli/setup_wizard.py +721 -0
  47. raxe/cli/stats.py +292 -0
  48. raxe/cli/suppress.py +501 -0
  49. raxe/cli/telemetry.py +1384 -0
  50. raxe/cli/test.py +130 -0
  51. raxe/cli/tune.py +315 -0
  52. raxe/cli/validate.py +218 -0
  53. raxe/domain/__init__.py +30 -0
  54. raxe/domain/analytics/__init__.py +97 -0
  55. raxe/domain/analytics/achievements.py +306 -0
  56. raxe/domain/analytics/models.py +120 -0
  57. raxe/domain/analytics/retention.py +168 -0
  58. raxe/domain/analytics/statistics.py +207 -0
  59. raxe/domain/analytics/streaks.py +173 -0
  60. raxe/domain/engine/__init__.py +15 -0
  61. raxe/domain/engine/executor.py +396 -0
  62. raxe/domain/engine/matcher.py +212 -0
  63. raxe/domain/inline_suppression.py +176 -0
  64. raxe/domain/ml/__init__.py +133 -0
  65. raxe/domain/ml/embedding_cache.py +309 -0
  66. raxe/domain/ml/gemma_detector.py +921 -0
  67. raxe/domain/ml/gemma_models.py +346 -0
  68. raxe/domain/ml/l2_config.py +428 -0
  69. raxe/domain/ml/l2_output_schema.py +443 -0
  70. raxe/domain/ml/manifest_loader.py +309 -0
  71. raxe/domain/ml/manifest_schema.py +345 -0
  72. raxe/domain/ml/model_metadata.py +263 -0
  73. raxe/domain/ml/model_registry.py +786 -0
  74. raxe/domain/ml/protocol.py +282 -0
  75. raxe/domain/ml/scoring_models.py +419 -0
  76. raxe/domain/ml/stub_detector.py +397 -0
  77. raxe/domain/ml/threat_scorer.py +757 -0
  78. raxe/domain/ml/tokenizer_registry.py +372 -0
  79. raxe/domain/ml/voting/__init__.py +89 -0
  80. raxe/domain/ml/voting/config.py +595 -0
  81. raxe/domain/ml/voting/engine.py +465 -0
  82. raxe/domain/ml/voting/head_voters.py +378 -0
  83. raxe/domain/ml/voting/models.py +222 -0
  84. raxe/domain/models.py +82 -0
  85. raxe/domain/packs/__init__.py +17 -0
  86. raxe/domain/packs/models.py +304 -0
  87. raxe/domain/policies/__init__.py +20 -0
  88. raxe/domain/policies/evaluator.py +212 -0
  89. raxe/domain/policies/models.py +223 -0
  90. raxe/domain/rules/__init__.py +32 -0
  91. raxe/domain/rules/custom.py +286 -0
  92. raxe/domain/rules/models.py +273 -0
  93. raxe/domain/rules/schema.py +166 -0
  94. raxe/domain/rules/validator.py +556 -0
  95. raxe/domain/suppression.py +801 -0
  96. raxe/domain/suppression_factory.py +174 -0
  97. raxe/domain/telemetry/__init__.py +116 -0
  98. raxe/domain/telemetry/backpressure.py +424 -0
  99. raxe/domain/telemetry/event_creator.py +362 -0
  100. raxe/domain/telemetry/events.py +1282 -0
  101. raxe/domain/telemetry/priority.py +263 -0
  102. raxe/domain/telemetry/scan_telemetry_builder.py +670 -0
  103. raxe/infrastructure/__init__.py +25 -0
  104. raxe/infrastructure/analytics/__init__.py +18 -0
  105. raxe/infrastructure/analytics/aggregator.py +484 -0
  106. raxe/infrastructure/analytics/aggregator_optimized.py +184 -0
  107. raxe/infrastructure/analytics/engine.py +748 -0
  108. raxe/infrastructure/analytics/repository.py +409 -0
  109. raxe/infrastructure/analytics/streaks.py +467 -0
  110. raxe/infrastructure/analytics/views.py +178 -0
  111. raxe/infrastructure/cloud/__init__.py +9 -0
  112. raxe/infrastructure/config/__init__.py +56 -0
  113. raxe/infrastructure/config/endpoints.py +641 -0
  114. raxe/infrastructure/config/scan_config.py +352 -0
  115. raxe/infrastructure/config/yaml_config.py +459 -0
  116. raxe/infrastructure/database/__init__.py +10 -0
  117. raxe/infrastructure/database/connection.py +200 -0
  118. raxe/infrastructure/database/models.py +325 -0
  119. raxe/infrastructure/database/scan_history.py +764 -0
  120. raxe/infrastructure/ml/__init__.py +0 -0
  121. raxe/infrastructure/ml/download_progress.py +438 -0
  122. raxe/infrastructure/ml/model_downloader.py +457 -0
  123. raxe/infrastructure/models/__init__.py +16 -0
  124. raxe/infrastructure/models/discovery.py +461 -0
  125. raxe/infrastructure/packs/__init__.py +13 -0
  126. raxe/infrastructure/packs/loader.py +407 -0
  127. raxe/infrastructure/packs/registry.py +381 -0
  128. raxe/infrastructure/policies/__init__.py +16 -0
  129. raxe/infrastructure/policies/api_client.py +256 -0
  130. raxe/infrastructure/policies/validator.py +227 -0
  131. raxe/infrastructure/policies/yaml_loader.py +250 -0
  132. raxe/infrastructure/rules/__init__.py +18 -0
  133. raxe/infrastructure/rules/custom_loader.py +224 -0
  134. raxe/infrastructure/rules/versioning.py +222 -0
  135. raxe/infrastructure/rules/yaml_loader.py +286 -0
  136. raxe/infrastructure/security/__init__.py +31 -0
  137. raxe/infrastructure/security/auth.py +145 -0
  138. raxe/infrastructure/security/policy_validator.py +124 -0
  139. raxe/infrastructure/security/signatures.py +171 -0
  140. raxe/infrastructure/suppression/__init__.py +36 -0
  141. raxe/infrastructure/suppression/composite_repository.py +154 -0
  142. raxe/infrastructure/suppression/sqlite_repository.py +231 -0
  143. raxe/infrastructure/suppression/yaml_composite_repository.py +156 -0
  144. raxe/infrastructure/suppression/yaml_repository.py +510 -0
  145. raxe/infrastructure/telemetry/__init__.py +79 -0
  146. raxe/infrastructure/telemetry/acquisition.py +179 -0
  147. raxe/infrastructure/telemetry/config.py +254 -0
  148. raxe/infrastructure/telemetry/credential_store.py +947 -0
  149. raxe/infrastructure/telemetry/dual_queue.py +1123 -0
  150. raxe/infrastructure/telemetry/flush_helper.py +343 -0
  151. raxe/infrastructure/telemetry/flush_scheduler.py +776 -0
  152. raxe/infrastructure/telemetry/health_client.py +394 -0
  153. raxe/infrastructure/telemetry/hook.py +347 -0
  154. raxe/infrastructure/telemetry/queue.py +520 -0
  155. raxe/infrastructure/telemetry/sender.py +476 -0
  156. raxe/infrastructure/tracking/__init__.py +13 -0
  157. raxe/infrastructure/tracking/usage.py +389 -0
  158. raxe/integrations/__init__.py +55 -0
  159. raxe/integrations/availability.py +143 -0
  160. raxe/integrations/registry.py +122 -0
  161. raxe/integrations/utils.py +135 -0
  162. raxe/mcp/__init__.py +62 -0
  163. raxe/mcp/cli.py +97 -0
  164. raxe/mcp/server.py +409 -0
  165. raxe/monitoring/__init__.py +51 -0
  166. raxe/monitoring/metrics.py +372 -0
  167. raxe/monitoring/profiler.py +388 -0
  168. raxe/monitoring/server.py +136 -0
  169. raxe/packs/core/v1.0.0/pack.yaml +1394 -0
  170. raxe/packs/core/v1.0.0/rules/PI/pi-001@1.0.0.yaml +49 -0
  171. raxe/packs/core/v1.0.0/rules/PI/pi-006@1.0.0.yaml +48 -0
  172. raxe/packs/core/v1.0.0/rules/PI/pi-014@1.0.0.yaml +54 -0
  173. raxe/packs/core/v1.0.0/rules/PI/pi-017@1.0.0.yaml +52 -0
  174. raxe/packs/core/v1.0.0/rules/PI/pi-022@1.0.0.yaml +67 -0
  175. raxe/packs/core/v1.0.0/rules/PI/pi-023@1.0.0.yaml +91 -0
  176. raxe/packs/core/v1.0.0/rules/PI/pi-024@1.0.0.yaml +80 -0
  177. raxe/packs/core/v1.0.0/rules/PI/pi-025@1.0.0.yaml +81 -0
  178. raxe/packs/core/v1.0.0/rules/PI/pi-026@1.0.0.yaml +50 -0
  179. raxe/packs/core/v1.0.0/rules/PI/pi-027@1.0.0.yaml +77 -0
  180. raxe/packs/core/v1.0.0/rules/PI/pi-028@1.0.0.yaml +52 -0
  181. raxe/packs/core/v1.0.0/rules/PI/pi-029@1.0.0.yaml +51 -0
  182. raxe/packs/core/v1.0.0/rules/PI/pi-030@1.0.0.yaml +55 -0
  183. raxe/packs/core/v1.0.0/rules/PI/pi-033@1.0.0.yaml +50 -0
  184. raxe/packs/core/v1.0.0/rules/PI/pi-034@1.0.0.yaml +50 -0
  185. raxe/packs/core/v1.0.0/rules/PI/pi-035@1.0.0.yaml +50 -0
  186. raxe/packs/core/v1.0.0/rules/PI/pi-046@1.0.0.yaml +50 -0
  187. raxe/packs/core/v1.0.0/rules/PI/pi-047@1.0.0.yaml +50 -0
  188. raxe/packs/core/v1.0.0/rules/PI/pi-048@1.0.0.yaml +50 -0
  189. raxe/packs/core/v1.0.0/rules/PI/pi-049@1.0.0.yaml +50 -0
  190. raxe/packs/core/v1.0.0/rules/PI/pi-050@1.0.0.yaml +50 -0
  191. raxe/packs/core/v1.0.0/rules/PI/pi-068@1.0.0.yaml +50 -0
  192. raxe/packs/core/v1.0.0/rules/PI/pi-078@1.0.0.yaml +50 -0
  193. raxe/packs/core/v1.0.0/rules/PI/pi-2001@1.0.0.yaml +35 -0
  194. raxe/packs/core/v1.0.0/rules/PI/pi-2004@1.0.0.yaml +39 -0
  195. raxe/packs/core/v1.0.0/rules/PI/pi-201@1.0.0.yaml +43 -0
  196. raxe/packs/core/v1.0.0/rules/PI/pi-202@1.0.0.yaml +47 -0
  197. raxe/packs/core/v1.0.0/rules/PI/pi-203@1.0.0.yaml +46 -0
  198. raxe/packs/core/v1.0.0/rules/PI/pi-3007@1.0.0.yaml +44 -0
  199. raxe/packs/core/v1.0.0/rules/PI/pi-3016@1.0.0.yaml +44 -0
  200. raxe/packs/core/v1.0.0/rules/PI/pi-3026@1.0.0.yaml +39 -0
  201. raxe/packs/core/v1.0.0/rules/PI/pi-3027@1.0.0.yaml +64 -0
  202. raxe/packs/core/v1.0.0/rules/PI/pi-3028@1.0.0.yaml +51 -0
  203. raxe/packs/core/v1.0.0/rules/PI/pi-3029@1.0.0.yaml +53 -0
  204. raxe/packs/core/v1.0.0/rules/PI/pi-3030@1.0.0.yaml +50 -0
  205. raxe/packs/core/v1.0.0/rules/PI/pi-3031@1.0.0.yaml +50 -0
  206. raxe/packs/core/v1.0.0/rules/PI/pi-3032@1.0.0.yaml +50 -0
  207. raxe/packs/core/v1.0.0/rules/PI/pi-3033@1.0.0.yaml +56 -0
  208. raxe/packs/core/v1.0.0/rules/PI/pi-3034@1.0.0.yaml +50 -0
  209. raxe/packs/core/v1.0.0/rules/PI/pi-79@1.0.0.yaml +38 -0
  210. raxe/packs/core/v1.0.0/rules/PI/pi-80@1.0.0.yaml +38 -0
  211. raxe/packs/core/v1.0.0/rules/PI/pi-81@1.0.0.yaml +38 -0
  212. raxe/packs/core/v1.0.0/rules/PI/pi-82@1.0.0.yaml +38 -0
  213. raxe/packs/core/v1.0.0/rules/PI/pi-83@1.0.0.yaml +38 -0
  214. raxe/packs/core/v1.0.0/rules/PI/pi-84@1.0.0.yaml +38 -0
  215. raxe/packs/core/v1.0.0/rules/PI/pi-85@1.0.0.yaml +38 -0
  216. raxe/packs/core/v1.0.0/rules/PI/pi-86@1.0.0.yaml +38 -0
  217. raxe/packs/core/v1.0.0/rules/PI/pi-87@1.0.0.yaml +38 -0
  218. raxe/packs/core/v1.0.0/rules/PI/pi-88@1.0.0.yaml +38 -0
  219. raxe/packs/core/v1.0.0/rules/PI/pi-89@1.0.0.yaml +38 -0
  220. raxe/packs/core/v1.0.0/rules/PI/pi-90@1.0.0.yaml +38 -0
  221. raxe/packs/core/v1.0.0/rules/PI/pi-91@1.0.0.yaml +38 -0
  222. raxe/packs/core/v1.0.0/rules/PI/pi-92@1.0.0.yaml +38 -0
  223. raxe/packs/core/v1.0.0/rules/PI/pi-93@1.0.0.yaml +38 -0
  224. raxe/packs/core/v1.0.0/rules/PI/pi-94@1.0.0.yaml +38 -0
  225. raxe/packs/core/v1.0.0/rules/PI/pi-95@1.0.0.yaml +38 -0
  226. raxe/packs/core/v1.0.0/rules/PI/pi-96@1.0.0.yaml +38 -0
  227. raxe/packs/core/v1.0.0/rules/PI/pi-97@1.0.0.yaml +38 -0
  228. raxe/packs/core/v1.0.0/rules/PI/pi-98@1.0.0.yaml +38 -0
  229. raxe/packs/core/v1.0.0/rules/cmd/cmd-001@1.0.0.yaml +48 -0
  230. raxe/packs/core/v1.0.0/rules/cmd/cmd-007@1.0.0.yaml +48 -0
  231. raxe/packs/core/v1.0.0/rules/cmd/cmd-015@1.0.0.yaml +56 -0
  232. raxe/packs/core/v1.0.0/rules/cmd/cmd-016@1.0.0.yaml +46 -0
  233. raxe/packs/core/v1.0.0/rules/cmd/cmd-017@1.0.0.yaml +57 -0
  234. raxe/packs/core/v1.0.0/rules/cmd/cmd-021@1.0.0.yaml +46 -0
  235. raxe/packs/core/v1.0.0/rules/cmd/cmd-022@1.0.0.yaml +46 -0
  236. raxe/packs/core/v1.0.0/rules/cmd/cmd-023@1.0.0.yaml +78 -0
  237. raxe/packs/core/v1.0.0/rules/cmd/cmd-024@1.0.0.yaml +46 -0
  238. raxe/packs/core/v1.0.0/rules/cmd/cmd-025@1.0.0.yaml +93 -0
  239. raxe/packs/core/v1.0.0/rules/cmd/cmd-026@1.0.0.yaml +81 -0
  240. raxe/packs/core/v1.0.0/rules/cmd/cmd-027@1.0.0.yaml +82 -0
  241. raxe/packs/core/v1.0.0/rules/cmd/cmd-028@1.0.0.yaml +46 -0
  242. raxe/packs/core/v1.0.0/rules/cmd/cmd-033@1.0.0.yaml +48 -0
  243. raxe/packs/core/v1.0.0/rules/cmd/cmd-036@1.0.0.yaml +47 -0
  244. raxe/packs/core/v1.0.0/rules/cmd/cmd-037@1.0.0.yaml +44 -0
  245. raxe/packs/core/v1.0.0/rules/cmd/cmd-052@1.0.0.yaml +43 -0
  246. raxe/packs/core/v1.0.0/rules/cmd/cmd-054@1.0.0.yaml +44 -0
  247. raxe/packs/core/v1.0.0/rules/cmd/cmd-056@1.0.0.yaml +43 -0
  248. raxe/packs/core/v1.0.0/rules/cmd/cmd-065@1.0.0.yaml +46 -0
  249. raxe/packs/core/v1.0.0/rules/cmd/cmd-075@1.0.0.yaml +45 -0
  250. raxe/packs/core/v1.0.0/rules/cmd/cmd-079@1.0.0.yaml +44 -0
  251. raxe/packs/core/v1.0.0/rules/cmd/cmd-1080@1.0.0.yaml +41 -0
  252. raxe/packs/core/v1.0.0/rules/cmd/cmd-1090@1.0.0.yaml +41 -0
  253. raxe/packs/core/v1.0.0/rules/cmd/cmd-1104@1.0.0.yaml +44 -0
  254. raxe/packs/core/v1.0.0/rules/cmd/cmd-1105@1.0.0.yaml +41 -0
  255. raxe/packs/core/v1.0.0/rules/cmd/cmd-1112@1.0.0.yaml +44 -0
  256. raxe/packs/core/v1.0.0/rules/cmd/cmd-201@1.0.0.yaml +47 -0
  257. raxe/packs/core/v1.0.0/rules/cmd/cmd-202@1.0.0.yaml +42 -0
  258. raxe/packs/core/v1.0.0/rules/cmd/cmd-203@1.0.0.yaml +43 -0
  259. raxe/packs/core/v1.0.0/rules/cmd/cmd-204@1.0.0.yaml +47 -0
  260. raxe/packs/core/v1.0.0/rules/cmd/cmd-205@1.0.0.yaml +44 -0
  261. raxe/packs/core/v1.0.0/rules/cmd/cmd-206@1.0.0.yaml +47 -0
  262. raxe/packs/core/v1.0.0/rules/cmd/cmd-207@1.0.0.yaml +46 -0
  263. raxe/packs/core/v1.0.0/rules/cmd/cmd-208@1.0.0.yaml +42 -0
  264. raxe/packs/core/v1.0.0/rules/cmd/cmd-209@1.0.0.yaml +38 -0
  265. raxe/packs/core/v1.0.0/rules/cmd/cmd-210@1.0.0.yaml +38 -0
  266. raxe/packs/core/v1.0.0/rules/cmd/cmd-211@1.0.0.yaml +38 -0
  267. raxe/packs/core/v1.0.0/rules/cmd/cmd-212@1.0.0.yaml +38 -0
  268. raxe/packs/core/v1.0.0/rules/cmd/cmd-213@1.0.0.yaml +38 -0
  269. raxe/packs/core/v1.0.0/rules/cmd/cmd-214@1.0.0.yaml +38 -0
  270. raxe/packs/core/v1.0.0/rules/cmd/cmd-215@1.0.0.yaml +38 -0
  271. raxe/packs/core/v1.0.0/rules/cmd/cmd-216@1.0.0.yaml +38 -0
  272. raxe/packs/core/v1.0.0/rules/cmd/cmd-217@1.0.0.yaml +38 -0
  273. raxe/packs/core/v1.0.0/rules/cmd/cmd-218@1.0.0.yaml +38 -0
  274. raxe/packs/core/v1.0.0/rules/cmd/cmd-219@1.0.0.yaml +38 -0
  275. raxe/packs/core/v1.0.0/rules/cmd/cmd-220@1.0.0.yaml +38 -0
  276. raxe/packs/core/v1.0.0/rules/cmd/cmd-221@1.0.0.yaml +38 -0
  277. raxe/packs/core/v1.0.0/rules/cmd/cmd-222@1.0.0.yaml +38 -0
  278. raxe/packs/core/v1.0.0/rules/cmd/cmd-223@1.0.0.yaml +38 -0
  279. raxe/packs/core/v1.0.0/rules/cmd/cmd-224@1.0.0.yaml +38 -0
  280. raxe/packs/core/v1.0.0/rules/cmd/cmd-225@1.0.0.yaml +38 -0
  281. raxe/packs/core/v1.0.0/rules/cmd/cmd-226@1.0.0.yaml +38 -0
  282. raxe/packs/core/v1.0.0/rules/cmd/cmd-227@1.0.0.yaml +38 -0
  283. raxe/packs/core/v1.0.0/rules/cmd/cmd-228@1.0.0.yaml +38 -0
  284. raxe/packs/core/v1.0.0/rules/cmd/cmd-229@1.0.0.yaml +38 -0
  285. raxe/packs/core/v1.0.0/rules/cmd/cmd-230@1.0.0.yaml +38 -0
  286. raxe/packs/core/v1.0.0/rules/cmd/cmd-231@1.0.0.yaml +38 -0
  287. raxe/packs/core/v1.0.0/rules/cmd/cmd-232@1.0.0.yaml +38 -0
  288. raxe/packs/core/v1.0.0/rules/cmd/cmd-233@1.0.0.yaml +38 -0
  289. raxe/packs/core/v1.0.0/rules/cmd/cmd-234@1.0.0.yaml +38 -0
  290. raxe/packs/core/v1.0.0/rules/cmd/cmd-235@1.0.0.yaml +38 -0
  291. raxe/packs/core/v1.0.0/rules/cmd/cmd-236@1.0.0.yaml +38 -0
  292. raxe/packs/core/v1.0.0/rules/cmd/cmd-237@1.0.0.yaml +38 -0
  293. raxe/packs/core/v1.0.0/rules/cmd/cmd-238@1.0.0.yaml +38 -0
  294. raxe/packs/core/v1.0.0/rules/enc/enc-001@1.0.0.yaml +48 -0
  295. raxe/packs/core/v1.0.0/rules/enc/enc-013@1.0.0.yaml +46 -0
  296. raxe/packs/core/v1.0.0/rules/enc/enc-019@1.0.0.yaml +43 -0
  297. raxe/packs/core/v1.0.0/rules/enc/enc-020@1.0.0.yaml +47 -0
  298. raxe/packs/core/v1.0.0/rules/enc/enc-024@1.0.0.yaml +46 -0
  299. raxe/packs/core/v1.0.0/rules/enc/enc-029@1.0.0.yaml +44 -0
  300. raxe/packs/core/v1.0.0/rules/enc/enc-038@1.0.0.yaml +44 -0
  301. raxe/packs/core/v1.0.0/rules/enc/enc-044@1.0.0.yaml +46 -0
  302. raxe/packs/core/v1.0.0/rules/enc/enc-067@1.0.0.yaml +42 -0
  303. raxe/packs/core/v1.0.0/rules/enc/enc-069@1.0.0.yaml +42 -0
  304. raxe/packs/core/v1.0.0/rules/enc/enc-100@1.0.0.yaml +38 -0
  305. raxe/packs/core/v1.0.0/rules/enc/enc-101@1.0.0.yaml +38 -0
  306. raxe/packs/core/v1.0.0/rules/enc/enc-102@1.0.0.yaml +38 -0
  307. raxe/packs/core/v1.0.0/rules/enc/enc-103@1.0.0.yaml +38 -0
  308. raxe/packs/core/v1.0.0/rules/enc/enc-104@1.0.0.yaml +38 -0
  309. raxe/packs/core/v1.0.0/rules/enc/enc-105@1.0.0.yaml +38 -0
  310. raxe/packs/core/v1.0.0/rules/enc/enc-106@1.0.0.yaml +38 -0
  311. raxe/packs/core/v1.0.0/rules/enc/enc-107@1.0.0.yaml +38 -0
  312. raxe/packs/core/v1.0.0/rules/enc/enc-108@1.0.0.yaml +38 -0
  313. raxe/packs/core/v1.0.0/rules/enc/enc-109@1.0.0.yaml +38 -0
  314. raxe/packs/core/v1.0.0/rules/enc/enc-110@1.0.0.yaml +38 -0
  315. raxe/packs/core/v1.0.0/rules/enc/enc-111@1.0.0.yaml +38 -0
  316. raxe/packs/core/v1.0.0/rules/enc/enc-112@1.0.0.yaml +38 -0
  317. raxe/packs/core/v1.0.0/rules/enc/enc-113@1.0.0.yaml +38 -0
  318. raxe/packs/core/v1.0.0/rules/enc/enc-114@1.0.0.yaml +38 -0
  319. raxe/packs/core/v1.0.0/rules/enc/enc-115@1.0.0.yaml +38 -0
  320. raxe/packs/core/v1.0.0/rules/enc/enc-116@1.0.0.yaml +38 -0
  321. raxe/packs/core/v1.0.0/rules/enc/enc-117@1.0.0.yaml +38 -0
  322. raxe/packs/core/v1.0.0/rules/enc/enc-118@1.0.0.yaml +38 -0
  323. raxe/packs/core/v1.0.0/rules/enc/enc-119@1.0.0.yaml +38 -0
  324. raxe/packs/core/v1.0.0/rules/enc/enc-120@1.0.0.yaml +38 -0
  325. raxe/packs/core/v1.0.0/rules/enc/enc-201@1.0.0.yaml +37 -0
  326. raxe/packs/core/v1.0.0/rules/enc/enc-202@1.0.0.yaml +41 -0
  327. raxe/packs/core/v1.0.0/rules/enc/enc-203@1.0.0.yaml +41 -0
  328. raxe/packs/core/v1.0.0/rules/enc/enc-3004@1.0.0.yaml +40 -0
  329. raxe/packs/core/v1.0.0/rules/enc/enc-3006@1.0.0.yaml +40 -0
  330. raxe/packs/core/v1.0.0/rules/enc/enc-3011@1.0.0.yaml +40 -0
  331. raxe/packs/core/v1.0.0/rules/enc/enc-5016@1.0.0.yaml +46 -0
  332. raxe/packs/core/v1.0.0/rules/enc/enc-6001@1.0.0.yaml +53 -0
  333. raxe/packs/core/v1.0.0/rules/enc/enc-6002@1.0.0.yaml +41 -0
  334. raxe/packs/core/v1.0.0/rules/enc/enc-70@1.0.0.yaml +38 -0
  335. raxe/packs/core/v1.0.0/rules/enc/enc-71@1.0.0.yaml +38 -0
  336. raxe/packs/core/v1.0.0/rules/enc/enc-72@1.0.0.yaml +38 -0
  337. raxe/packs/core/v1.0.0/rules/enc/enc-73@1.0.0.yaml +38 -0
  338. raxe/packs/core/v1.0.0/rules/enc/enc-74@1.0.0.yaml +38 -0
  339. raxe/packs/core/v1.0.0/rules/enc/enc-75@1.0.0.yaml +38 -0
  340. raxe/packs/core/v1.0.0/rules/enc/enc-76@1.0.0.yaml +38 -0
  341. raxe/packs/core/v1.0.0/rules/enc/enc-77@1.0.0.yaml +38 -0
  342. raxe/packs/core/v1.0.0/rules/enc/enc-78@1.0.0.yaml +38 -0
  343. raxe/packs/core/v1.0.0/rules/enc/enc-79@1.0.0.yaml +38 -0
  344. raxe/packs/core/v1.0.0/rules/enc/enc-80@1.0.0.yaml +38 -0
  345. raxe/packs/core/v1.0.0/rules/enc/enc-81@1.0.0.yaml +38 -0
  346. raxe/packs/core/v1.0.0/rules/enc/enc-82@1.0.0.yaml +38 -0
  347. raxe/packs/core/v1.0.0/rules/enc/enc-83@1.0.0.yaml +38 -0
  348. raxe/packs/core/v1.0.0/rules/enc/enc-84@1.0.0.yaml +38 -0
  349. raxe/packs/core/v1.0.0/rules/enc/enc-85@1.0.0.yaml +38 -0
  350. raxe/packs/core/v1.0.0/rules/enc/enc-86@1.0.0.yaml +38 -0
  351. raxe/packs/core/v1.0.0/rules/enc/enc-87@1.0.0.yaml +38 -0
  352. raxe/packs/core/v1.0.0/rules/enc/enc-88@1.0.0.yaml +38 -0
  353. raxe/packs/core/v1.0.0/rules/enc/enc-89@1.0.0.yaml +38 -0
  354. raxe/packs/core/v1.0.0/rules/enc/enc-90@1.0.0.yaml +38 -0
  355. raxe/packs/core/v1.0.0/rules/enc/enc-91@1.0.0.yaml +38 -0
  356. raxe/packs/core/v1.0.0/rules/enc/enc-92@1.0.0.yaml +38 -0
  357. raxe/packs/core/v1.0.0/rules/enc/enc-93@1.0.0.yaml +38 -0
  358. raxe/packs/core/v1.0.0/rules/enc/enc-94@1.0.0.yaml +38 -0
  359. raxe/packs/core/v1.0.0/rules/enc/enc-95@1.0.0.yaml +38 -0
  360. raxe/packs/core/v1.0.0/rules/enc/enc-96@1.0.0.yaml +38 -0
  361. raxe/packs/core/v1.0.0/rules/enc/enc-97@1.0.0.yaml +38 -0
  362. raxe/packs/core/v1.0.0/rules/enc/enc-98@1.0.0.yaml +38 -0
  363. raxe/packs/core/v1.0.0/rules/enc/enc-99@1.0.0.yaml +38 -0
  364. raxe/packs/core/v1.0.0/rules/hc/hc-001@1.0.0.yaml +73 -0
  365. raxe/packs/core/v1.0.0/rules/hc/hc-002@1.0.0.yaml +71 -0
  366. raxe/packs/core/v1.0.0/rules/hc/hc-003@1.0.0.yaml +65 -0
  367. raxe/packs/core/v1.0.0/rules/hc/hc-004@1.0.0.yaml +73 -0
  368. raxe/packs/core/v1.0.0/rules/hc/hc-101@1.0.0.yaml +47 -0
  369. raxe/packs/core/v1.0.0/rules/hc/hc-102@1.0.0.yaml +47 -0
  370. raxe/packs/core/v1.0.0/rules/hc/hc-103@1.0.0.yaml +47 -0
  371. raxe/packs/core/v1.0.0/rules/hc/hc-104@1.0.0.yaml +47 -0
  372. raxe/packs/core/v1.0.0/rules/hc/hc-105@1.0.0.yaml +48 -0
  373. raxe/packs/core/v1.0.0/rules/hc/hc-106@1.0.0.yaml +40 -0
  374. raxe/packs/core/v1.0.0/rules/hc/hc-107@1.0.0.yaml +47 -0
  375. raxe/packs/core/v1.0.0/rules/hc/hc-108@1.0.0.yaml +47 -0
  376. raxe/packs/core/v1.0.0/rules/hc/hc-109@1.0.0.yaml +50 -0
  377. raxe/packs/core/v1.0.0/rules/hc/hc-110@1.0.0.yaml +56 -0
  378. raxe/packs/core/v1.0.0/rules/hc/hc-111@1.0.0.yaml +49 -0
  379. raxe/packs/core/v1.0.0/rules/hc/hc-112@1.0.0.yaml +53 -0
  380. raxe/packs/core/v1.0.0/rules/hc/hc-113@1.0.0.yaml +52 -0
  381. raxe/packs/core/v1.0.0/rules/hc/hc-114@1.0.0.yaml +52 -0
  382. raxe/packs/core/v1.0.0/rules/hc/hc-115@1.0.0.yaml +52 -0
  383. raxe/packs/core/v1.0.0/rules/hc/hc-116@1.0.0.yaml +53 -0
  384. raxe/packs/core/v1.0.0/rules/hc/hc-117@1.0.0.yaml +54 -0
  385. raxe/packs/core/v1.0.0/rules/hc/hc-118@1.0.0.yaml +52 -0
  386. raxe/packs/core/v1.0.0/rules/hc/hc-119@1.0.0.yaml +51 -0
  387. raxe/packs/core/v1.0.0/rules/hc/hc-120@1.0.0.yaml +52 -0
  388. raxe/packs/core/v1.0.0/rules/hc/hc-121@1.0.0.yaml +51 -0
  389. raxe/packs/core/v1.0.0/rules/hc/hc-122@1.0.0.yaml +51 -0
  390. raxe/packs/core/v1.0.0/rules/hc/hc-123@1.0.0.yaml +52 -0
  391. raxe/packs/core/v1.0.0/rules/hc/hc-124@1.0.0.yaml +53 -0
  392. raxe/packs/core/v1.0.0/rules/hc/hc-125@1.0.0.yaml +53 -0
  393. raxe/packs/core/v1.0.0/rules/hc/hc-126@1.0.0.yaml +53 -0
  394. raxe/packs/core/v1.0.0/rules/hc/hc-127@1.0.0.yaml +53 -0
  395. raxe/packs/core/v1.0.0/rules/hc/hc-128@1.0.0.yaml +53 -0
  396. raxe/packs/core/v1.0.0/rules/hc/hc-129@1.0.0.yaml +51 -0
  397. raxe/packs/core/v1.0.0/rules/hc/hc-130@1.0.0.yaml +51 -0
  398. raxe/packs/core/v1.0.0/rules/hc/hc-131@1.0.0.yaml +51 -0
  399. raxe/packs/core/v1.0.0/rules/hc/hc-132@1.0.0.yaml +51 -0
  400. raxe/packs/core/v1.0.0/rules/hc/hc-133@1.0.0.yaml +53 -0
  401. raxe/packs/core/v1.0.0/rules/hc/hc-134@1.0.0.yaml +51 -0
  402. raxe/packs/core/v1.0.0/rules/hc/hc-135@1.0.0.yaml +51 -0
  403. raxe/packs/core/v1.0.0/rules/hc/hc-136@1.0.0.yaml +51 -0
  404. raxe/packs/core/v1.0.0/rules/hc/hc-137@1.0.0.yaml +51 -0
  405. raxe/packs/core/v1.0.0/rules/hc/hc-138@1.0.0.yaml +51 -0
  406. raxe/packs/core/v1.0.0/rules/hc/hc-139@1.0.0.yaml +51 -0
  407. raxe/packs/core/v1.0.0/rules/hc/hc-140@1.0.0.yaml +51 -0
  408. raxe/packs/core/v1.0.0/rules/hc/hc-141@1.0.0.yaml +41 -0
  409. raxe/packs/core/v1.0.0/rules/hc/hc-142@1.0.0.yaml +37 -0
  410. raxe/packs/core/v1.0.0/rules/hc/hc-143@1.0.0.yaml +37 -0
  411. raxe/packs/core/v1.0.0/rules/hc/hc-144@1.0.0.yaml +37 -0
  412. raxe/packs/core/v1.0.0/rules/hc/hc-145@1.0.0.yaml +37 -0
  413. raxe/packs/core/v1.0.0/rules/hc/hc-146@1.0.0.yaml +37 -0
  414. raxe/packs/core/v1.0.0/rules/hc/hc-147@1.0.0.yaml +37 -0
  415. raxe/packs/core/v1.0.0/rules/hc/hc-148@1.0.0.yaml +37 -0
  416. raxe/packs/core/v1.0.0/rules/hc/hc-149@1.0.0.yaml +37 -0
  417. raxe/packs/core/v1.0.0/rules/hc/hc-150@1.0.0.yaml +37 -0
  418. raxe/packs/core/v1.0.0/rules/hc/hc-151@1.0.0.yaml +37 -0
  419. raxe/packs/core/v1.0.0/rules/hc/hc-152@1.0.0.yaml +37 -0
  420. raxe/packs/core/v1.0.0/rules/hc/hc-153@1.0.0.yaml +37 -0
  421. raxe/packs/core/v1.0.0/rules/hc/hc-154@1.0.0.yaml +37 -0
  422. raxe/packs/core/v1.0.0/rules/hc/hc-155@1.0.0.yaml +37 -0
  423. raxe/packs/core/v1.0.0/rules/hc/hc-156@1.0.0.yaml +37 -0
  424. raxe/packs/core/v1.0.0/rules/hc/hc-157@1.0.0.yaml +37 -0
  425. raxe/packs/core/v1.0.0/rules/hc/hc-158@1.0.0.yaml +37 -0
  426. raxe/packs/core/v1.0.0/rules/hc/hc-159@1.0.0.yaml +37 -0
  427. raxe/packs/core/v1.0.0/rules/hc/hc-160@1.0.0.yaml +37 -0
  428. raxe/packs/core/v1.0.0/rules/hc/hc-161@1.0.0.yaml +37 -0
  429. raxe/packs/core/v1.0.0/rules/jb/jb-001@1.0.0.yaml +47 -0
  430. raxe/packs/core/v1.0.0/rules/jb/jb-009@1.0.0.yaml +47 -0
  431. raxe/packs/core/v1.0.0/rules/jb/jb-020@1.0.0.yaml +47 -0
  432. raxe/packs/core/v1.0.0/rules/jb/jb-021@1.0.0.yaml +46 -0
  433. raxe/packs/core/v1.0.0/rules/jb/jb-022@1.0.0.yaml +47 -0
  434. raxe/packs/core/v1.0.0/rules/jb/jb-028@1.0.0.yaml +43 -0
  435. raxe/packs/core/v1.0.0/rules/jb/jb-033@1.0.0.yaml +46 -0
  436. raxe/packs/core/v1.0.0/rules/jb/jb-034@1.0.0.yaml +46 -0
  437. raxe/packs/core/v1.0.0/rules/jb/jb-036@1.0.0.yaml +41 -0
  438. raxe/packs/core/v1.0.0/rules/jb/jb-039@1.0.0.yaml +41 -0
  439. raxe/packs/core/v1.0.0/rules/jb/jb-056@1.0.0.yaml +38 -0
  440. raxe/packs/core/v1.0.0/rules/jb/jb-066@1.0.0.yaml +37 -0
  441. raxe/packs/core/v1.0.0/rules/jb/jb-076@1.0.0.yaml +37 -0
  442. raxe/packs/core/v1.0.0/rules/jb/jb-098@1.0.0.yaml +46 -0
  443. raxe/packs/core/v1.0.0/rules/jb/jb-103@1.0.0.yaml +47 -0
  444. raxe/packs/core/v1.0.0/rules/jb/jb-104@1.0.0.yaml +52 -0
  445. raxe/packs/core/v1.0.0/rules/jb/jb-105@1.0.0.yaml +56 -0
  446. raxe/packs/core/v1.0.0/rules/jb/jb-110@1.0.0.yaml +56 -0
  447. raxe/packs/core/v1.0.0/rules/jb/jb-111@1.0.0.yaml +57 -0
  448. raxe/packs/core/v1.0.0/rules/jb/jb-112@1.0.0.yaml +38 -0
  449. raxe/packs/core/v1.0.0/rules/jb/jb-113@1.0.0.yaml +38 -0
  450. raxe/packs/core/v1.0.0/rules/jb/jb-114@1.0.0.yaml +38 -0
  451. raxe/packs/core/v1.0.0/rules/jb/jb-115@1.0.0.yaml +38 -0
  452. raxe/packs/core/v1.0.0/rules/jb/jb-116@1.0.0.yaml +38 -0
  453. raxe/packs/core/v1.0.0/rules/jb/jb-117@1.0.0.yaml +38 -0
  454. raxe/packs/core/v1.0.0/rules/jb/jb-118@1.0.0.yaml +38 -0
  455. raxe/packs/core/v1.0.0/rules/jb/jb-119@1.0.0.yaml +38 -0
  456. raxe/packs/core/v1.0.0/rules/jb/jb-120@1.0.0.yaml +38 -0
  457. raxe/packs/core/v1.0.0/rules/jb/jb-121@1.0.0.yaml +38 -0
  458. raxe/packs/core/v1.0.0/rules/jb/jb-122@1.0.0.yaml +38 -0
  459. raxe/packs/core/v1.0.0/rules/jb/jb-123@1.0.0.yaml +38 -0
  460. raxe/packs/core/v1.0.0/rules/jb/jb-124@1.0.0.yaml +38 -0
  461. raxe/packs/core/v1.0.0/rules/jb/jb-125@1.0.0.yaml +38 -0
  462. raxe/packs/core/v1.0.0/rules/jb/jb-126@1.0.0.yaml +38 -0
  463. raxe/packs/core/v1.0.0/rules/jb/jb-127@1.0.0.yaml +38 -0
  464. raxe/packs/core/v1.0.0/rules/jb/jb-128@1.0.0.yaml +38 -0
  465. raxe/packs/core/v1.0.0/rules/jb/jb-129@1.0.0.yaml +38 -0
  466. raxe/packs/core/v1.0.0/rules/jb/jb-130@1.0.0.yaml +38 -0
  467. raxe/packs/core/v1.0.0/rules/jb/jb-131@1.0.0.yaml +38 -0
  468. raxe/packs/core/v1.0.0/rules/jb/jb-132@1.0.0.yaml +38 -0
  469. raxe/packs/core/v1.0.0/rules/jb/jb-133@1.0.0.yaml +38 -0
  470. raxe/packs/core/v1.0.0/rules/jb/jb-134@1.0.0.yaml +38 -0
  471. raxe/packs/core/v1.0.0/rules/jb/jb-135@1.0.0.yaml +38 -0
  472. raxe/packs/core/v1.0.0/rules/jb/jb-136@1.0.0.yaml +38 -0
  473. raxe/packs/core/v1.0.0/rules/jb/jb-137@1.0.0.yaml +38 -0
  474. raxe/packs/core/v1.0.0/rules/jb/jb-138@1.0.0.yaml +38 -0
  475. raxe/packs/core/v1.0.0/rules/jb/jb-139@1.0.0.yaml +38 -0
  476. raxe/packs/core/v1.0.0/rules/jb/jb-140@1.0.0.yaml +38 -0
  477. raxe/packs/core/v1.0.0/rules/jb/jb-141@1.0.0.yaml +38 -0
  478. raxe/packs/core/v1.0.0/rules/jb/jb-142@1.0.0.yaml +38 -0
  479. raxe/packs/core/v1.0.0/rules/jb/jb-143@1.0.0.yaml +38 -0
  480. raxe/packs/core/v1.0.0/rules/jb/jb-144@1.0.0.yaml +38 -0
  481. raxe/packs/core/v1.0.0/rules/jb/jb-145@1.0.0.yaml +38 -0
  482. raxe/packs/core/v1.0.0/rules/jb/jb-146@1.0.0.yaml +38 -0
  483. raxe/packs/core/v1.0.0/rules/jb/jb-147@1.0.0.yaml +38 -0
  484. raxe/packs/core/v1.0.0/rules/jb/jb-148@1.0.0.yaml +38 -0
  485. raxe/packs/core/v1.0.0/rules/jb/jb-149@1.0.0.yaml +38 -0
  486. raxe/packs/core/v1.0.0/rules/jb/jb-150@1.0.0.yaml +38 -0
  487. raxe/packs/core/v1.0.0/rules/jb/jb-151@1.0.0.yaml +38 -0
  488. raxe/packs/core/v1.0.0/rules/jb/jb-152@1.0.0.yaml +38 -0
  489. raxe/packs/core/v1.0.0/rules/jb/jb-153@1.0.0.yaml +38 -0
  490. raxe/packs/core/v1.0.0/rules/jb/jb-154@1.0.0.yaml +38 -0
  491. raxe/packs/core/v1.0.0/rules/jb/jb-155@1.0.0.yaml +38 -0
  492. raxe/packs/core/v1.0.0/rules/jb/jb-156@1.0.0.yaml +38 -0
  493. raxe/packs/core/v1.0.0/rules/jb/jb-157@1.0.0.yaml +38 -0
  494. raxe/packs/core/v1.0.0/rules/jb/jb-158@1.0.0.yaml +38 -0
  495. raxe/packs/core/v1.0.0/rules/jb/jb-159@1.0.0.yaml +38 -0
  496. raxe/packs/core/v1.0.0/rules/jb/jb-160@1.0.0.yaml +38 -0
  497. raxe/packs/core/v1.0.0/rules/jb/jb-161@1.0.0.yaml +38 -0
  498. raxe/packs/core/v1.0.0/rules/jb/jb-162@1.0.0.yaml +38 -0
  499. raxe/packs/core/v1.0.0/rules/jb/jb-201@1.0.0.yaml +40 -0
  500. raxe/packs/core/v1.0.0/rules/jb/jb-202@1.0.0.yaml +41 -0
  501. raxe/packs/core/v1.0.0/rules/jb/jb-203@1.0.0.yaml +51 -0
  502. raxe/packs/core/v1.0.0/rules/jb/jb-204@1.0.0.yaml +50 -0
  503. raxe/packs/core/v1.0.0/rules/jb/jb-205@1.0.0.yaml +50 -0
  504. raxe/packs/core/v1.0.0/rules/jb/jb-206@1.0.0.yaml +50 -0
  505. raxe/packs/core/v1.0.0/rules/jb/jb-207@1.0.0.yaml +49 -0
  506. raxe/packs/core/v1.0.0/rules/pii/pii-001@1.0.0.yaml +48 -0
  507. raxe/packs/core/v1.0.0/rules/pii/pii-009@1.0.0.yaml +48 -0
  508. raxe/packs/core/v1.0.0/rules/pii/pii-012@1.0.0.yaml +48 -0
  509. raxe/packs/core/v1.0.0/rules/pii/pii-017@1.0.0.yaml +48 -0
  510. raxe/packs/core/v1.0.0/rules/pii/pii-022@1.0.0.yaml +47 -0
  511. raxe/packs/core/v1.0.0/rules/pii/pii-025@1.0.0.yaml +47 -0
  512. raxe/packs/core/v1.0.0/rules/pii/pii-027@1.0.0.yaml +47 -0
  513. raxe/packs/core/v1.0.0/rules/pii/pii-028@1.0.0.yaml +47 -0
  514. raxe/packs/core/v1.0.0/rules/pii/pii-034@1.0.0.yaml +47 -0
  515. raxe/packs/core/v1.0.0/rules/pii/pii-037@1.0.0.yaml +47 -0
  516. raxe/packs/core/v1.0.0/rules/pii/pii-040@1.0.0.yaml +47 -0
  517. raxe/packs/core/v1.0.0/rules/pii/pii-041@1.0.0.yaml +47 -0
  518. raxe/packs/core/v1.0.0/rules/pii/pii-044@1.0.0.yaml +47 -0
  519. raxe/packs/core/v1.0.0/rules/pii/pii-050@1.0.0.yaml +57 -0
  520. raxe/packs/core/v1.0.0/rules/pii/pii-051@1.0.0.yaml +53 -0
  521. raxe/packs/core/v1.0.0/rules/pii/pii-052@1.0.0.yaml +52 -0
  522. raxe/packs/core/v1.0.0/rules/pii/pii-053@1.0.0.yaml +56 -0
  523. raxe/packs/core/v1.0.0/rules/pii/pii-054@1.0.0.yaml +53 -0
  524. raxe/packs/core/v1.0.0/rules/pii/pii-055@1.0.0.yaml +51 -0
  525. raxe/packs/core/v1.0.0/rules/pii/pii-056@1.0.0.yaml +51 -0
  526. raxe/packs/core/v1.0.0/rules/pii/pii-058@1.0.0.yaml +47 -0
  527. raxe/packs/core/v1.0.0/rules/pii/pii-2015@1.0.0.yaml +41 -0
  528. raxe/packs/core/v1.0.0/rules/pii/pii-2025@1.0.0.yaml +35 -0
  529. raxe/packs/core/v1.0.0/rules/pii/pii-2026@1.0.0.yaml +39 -0
  530. raxe/packs/core/v1.0.0/rules/pii/pii-2035@1.0.0.yaml +39 -0
  531. raxe/packs/core/v1.0.0/rules/pii/pii-2037@1.0.0.yaml +39 -0
  532. raxe/packs/core/v1.0.0/rules/pii/pii-2042@1.0.0.yaml +39 -0
  533. raxe/packs/core/v1.0.0/rules/pii/pii-3001@1.0.0.yaml +39 -0
  534. raxe/packs/core/v1.0.0/rules/pii/pii-3002@1.0.0.yaml +41 -0
  535. raxe/packs/core/v1.0.0/rules/pii/pii-3003@1.0.0.yaml +36 -0
  536. raxe/packs/core/v1.0.0/rules/pii/pii-3004@1.0.0.yaml +41 -0
  537. raxe/packs/core/v1.0.0/rules/pii/pii-3005@1.0.0.yaml +39 -0
  538. raxe/packs/core/v1.0.0/rules/pii/pii-3006@1.0.0.yaml +35 -0
  539. raxe/packs/core/v1.0.0/rules/pii/pii-3007@1.0.0.yaml +37 -0
  540. raxe/packs/core/v1.0.0/rules/pii/pii-3008@1.0.0.yaml +35 -0
  541. raxe/packs/core/v1.0.0/rules/pii/pii-3009@1.0.0.yaml +42 -0
  542. raxe/packs/core/v1.0.0/rules/pii/pii-3010@1.0.0.yaml +39 -0
  543. raxe/packs/core/v1.0.0/rules/pii/pii-3011@1.0.0.yaml +35 -0
  544. raxe/packs/core/v1.0.0/rules/pii/pii-3012@1.0.0.yaml +35 -0
  545. raxe/packs/core/v1.0.0/rules/pii/pii-3013@1.0.0.yaml +36 -0
  546. raxe/packs/core/v1.0.0/rules/pii/pii-3014@1.0.0.yaml +36 -0
  547. raxe/packs/core/v1.0.0/rules/pii/pii-3015@1.0.0.yaml +42 -0
  548. raxe/packs/core/v1.0.0/rules/pii/pii-3016@1.0.0.yaml +42 -0
  549. raxe/packs/core/v1.0.0/rules/pii/pii-3017@1.0.0.yaml +40 -0
  550. raxe/packs/core/v1.0.0/rules/pii/pii-3018@1.0.0.yaml +38 -0
  551. raxe/packs/core/v1.0.0/rules/pii/pii-3019@1.0.0.yaml +40 -0
  552. raxe/packs/core/v1.0.0/rules/pii/pii-3020@1.0.0.yaml +40 -0
  553. raxe/packs/core/v1.0.0/rules/pii/pii-3021@1.0.0.yaml +39 -0
  554. raxe/packs/core/v1.0.0/rules/pii/pii-3022@1.0.0.yaml +36 -0
  555. raxe/packs/core/v1.0.0/rules/pii/pii-3023@1.0.0.yaml +41 -0
  556. raxe/packs/core/v1.0.0/rules/pii/pii-3024@1.0.0.yaml +37 -0
  557. raxe/packs/core/v1.0.0/rules/pii/pii-3025@1.0.0.yaml +38 -0
  558. raxe/packs/core/v1.0.0/rules/pii/pii-3026@1.0.0.yaml +42 -0
  559. raxe/packs/core/v1.0.0/rules/pii/pii-3027@1.0.0.yaml +38 -0
  560. raxe/packs/core/v1.0.0/rules/pii/pii-3028@1.0.0.yaml +42 -0
  561. raxe/packs/core/v1.0.0/rules/pii/pii-3029@1.0.0.yaml +36 -0
  562. raxe/packs/core/v1.0.0/rules/pii/pii-3030@1.0.0.yaml +42 -0
  563. raxe/packs/core/v1.0.0/rules/pii/pii-3031@1.0.0.yaml +37 -0
  564. raxe/packs/core/v1.0.0/rules/pii/pii-3032@1.0.0.yaml +42 -0
  565. raxe/packs/core/v1.0.0/rules/pii/pii-3033@1.0.0.yaml +39 -0
  566. raxe/packs/core/v1.0.0/rules/pii/pii-3034@1.0.0.yaml +40 -0
  567. raxe/packs/core/v1.0.0/rules/pii/pii-3035@1.0.0.yaml +43 -0
  568. raxe/packs/core/v1.0.0/rules/pii/pii-3036@1.0.0.yaml +41 -0
  569. raxe/packs/core/v1.0.0/rules/pii/pii-3037@1.0.0.yaml +35 -0
  570. raxe/packs/core/v1.0.0/rules/pii/pii-3038@1.0.0.yaml +35 -0
  571. raxe/packs/core/v1.0.0/rules/pii/pii-3039@1.0.0.yaml +35 -0
  572. raxe/packs/core/v1.0.0/rules/pii/pii-3040@1.0.0.yaml +41 -0
  573. raxe/packs/core/v1.0.0/rules/pii/pii-3041@1.0.0.yaml +39 -0
  574. raxe/packs/core/v1.0.0/rules/pii/pii-3042@1.0.0.yaml +36 -0
  575. raxe/packs/core/v1.0.0/rules/pii/pii-3043@1.0.0.yaml +35 -0
  576. raxe/packs/core/v1.0.0/rules/pii/pii-3044@1.0.0.yaml +43 -0
  577. raxe/packs/core/v1.0.0/rules/pii/pii-3045@1.0.0.yaml +36 -0
  578. raxe/packs/core/v1.0.0/rules/pii/pii-3046@1.0.0.yaml +37 -0
  579. raxe/packs/core/v1.0.0/rules/pii/pii-3047@1.0.0.yaml +36 -0
  580. raxe/packs/core/v1.0.0/rules/pii/pii-3048@1.0.0.yaml +36 -0
  581. raxe/packs/core/v1.0.0/rules/pii/pii-3049@1.0.0.yaml +38 -0
  582. raxe/packs/core/v1.0.0/rules/pii/pii-3050@1.0.0.yaml +44 -0
  583. raxe/packs/core/v1.0.0/rules/pii/pii-3051@1.0.0.yaml +35 -0
  584. raxe/packs/core/v1.0.0/rules/pii/pii-3052@1.0.0.yaml +36 -0
  585. raxe/packs/core/v1.0.0/rules/pii/pii-3053@1.0.0.yaml +35 -0
  586. raxe/packs/core/v1.0.0/rules/pii/pii-3054@1.0.0.yaml +35 -0
  587. raxe/packs/core/v1.0.0/rules/pii/pii-3055@1.0.0.yaml +40 -0
  588. raxe/packs/core/v1.0.0/rules/pii/pii-3056@1.0.0.yaml +38 -0
  589. raxe/packs/core/v1.0.0/rules/pii/pii-3057@1.0.0.yaml +40 -0
  590. raxe/packs/core/v1.0.0/rules/pii/pii-3058@1.0.0.yaml +43 -0
  591. raxe/packs/core/v1.0.0/rules/pii/pii-3059@1.0.0.yaml +42 -0
  592. raxe/packs/core/v1.0.0/rules/pii/pii-3060@1.0.0.yaml +42 -0
  593. raxe/packs/core/v1.0.0/rules/pii/pii-3061@1.0.0.yaml +50 -0
  594. raxe/packs/core/v1.0.0/rules/pii/pii-3062@1.0.0.yaml +50 -0
  595. raxe/packs/core/v1.0.0/rules/pii/pii-3063@1.0.0.yaml +54 -0
  596. raxe/packs/core/v1.0.0/rules/pii/pii-3064@1.0.0.yaml +78 -0
  597. raxe/packs/core/v1.0.0/rules/pii/pii-3065@1.0.0.yaml +84 -0
  598. raxe/packs/core/v1.0.0/rules/pii/pii-3066@1.0.0.yaml +84 -0
  599. raxe/packs/core/v1.0.0/rules/pii/pii-3067@1.0.0.yaml +88 -0
  600. raxe/packs/core/v1.0.0/rules/pii/pii-3068@1.0.0.yaml +94 -0
  601. raxe/packs/core/v1.0.0/rules/pii/pii-3069@1.0.0.yaml +90 -0
  602. raxe/packs/core/v1.0.0/rules/pii/pii-3070@1.0.0.yaml +99 -0
  603. raxe/packs/core/v1.0.0/rules/pii/pii-3071@1.0.0.yaml +91 -0
  604. raxe/packs/core/v1.0.0/rules/pii/pii-3072@1.0.0.yaml +38 -0
  605. raxe/packs/core/v1.0.0/rules/pii/pii-3073@1.0.0.yaml +38 -0
  606. raxe/packs/core/v1.0.0/rules/pii/pii-3074@1.0.0.yaml +38 -0
  607. raxe/packs/core/v1.0.0/rules/pii/pii-3075@1.0.0.yaml +38 -0
  608. raxe/packs/core/v1.0.0/rules/pii/pii-3076@1.0.0.yaml +38 -0
  609. raxe/packs/core/v1.0.0/rules/pii/pii-3077@1.0.0.yaml +38 -0
  610. raxe/packs/core/v1.0.0/rules/pii/pii-3078@1.0.0.yaml +38 -0
  611. raxe/packs/core/v1.0.0/rules/pii/pii-3079@1.0.0.yaml +38 -0
  612. raxe/packs/core/v1.0.0/rules/pii/pii-3080@1.0.0.yaml +38 -0
  613. raxe/packs/core/v1.0.0/rules/pii/pii-3081@1.0.0.yaml +38 -0
  614. raxe/packs/core/v1.0.0/rules/pii/pii-3082@1.0.0.yaml +38 -0
  615. raxe/packs/core/v1.0.0/rules/pii/pii-3083@1.0.0.yaml +38 -0
  616. raxe/packs/core/v1.0.0/rules/pii/pii-3084@1.0.0.yaml +38 -0
  617. raxe/packs/core/v1.0.0/rules/pii/pii-3085@1.0.0.yaml +38 -0
  618. raxe/packs/core/v1.0.0/rules/rag/rag-016@1.0.0.yaml +47 -0
  619. raxe/packs/core/v1.0.0/rules/rag/rag-028@1.0.0.yaml +47 -0
  620. raxe/packs/core/v1.0.0/rules/rag/rag-042@1.0.0.yaml +47 -0
  621. raxe/packs/core/v1.0.0/rules/rag/rag-044@1.0.0.yaml +47 -0
  622. raxe/packs/core/v1.0.0/rules/rag/rag-045@1.0.0.yaml +47 -0
  623. raxe/packs/core/v1.0.0/rules/rag/rag-050@1.0.0.yaml +47 -0
  624. raxe/packs/core/v1.0.0/rules/rag/rag-201@1.0.0.yaml +41 -0
  625. raxe/packs/core/v1.0.0/rules/rag/rag-202@1.0.0.yaml +41 -0
  626. raxe/packs/core/v1.0.0/rules/rag/rag-3001@1.0.0.yaml +41 -0
  627. raxe/packs/core/v1.0.0/rules/rag/rag-3006@1.0.0.yaml +41 -0
  628. raxe/packs/core/v1.0.0/rules/rag/rag-3009@1.0.0.yaml +41 -0
  629. raxe/packs/core/v1.0.0/rules/rag/rag-3012@1.0.0.yaml +41 -0
  630. raxe/plugins/__init__.py +98 -0
  631. raxe/plugins/custom_rules.py +380 -0
  632. raxe/plugins/loader.py +389 -0
  633. raxe/plugins/manager.py +538 -0
  634. raxe/plugins/protocol.py +428 -0
  635. raxe/py.typed +0 -0
  636. raxe/sdk/__init__.py +77 -0
  637. raxe/sdk/agent_scanner.py +1918 -0
  638. raxe/sdk/client.py +1603 -0
  639. raxe/sdk/decorator.py +175 -0
  640. raxe/sdk/exceptions.py +859 -0
  641. raxe/sdk/integrations/__init__.py +277 -0
  642. raxe/sdk/integrations/agent_scanner.py +71 -0
  643. raxe/sdk/integrations/autogen.py +872 -0
  644. raxe/sdk/integrations/crewai.py +1368 -0
  645. raxe/sdk/integrations/dspy.py +845 -0
  646. raxe/sdk/integrations/extractors.py +363 -0
  647. raxe/sdk/integrations/huggingface.py +395 -0
  648. raxe/sdk/integrations/langchain.py +948 -0
  649. raxe/sdk/integrations/litellm.py +484 -0
  650. raxe/sdk/integrations/llamaindex.py +1049 -0
  651. raxe/sdk/integrations/portkey.py +831 -0
  652. raxe/sdk/suppression_context.py +215 -0
  653. raxe/sdk/wrappers/__init__.py +163 -0
  654. raxe/sdk/wrappers/anthropic.py +310 -0
  655. raxe/sdk/wrappers/openai.py +221 -0
  656. raxe/sdk/wrappers/vertexai.py +484 -0
  657. raxe/utils/__init__.py +12 -0
  658. raxe/utils/error_sanitizer.py +135 -0
  659. raxe/utils/logging.py +241 -0
  660. raxe/utils/performance.py +414 -0
  661. raxe/utils/profiler.py +339 -0
  662. raxe/utils/validators.py +170 -0
  663. raxe-0.4.6.dist-info/METADATA +471 -0
  664. raxe-0.4.6.dist-info/RECORD +668 -0
  665. raxe-0.4.6.dist-info/WHEEL +5 -0
  666. raxe-0.4.6.dist-info/entry_points.txt +2 -0
  667. raxe-0.4.6.dist-info/licenses/LICENSE +56 -0
  668. raxe-0.4.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1918 @@
1
+ """AgentScanner - Core scanning component for AI agent framework integrations.
2
+
3
+ This module provides the shared AgentScanner class that all agent framework
4
+ integrations (LangChain, CrewAI, AutoGen, LlamaIndex, etc.) use via COMPOSITION.
5
+
6
+ Design Principles:
7
+ - Composition over inheritance: AgentScanner is composed into integrations
8
+ - Sync-first with async wrappers: Core logic is synchronous for simplicity
9
+ - Default: log-only (blocking requires explicit on_threat="block")
10
+ - Privacy-first: No PII in telemetry, only hashes and metadata
11
+
12
+ Usage:
13
+ from raxe import Raxe
14
+ from raxe.sdk.agent_scanner import AgentScanner, AgentScannerConfig
15
+
16
+ # Create scanner with existing Raxe client
17
+ raxe = Raxe()
18
+ config = AgentScannerConfig(
19
+ scan_prompts=True,
20
+ scan_tool_calls=True,
21
+ on_threat="log", # Default: log only, don't block
22
+ )
23
+ scanner = AgentScanner(raxe=raxe, config=config)
24
+
25
+ # Scan a prompt
26
+ result = scanner.scan_prompt("User input here")
27
+ if result.should_block:
28
+ raise ThreatDetectedError(result)
29
+
30
+ # Validate a tool call
31
+ tool_result = scanner.validate_tool_call(
32
+ tool_name="shell_execute",
33
+ arguments={"command": "rm -rf /"},
34
+ )
35
+ if not tool_result.is_allowed:
36
+ print(f"Tool blocked: {tool_result.reason}")
37
+
38
+ See: docs/agent_integrations.md for integration guide.
39
+
40
+ Version History:
41
+ - v1.0: Initial implementation with basic scanning
42
+ - v2.0: Redesigned with AgentScannerConfig, improved tool validation,
43
+ async support, and enhanced telemetry (current)
44
+ """
45
+ from __future__ import annotations
46
+
47
+ import asyncio
48
+ import hashlib
49
+ import logging
50
+ import re
51
+ import time
52
+ import uuid
53
+ from dataclasses import dataclass, field
54
+ from enum import Enum
55
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Pattern
56
+
57
+ from raxe.sdk.client import Raxe
58
+ from raxe.sdk.exceptions import (
59
+ ErrorCode,
60
+ RaxeError,
61
+ RaxeException,
62
+ SecurityException,
63
+ )
64
+ from raxe.utils.logging import get_logger
65
+
66
+ if TYPE_CHECKING:
67
+ from raxe.application.scan_pipeline import ScanPipelineResult
68
+
69
+ logger = get_logger(__name__)
70
+
71
+
72
+ class ScanType(str, Enum):
73
+ """Types of scans in agentic systems.
74
+
75
+ Used for telemetry, logging, and per-type configuration.
76
+ """
77
+
78
+ PROMPT = "prompt"
79
+ RESPONSE = "response"
80
+ TOOL_CALL = "tool_call"
81
+ TOOL_RESULT = "tool_result"
82
+ AGENT_ACTION = "agent_action"
83
+ CHAIN_INPUT = "chain_input"
84
+ CHAIN_OUTPUT = "chain_output"
85
+ SYSTEM_PROMPT = "system_prompt"
86
+ RAG_CONTEXT = "rag_context"
87
+ MEMORY_CONTENT = "memory_content"
88
+
89
+
90
+ class ThreatAction(str, Enum):
91
+ """Action to take when a threat is detected.
92
+
93
+ Attributes:
94
+ LOG: Log the threat but allow the request to proceed (default)
95
+ BLOCK: Block the request and raise ThreatDetectedError
96
+ WARN: Log a warning and allow (same as LOG but higher visibility)
97
+ """
98
+
99
+ LOG = "log"
100
+ BLOCK = "block"
101
+ WARN = "warn"
102
+
103
+
104
+ class ToolValidationMode(str, Enum):
105
+ """How to validate tool calls."""
106
+
107
+ DISABLED = "disabled" # No tool validation
108
+ ALLOWLIST = "allowlist" # Only allowed tools can execute
109
+ BLOCKLIST = "blocklist" # Blocked tools cannot execute
110
+
111
+
112
+ class ToolValidationResult(str, Enum):
113
+ """Result of tool validation.
114
+
115
+ Attributes:
116
+ ALLOWED: Tool call is allowed to proceed
117
+ BLOCKED: Tool call is blocked (dangerous or blocklisted)
118
+ SUSPICIOUS: Tool call is suspicious but allowed (logged)
119
+ """
120
+
121
+ ALLOWED = "allowed"
122
+ BLOCKED = "blocked"
123
+ SUSPICIOUS = "suspicious"
124
+
125
+
126
+ class ScanMode(str, Enum):
127
+ """Scan mode controlling threat response behavior.
128
+
129
+ Determines what action to take when threats are detected.
130
+
131
+ Attributes:
132
+ LOG_ONLY: Log threats but don't block (default, safe)
133
+ BLOCK_ON_THREAT: Block on any threat detection
134
+ BLOCK_ON_HIGH: Block only on HIGH or CRITICAL severity
135
+ BLOCK_ON_CRITICAL: Block only on CRITICAL severity
136
+ """
137
+
138
+ LOG_ONLY = "log_only"
139
+ BLOCK_ON_THREAT = "block_on_threat"
140
+ BLOCK_ON_HIGH = "block_on_high"
141
+ BLOCK_ON_CRITICAL = "block_on_critical"
142
+
143
+
144
+ class MessageType(str, Enum):
145
+ """Type of message in multi-agent communication.
146
+
147
+ Used for context-aware scanning and telemetry.
148
+
149
+ Attributes:
150
+ HUMAN_INPUT: Direct input from human user
151
+ AGENT_TO_AGENT: Message between agents in a multi-agent system
152
+ AGENT_RESPONSE: Response from an agent to user/system
153
+ FUNCTION_CALL: Tool/function invocation
154
+ FUNCTION_RESULT: Result from tool/function execution
155
+ SYSTEM: System-level message (instructions, config)
156
+ """
157
+
158
+ HUMAN_INPUT = "human_input"
159
+ AGENT_TO_AGENT = "agent_to_agent"
160
+ AGENT_RESPONSE = "agent_response"
161
+ FUNCTION_CALL = "function_call"
162
+ FUNCTION_RESULT = "function_result"
163
+ SYSTEM = "system"
164
+
165
+
166
+ @dataclass
167
+ class ScanContext:
168
+ """Context for a scan operation in multi-agent systems.
169
+
170
+ Provides metadata about the message being scanned for
171
+ better threat detection and telemetry.
172
+
173
+ Attributes:
174
+ message_type: Type of message (human input, agent-to-agent, etc.)
175
+ sender_name: Name of the sending agent (if applicable)
176
+ receiver_name: Name of the receiving agent (if applicable)
177
+ conversation_id: Unique ID for the conversation thread
178
+ message_index: Position in the conversation (0-indexed)
179
+ metadata: Additional context metadata
180
+ """
181
+
182
+ message_type: MessageType
183
+ sender_name: str | None = None
184
+ receiver_name: str | None = None
185
+ conversation_id: str | None = None
186
+ message_index: int = 0
187
+ metadata: dict[str, Any] = field(default_factory=dict)
188
+
189
+
190
+ @dataclass(frozen=True)
191
+ class ToolPolicy:
192
+ """Policy for tool validation.
193
+
194
+ Defines which tools are allowed/blocked and any argument restrictions.
195
+
196
+ Attributes:
197
+ mode: Validation mode (allowlist, blocklist, or disabled)
198
+ tools: Set of tool names for allowlist/blocklist
199
+ argument_patterns: Dict mapping tool names to forbidden argument patterns
200
+ block_on_violation: Whether to block or just log on policy violation
201
+ """
202
+
203
+ mode: ToolValidationMode = ToolValidationMode.DISABLED
204
+ tools: frozenset[str] = field(default_factory=frozenset)
205
+ argument_patterns: dict[str, list[Pattern[str]]] = field(default_factory=dict)
206
+ block_on_violation: bool = True
207
+
208
+ @classmethod
209
+ def allow_only(cls, *tools: str, block: bool = True) -> ToolPolicy:
210
+ """Create allowlist policy - only specified tools can run.
211
+
212
+ Args:
213
+ *tools: Tool names that are allowed
214
+ block: Whether to block violations (default: True)
215
+
216
+ Returns:
217
+ ToolPolicy configured as allowlist
218
+
219
+ Example:
220
+ policy = ToolPolicy.allow_only("calculator", "search")
221
+ """
222
+ return cls(
223
+ mode=ToolValidationMode.ALLOWLIST,
224
+ tools=frozenset(tools),
225
+ block_on_violation=block,
226
+ )
227
+
228
+ @classmethod
229
+ def block_tools(cls, *tools: str, block: bool = True) -> ToolPolicy:
230
+ """Create blocklist policy - specified tools cannot run.
231
+
232
+ Args:
233
+ *tools: Tool names that are blocked
234
+ block: Whether to block violations (default: True)
235
+
236
+ Returns:
237
+ ToolPolicy configured as blocklist
238
+
239
+ Example:
240
+ policy = ToolPolicy.block_tools("shell", "file_write")
241
+ """
242
+ return cls(
243
+ mode=ToolValidationMode.BLOCKLIST,
244
+ tools=frozenset(tools),
245
+ block_on_violation=block,
246
+ )
247
+
248
+ def is_tool_allowed(self, tool_name: str) -> bool:
249
+ """Check if a tool is allowed by this policy.
250
+
251
+ Args:
252
+ tool_name: Name of the tool to check
253
+
254
+ Returns:
255
+ True if tool is allowed, False if blocked
256
+ """
257
+ if self.mode == ToolValidationMode.DISABLED:
258
+ return True
259
+ elif self.mode == ToolValidationMode.ALLOWLIST:
260
+ return tool_name in self.tools
261
+ elif self.mode == ToolValidationMode.BLOCKLIST:
262
+ return tool_name not in self.tools
263
+ return True
264
+
265
+
266
+ @dataclass(frozen=True)
267
+ class AgentScanResult:
268
+ """Result from an AgentScanner scan.
269
+
270
+ Immutable result containing scan outcome and metadata.
271
+ Used by both scan_prompt() and validate_tool_call().
272
+
273
+ Attributes:
274
+ scan_type: Type of scan performed
275
+ has_threats: Whether threats were detected
276
+ should_block: Whether the action should be blocked
277
+ severity: Highest severity detected
278
+ detection_count: Number of detections
279
+ trace_id: Correlation ID for this agent run
280
+ step_id: Step number within the agent run
281
+ duration_ms: Scan duration in milliseconds
282
+ message: Human-readable summary
283
+ details: Additional details (tool name, etc.)
284
+ policy_violation: Whether a tool policy was violated
285
+ rule_ids: List of triggered rule IDs (for debugging)
286
+ families: Threat families detected (PI, JB, etc.)
287
+ prompt_hash: SHA256 hash of scanned content (privacy-preserving)
288
+ action_taken: Action that was/will be taken (log, block, warn)
289
+ pipeline_result: Full ScanPipelineResult (for advanced use)
290
+ """
291
+
292
+ scan_type: ScanType
293
+ has_threats: bool
294
+ should_block: bool
295
+ severity: str | None
296
+ detection_count: int
297
+ trace_id: str
298
+ step_id: int
299
+ duration_ms: float
300
+ message: str
301
+ details: dict[str, Any] = field(default_factory=dict)
302
+ policy_violation: bool = False
303
+ rule_ids: list[str] = field(default_factory=list)
304
+ families: list[str] = field(default_factory=list)
305
+ prompt_hash: str = ""
306
+ action_taken: str = "allow"
307
+ pipeline_result: Any = None # ScanPipelineResult, optional for advanced use
308
+
309
+ def to_dict(self) -> dict[str, Any]:
310
+ """Convert to dictionary for serialization.
311
+
312
+ Returns:
313
+ Dictionary representation (excludes pipeline_result for privacy)
314
+ """
315
+ return {
316
+ "scan_type": self.scan_type.value,
317
+ "has_threats": self.has_threats,
318
+ "should_block": self.should_block,
319
+ "severity": self.severity,
320
+ "detection_count": self.detection_count,
321
+ "trace_id": self.trace_id,
322
+ "step_id": self.step_id,
323
+ "duration_ms": self.duration_ms,
324
+ "message": self.message,
325
+ "details": self.details,
326
+ "policy_violation": self.policy_violation,
327
+ "rule_ids": self.rule_ids,
328
+ "families": self.families,
329
+ "prompt_hash": self.prompt_hash,
330
+ "action_taken": self.action_taken,
331
+ }
332
+
333
+
334
+ @dataclass(frozen=True)
335
+ class ToolValidationResponse:
336
+ """Result of tool call validation.
337
+
338
+ Attributes:
339
+ is_allowed: True if tool call should proceed
340
+ result: Validation result enum (ALLOWED, BLOCKED, SUSPICIOUS)
341
+ reason: Human-readable reason for decision
342
+ tool_name: Name of the tool being validated
343
+ is_dangerous: True if tool is on dangerous tools list
344
+ arguments_scanned: True if arguments were scanned for threats
345
+ scan_result: AgentScanResult if arguments were scanned
346
+ metadata: Additional validation metadata
347
+ """
348
+
349
+ is_allowed: bool
350
+ result: ToolValidationResult
351
+ reason: str
352
+ tool_name: str
353
+ is_dangerous: bool = False
354
+ arguments_scanned: bool = False
355
+ scan_result: AgentScanResult | None = None
356
+ metadata: dict[str, Any] = field(default_factory=dict)
357
+
358
+ def to_dict(self) -> dict[str, Any]:
359
+ """Convert to dictionary for serialization."""
360
+ result_dict = {
361
+ "is_allowed": self.is_allowed,
362
+ "result": self.result.value,
363
+ "reason": self.reason,
364
+ "tool_name": self.tool_name,
365
+ "is_dangerous": self.is_dangerous,
366
+ "arguments_scanned": self.arguments_scanned,
367
+ "metadata": self.metadata,
368
+ }
369
+ if self.scan_result:
370
+ result_dict["scan_result"] = self.scan_result.to_dict()
371
+ return result_dict
372
+
373
+
374
+ @dataclass
375
+ class ScanConfig:
376
+ """Configuration for a specific scan type.
377
+
378
+ Attributes:
379
+ enabled: Whether this scan type is enabled
380
+ block_on_threat: Whether to block on threat detection
381
+ min_severity_to_block: Minimum severity level to trigger blocking
382
+ """
383
+
384
+ enabled: bool = True
385
+ block_on_threat: bool = False # Default: log-only
386
+ min_severity_to_block: str = "MEDIUM"
387
+
388
+
389
+ @dataclass
390
+ class ToolValidationConfig:
391
+ """Configuration for tool call validation.
392
+
393
+ Attributes:
394
+ enabled: Enable tool call validation (default: True)
395
+ allowlist: List of explicitly allowed tool names (empty = allow all)
396
+ blocklist: List of explicitly blocked tool names
397
+ scan_arguments: Scan tool arguments for threats (default: True)
398
+ dangerous_tools: Tools considered high-risk requiring extra scrutiny
399
+ argument_patterns_to_scan: Argument names to always scan
400
+ max_argument_length: Maximum argument length to scan
401
+ """
402
+
403
+ enabled: bool = True
404
+ allowlist: list[str] = field(default_factory=list)
405
+ blocklist: list[str] = field(default_factory=list)
406
+ scan_arguments: bool = True
407
+ dangerous_tools: list[str] = field(
408
+ default_factory=lambda: [
409
+ "shell",
410
+ "bash",
411
+ "exec",
412
+ "execute",
413
+ "run_command",
414
+ "shell_execute",
415
+ "code_interpreter",
416
+ "python_repl",
417
+ "eval",
418
+ "subprocess",
419
+ "terminal",
420
+ "ssh",
421
+ ]
422
+ )
423
+ argument_patterns_to_scan: list[str] = field(
424
+ default_factory=lambda: [
425
+ "command",
426
+ "code",
427
+ "script",
428
+ "query",
429
+ "sql",
430
+ "input",
431
+ "prompt",
432
+ "message",
433
+ "content",
434
+ "text",
435
+ "body",
436
+ "url",
437
+ "path",
438
+ ]
439
+ )
440
+ max_argument_length: int = 10000
441
+
442
+
443
+ @dataclass
444
+ class AgentScannerConfig:
445
+ """Configuration for AgentScanner behavior.
446
+
447
+ This dataclass defines ALL configuration options for the AgentScanner.
448
+ Settings cascade: explicit > environment > defaults.
449
+
450
+ Attributes:
451
+ scan_prompts: Scan user prompts before sending to LLM (default: True)
452
+ scan_system_prompts: Scan system prompts for injection (default: True)
453
+ scan_tool_calls: Validate tool calls before execution (default: True)
454
+ scan_tool_results: Scan tool execution results (default: False)
455
+ scan_responses: Scan LLM responses for threats (default: False)
456
+ scan_memory: Scan memory/context retrieved (default: False)
457
+ scan_rag_context: Scan RAG-retrieved context (default: True)
458
+
459
+ on_threat: Action when threat detected - "log", "block", or "warn"
460
+ block_severity_threshold: Minimum severity to trigger block
461
+ allow_severity: List of severities to always allow
462
+
463
+ tool_validation: Configuration for tool call validation
464
+ confidence_threshold: Minimum confidence to report detections
465
+ l2_enabled: Enable L2 ML detection for agent scans
466
+
467
+ on_threat_callback: Optional callback when threat detected
468
+ on_block_callback: Optional callback when request blocked
469
+ telemetry_enabled: Enable agent-specific telemetry
470
+
471
+ timeout_ms: Scan timeout in milliseconds
472
+ fail_open: If scan fails/times out, allow request
473
+ max_prompt_length: Maximum prompt length to scan
474
+ """
475
+
476
+ # Scan targets
477
+ scan_prompts: bool = True
478
+ scan_system_prompts: bool = True
479
+ scan_tool_calls: bool = True
480
+ scan_tool_results: bool = False
481
+ scan_responses: bool = False
482
+ scan_memory: bool = False
483
+ scan_rag_context: bool = True
484
+
485
+ # Threat handling - DEFAULT IS LOG-ONLY (safe default)
486
+ on_threat: Literal["log", "block", "warn"] = "log"
487
+ block_severity_threshold: Literal["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"] = (
488
+ "HIGH"
489
+ )
490
+ allow_severity: list[str] = field(default_factory=list)
491
+
492
+ # Tool validation
493
+ tool_validation: ToolValidationConfig = field(
494
+ default_factory=ToolValidationConfig
495
+ )
496
+
497
+ # Detection settings
498
+ confidence_threshold: float = 0.5
499
+ l2_enabled: bool = True
500
+
501
+ # Callbacks
502
+ on_threat_callback: Callable[[AgentScanResult], None] | None = None
503
+ on_block_callback: Callable[[AgentScanResult], None] | None = None
504
+
505
+ # Telemetry
506
+ telemetry_enabled: bool = True
507
+
508
+ # Performance
509
+ timeout_ms: float = 100.0
510
+ fail_open: bool = True
511
+ max_prompt_length: int = 50000
512
+
513
+ def should_scan(self, scan_type: ScanType) -> bool:
514
+ """Check if a specific scan type should be enabled.
515
+
516
+ Args:
517
+ scan_type: The type of content to potentially scan
518
+
519
+ Returns:
520
+ True if scanning is enabled for this type
521
+ """
522
+ mapping = {
523
+ ScanType.PROMPT: self.scan_prompts,
524
+ ScanType.SYSTEM_PROMPT: self.scan_system_prompts,
525
+ ScanType.TOOL_CALL: self.scan_tool_calls,
526
+ ScanType.TOOL_RESULT: self.scan_tool_results,
527
+ ScanType.RESPONSE: self.scan_responses,
528
+ ScanType.MEMORY_CONTENT: self.scan_memory,
529
+ ScanType.RAG_CONTEXT: self.scan_rag_context,
530
+ ScanType.AGENT_ACTION: self.scan_prompts, # Use prompt setting
531
+ ScanType.CHAIN_INPUT: self.scan_prompts,
532
+ ScanType.CHAIN_OUTPUT: self.scan_responses,
533
+ }
534
+ return mapping.get(scan_type, False)
535
+
536
+
537
+ # =============================================================================
538
+ # Exceptions
539
+ # =============================================================================
540
+
541
+
542
+ class ThreatDetectedError(RaxeException):
543
+ """Raised when a threat is detected and blocking is enabled.
544
+
545
+ This exception is raised when:
546
+ - A threat is detected during agent operation
547
+ - The AgentScannerConfig has on_threat="block"
548
+ - The severity meets or exceeds block_severity_threshold
549
+
550
+ Attributes:
551
+ result: The AgentScanResult that triggered the block
552
+ """
553
+
554
+ def __init__(
555
+ self,
556
+ result: AgentScanResult,
557
+ message: str | None = None,
558
+ ) -> None:
559
+ """Initialize threat detected error.
560
+
561
+ Args:
562
+ result: AgentScanResult containing threat details
563
+ message: Optional custom message
564
+ """
565
+ self.result = result
566
+
567
+ error = RaxeError(
568
+ code=ErrorCode.SEC_THREAT_DETECTED,
569
+ message=message
570
+ or f"Agent threat detected: {result.severity} severity ({result.detection_count} detection(s))",
571
+ details={
572
+ "severity": result.severity,
573
+ "threat_count": result.detection_count,
574
+ "scan_type": result.scan_type.value,
575
+ "rule_ids": result.rule_ids[:5],
576
+ "families": result.families,
577
+ },
578
+ remediation="Review the detected threat. If false positive, adjust "
579
+ "AgentScannerConfig.allow_severity or add a suppression rule.",
580
+ )
581
+
582
+ super().__init__(error)
583
+
584
+
585
+ class ToolBlockedError(RaxeException):
586
+ """Raised when a tool call is blocked.
587
+
588
+ Attributes:
589
+ response: The ToolValidationResponse that triggered the block
590
+ """
591
+
592
+ def __init__(
593
+ self,
594
+ response: ToolValidationResponse,
595
+ message: str | None = None,
596
+ ) -> None:
597
+ """Initialize tool blocked error."""
598
+ self.response = response
599
+
600
+ error = RaxeError(
601
+ code=ErrorCode.SEC_BLOCKED_BY_POLICY,
602
+ message=message or f"Tool blocked: {response.tool_name} - {response.reason}",
603
+ details={
604
+ "tool_name": response.tool_name,
605
+ "reason": response.reason,
606
+ "is_dangerous": response.is_dangerous,
607
+ },
608
+ remediation="Review tool validation config. Add tool to allowlist if safe.",
609
+ )
610
+
611
+ super().__init__(error)
612
+
613
+
614
+ class AgentScanner:
615
+ """Unified scanner for agentic AI systems.
616
+
617
+ This class provides a composable scanner that can be integrated with
618
+ any agentic framework. It handles:
619
+ - Prompt and response scanning
620
+ - Tool call validation and argument scanning
621
+ - Agent action monitoring
622
+ - Trace-aware scanning with correlation IDs
623
+
624
+ Use via composition in framework-specific integrations:
625
+ - LangChain: RaxeCallbackHandler wraps AgentScanner
626
+ - LlamaIndex: RaxeQueryEngine wraps AgentScanner
627
+ - Custom: Direct AgentScanner usage
628
+
629
+ Attributes:
630
+ raxe: Underlying Raxe client for scanning
631
+ tool_policy: Policy for tool validation
632
+ scan_configs: Per-scan-type configuration
633
+
634
+ Example:
635
+ >>> scanner = AgentScanner()
636
+ >>> result = scanner.scan_tool_call("search", {"query": user_input})
637
+ >>> if result.should_block:
638
+ ... raise SecurityError(result.message)
639
+ """
640
+
641
+ def __init__(
642
+ self,
643
+ raxe_client: Raxe | None = None,
644
+ *,
645
+ tool_policy: ToolPolicy | None = None,
646
+ default_block: bool = False,
647
+ scan_configs: dict[ScanType, ScanConfig] | None = None,
648
+ on_threat: Callable[[AgentScanResult], None] | None = None,
649
+ timeout_ms: float = 100.0,
650
+ fail_open: bool = True,
651
+ integration_type: str | None = None,
652
+ ) -> None:
653
+ """Initialize AgentScanner.
654
+
655
+ Args:
656
+ raxe_client: Optional Raxe instance (creates default if None)
657
+ tool_policy: Policy for tool validation (default: no restrictions)
658
+ default_block: Default blocking behavior (default: False = log-only)
659
+ scan_configs: Per-scan-type configuration overrides
660
+ on_threat: Optional callback invoked when threat detected
661
+ timeout_ms: Scan timeout in milliseconds (default: 100.0)
662
+ fail_open: If scan fails/times out, allow request (default: True).
663
+ Set to False for fail-closed behavior.
664
+ integration_type: Framework identifier for telemetry (langchain, crewai, etc.)
665
+
666
+ Example:
667
+ # Basic usage with defaults
668
+ scanner = AgentScanner()
669
+
670
+ # With tool restrictions
671
+ scanner = AgentScanner(
672
+ tool_policy=ToolPolicy.block_tools("shell", "file_write")
673
+ )
674
+
675
+ # Blocking mode for prompts only
676
+ scanner = AgentScanner(
677
+ scan_configs={
678
+ ScanType.PROMPT: ScanConfig(block_on_threat=True),
679
+ ScanType.RESPONSE: ScanConfig(block_on_threat=False),
680
+ }
681
+ )
682
+
683
+ # Fail-closed mode (block on timeout/error)
684
+ scanner = AgentScanner(
685
+ fail_open=False,
686
+ timeout_ms=50.0,
687
+ )
688
+ """
689
+ self.raxe = raxe_client or Raxe()
690
+ self.tool_policy = tool_policy or ToolPolicy()
691
+ self.default_block = default_block
692
+ self.on_threat = on_threat
693
+ self.timeout_ms = timeout_ms
694
+ self.fail_open = fail_open
695
+ self.integration_type = integration_type
696
+
697
+ # Initialize default scan configs
698
+ self._scan_configs: dict[ScanType, ScanConfig] = {
699
+ scan_type: ScanConfig(block_on_threat=default_block)
700
+ for scan_type in ScanType
701
+ }
702
+
703
+ # Apply custom configs
704
+ if scan_configs:
705
+ for scan_type, config in scan_configs.items():
706
+ self._scan_configs[scan_type] = config
707
+
708
+ # Trace management
709
+ self._current_trace_id: str | None = None
710
+ self._step_counter: int = 0
711
+
712
+ logger.debug(
713
+ "AgentScanner initialized",
714
+ extra={
715
+ "tool_policy_mode": self.tool_policy.mode.value,
716
+ "default_block": default_block,
717
+ },
718
+ )
719
+
720
+ def start_trace(self, trace_id: str | None = None) -> str:
721
+ """Start a new agent trace for correlation.
722
+
723
+ Call this at the start of an agent run to enable step correlation.
724
+
725
+ Args:
726
+ trace_id: Optional custom trace ID (generates UUID if None)
727
+
728
+ Returns:
729
+ The trace ID being used
730
+
731
+ Example:
732
+ trace_id = scanner.start_trace()
733
+ # ... agent execution ...
734
+ scanner.end_trace()
735
+ """
736
+ self._current_trace_id = trace_id or str(uuid.uuid4())
737
+ self._step_counter = 0
738
+ logger.debug(f"Started agent trace: {self._current_trace_id}")
739
+ return self._current_trace_id
740
+
741
+ def end_trace(self) -> None:
742
+ """End the current agent trace."""
743
+ if self._current_trace_id:
744
+ logger.debug(
745
+ f"Ended agent trace: {self._current_trace_id}, "
746
+ f"steps: {self._step_counter}"
747
+ )
748
+ self._current_trace_id = None
749
+ self._step_counter = 0
750
+
751
+ def _get_trace_id(self) -> str:
752
+ """Get current trace ID or generate one."""
753
+ return self._current_trace_id or str(uuid.uuid4())
754
+
755
+ def _next_step(self) -> int:
756
+ """Increment and return step counter."""
757
+ self._step_counter += 1
758
+ return self._step_counter
759
+
760
+ def _build_result(
761
+ self,
762
+ scan_type: ScanType,
763
+ has_threats: bool,
764
+ should_block: bool,
765
+ severity: str | None,
766
+ detection_count: int,
767
+ duration_ms: float,
768
+ message: str,
769
+ details: dict[str, Any] | None = None,
770
+ policy_violation: bool = False,
771
+ content: str | None = None,
772
+ ) -> AgentScanResult:
773
+ """Build an AgentScanResult with trace context.
774
+
775
+ Args:
776
+ content: The scanned content (used for hash, NOT stored in result)
777
+ """
778
+ # Compute privacy-preserving hash of content
779
+ prompt_hash = ""
780
+ if content:
781
+ prompt_hash = f"sha256:{hashlib.sha256(content.encode()).hexdigest()}"
782
+
783
+ # Determine action taken
784
+ action_taken = "allow"
785
+ if has_threats:
786
+ action_taken = "block" if should_block else "log"
787
+
788
+ return AgentScanResult(
789
+ scan_type=scan_type,
790
+ has_threats=has_threats,
791
+ should_block=should_block,
792
+ severity=severity,
793
+ detection_count=detection_count,
794
+ trace_id=self._get_trace_id(),
795
+ step_id=self._next_step(),
796
+ duration_ms=duration_ms,
797
+ message=message,
798
+ details=details or {},
799
+ policy_violation=policy_violation,
800
+ prompt_hash=prompt_hash,
801
+ action_taken=action_taken,
802
+ )
803
+
804
+ def _should_block(
805
+ self,
806
+ scan_type: ScanType,
807
+ severity: str | None,
808
+ ) -> bool:
809
+ """Determine if action should be blocked based on config and severity."""
810
+ config = self._scan_configs.get(scan_type)
811
+ if not config or not config.block_on_threat:
812
+ return False
813
+
814
+ if not severity:
815
+ return False
816
+
817
+ # Severity ordering
818
+ severity_order = {"INFO": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 3, "CRITICAL": 4}
819
+ min_severity = severity_order.get(config.min_severity_to_block, 2)
820
+ actual_severity = severity_order.get(severity.upper(), 0)
821
+
822
+ return actual_severity >= min_severity
823
+
824
+ def _scan_with_timeout(
825
+ self,
826
+ text: str,
827
+ *,
828
+ block_on_threat: bool = False,
829
+ ) -> tuple[Any, bool, str | None]:
830
+ """Execute scan with timeout and fail-open/fail-closed handling.
831
+
832
+ This method wraps the actual scan call with:
833
+ - Configurable timeout (timeout_ms from AgentScannerConfig)
834
+ - Fail-open (default) or fail-closed behavior on errors/timeouts
835
+
836
+ Args:
837
+ text: Text to scan
838
+ block_on_threat: Whether to block on threat detection
839
+
840
+ Returns:
841
+ Tuple of (scan_result, timed_out, error_message)
842
+ - scan_result: ScanResult from Raxe.scan() or None if error/timeout
843
+ - timed_out: True if the scan timed out
844
+ - error_message: Error message if error occurred, None otherwise
845
+
846
+ Note:
847
+ When fail_open=True (default):
848
+ - Timeout/error → allow request (result=None, treated as clean)
849
+ When fail_open=False (fail-closed):
850
+ - Timeout/error → block request (raise or return blocking result)
851
+ """
852
+ import concurrent.futures
853
+
854
+ timeout_seconds = self.timeout_ms / 1000.0
855
+
856
+ try:
857
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
858
+ future = executor.submit(
859
+ self.raxe.scan,
860
+ text,
861
+ block_on_threat=block_on_threat,
862
+ integration_type=self.integration_type,
863
+ )
864
+ try:
865
+ result = future.result(timeout=timeout_seconds)
866
+ return result, False, None
867
+ except concurrent.futures.TimeoutError:
868
+ # Scan timed out
869
+ logger.warning(
870
+ "scan_timeout",
871
+ extra={
872
+ "timeout_ms": self.timeout_ms,
873
+ "fail_open": self.fail_open,
874
+ },
875
+ )
876
+ return None, True, f"Scan timed out after {self.timeout_ms}ms"
877
+
878
+ except Exception as e:
879
+ # Scan failed due to error
880
+ logger.error(
881
+ "scan_error",
882
+ extra={
883
+ "error": str(e),
884
+ "error_type": type(e).__name__,
885
+ "fail_open": self.fail_open,
886
+ },
887
+ )
888
+ return None, False, f"Scan error: {e}"
889
+
890
+ def scan_prompt(
891
+ self,
892
+ prompt: str,
893
+ *,
894
+ metadata: dict[str, Any] | None = None,
895
+ ) -> AgentScanResult:
896
+ """Scan a prompt before sending to LLM.
897
+
898
+ Args:
899
+ prompt: The prompt text to scan
900
+ metadata: Optional metadata about the prompt
901
+
902
+ Returns:
903
+ AgentScanResult with scan results
904
+
905
+ Raises:
906
+ SecurityException: If blocking enabled and threat detected,
907
+ or if fail_open=False and scan times out/fails
908
+ """
909
+ if not prompt or not prompt.strip():
910
+ return self._build_result(
911
+ scan_type=ScanType.PROMPT,
912
+ has_threats=False,
913
+ should_block=False,
914
+ severity=None,
915
+ detection_count=0,
916
+ duration_ms=0.0,
917
+ message="Empty prompt skipped",
918
+ content=prompt,
919
+ )
920
+
921
+ start = time.perf_counter()
922
+ config = self._scan_configs[ScanType.PROMPT]
923
+
924
+ # Use timeout wrapper
925
+ result, timed_out, error_msg = self._scan_with_timeout(
926
+ prompt, block_on_threat=config.block_on_threat
927
+ )
928
+ duration_ms = (time.perf_counter() - start) * 1000
929
+
930
+ # Handle timeout or error
931
+ if result is None:
932
+ if self.fail_open:
933
+ # Fail-open: allow request when scan fails/times out
934
+ return self._build_result(
935
+ scan_type=ScanType.PROMPT,
936
+ has_threats=False,
937
+ should_block=False,
938
+ severity=None,
939
+ detection_count=0,
940
+ duration_ms=duration_ms,
941
+ message=f"Scan failed (fail-open): {error_msg}",
942
+ content=prompt,
943
+ )
944
+ else:
945
+ # Fail-closed: block request when scan fails/times out
946
+ from raxe.sdk.exceptions import ScanTimeoutError
947
+
948
+ raise ScanTimeoutError(
949
+ f"Scan failed (fail-closed): {error_msg}",
950
+ timeout_ms=self.timeout_ms,
951
+ )
952
+
953
+ should_block = self._should_block(ScanType.PROMPT, result.severity)
954
+
955
+ agent_result = self._build_result(
956
+ scan_type=ScanType.PROMPT,
957
+ has_threats=result.has_threats,
958
+ should_block=should_block,
959
+ severity=result.severity,
960
+ detection_count=result.total_detections,
961
+ duration_ms=duration_ms,
962
+ message=f"Prompt scan: {result.severity or 'clean'}"
963
+ if result.has_threats
964
+ else "Prompt scan: clean",
965
+ details=metadata,
966
+ content=prompt,
967
+ )
968
+
969
+ if result.has_threats and self.on_threat:
970
+ self.on_threat(agent_result)
971
+
972
+ return agent_result
973
+
974
+ def scan_response(
975
+ self,
976
+ response: str,
977
+ *,
978
+ metadata: dict[str, Any] | None = None,
979
+ ) -> AgentScanResult:
980
+ """Scan an LLM response.
981
+
982
+ Args:
983
+ response: The response text to scan
984
+ metadata: Optional metadata about the response
985
+
986
+ Returns:
987
+ AgentScanResult with scan results
988
+
989
+ Raises:
990
+ SecurityException: If blocking enabled and threat detected,
991
+ or if fail_open=False and scan times out/fails
992
+ """
993
+ if not response or not response.strip():
994
+ return self._build_result(
995
+ scan_type=ScanType.RESPONSE,
996
+ has_threats=False,
997
+ should_block=False,
998
+ severity=None,
999
+ detection_count=0,
1000
+ duration_ms=0.0,
1001
+ message="Empty response skipped",
1002
+ content=response,
1003
+ )
1004
+
1005
+ start = time.perf_counter()
1006
+ config = self._scan_configs[ScanType.RESPONSE]
1007
+
1008
+ # Use timeout wrapper
1009
+ result, timed_out, error_msg = self._scan_with_timeout(
1010
+ response, block_on_threat=config.block_on_threat
1011
+ )
1012
+ duration_ms = (time.perf_counter() - start) * 1000
1013
+
1014
+ # Handle timeout or error
1015
+ if result is None:
1016
+ if self.fail_open:
1017
+ return self._build_result(
1018
+ scan_type=ScanType.RESPONSE,
1019
+ has_threats=False,
1020
+ should_block=False,
1021
+ severity=None,
1022
+ detection_count=0,
1023
+ duration_ms=duration_ms,
1024
+ message=f"Scan failed (fail-open): {error_msg}",
1025
+ content=response,
1026
+ )
1027
+ else:
1028
+ from raxe.sdk.exceptions import ScanTimeoutError
1029
+
1030
+ raise ScanTimeoutError(
1031
+ f"Scan failed (fail-closed): {error_msg}",
1032
+ timeout_ms=self.timeout_ms,
1033
+ )
1034
+
1035
+ should_block = self._should_block(ScanType.RESPONSE, result.severity)
1036
+
1037
+ agent_result = self._build_result(
1038
+ scan_type=ScanType.RESPONSE,
1039
+ has_threats=result.has_threats,
1040
+ should_block=should_block,
1041
+ severity=result.severity,
1042
+ detection_count=result.total_detections,
1043
+ duration_ms=duration_ms,
1044
+ message=f"Response scan: {result.severity or 'clean'}"
1045
+ if result.has_threats
1046
+ else "Response scan: clean",
1047
+ details=metadata,
1048
+ content=response,
1049
+ )
1050
+
1051
+ if result.has_threats and self.on_threat:
1052
+ self.on_threat(agent_result)
1053
+
1054
+ return agent_result
1055
+
1056
+ def validate_tool(self, tool_name: str) -> tuple[bool, str]:
1057
+ """Validate a tool against the policy (without scanning arguments).
1058
+
1059
+ Args:
1060
+ tool_name: Name of the tool to validate
1061
+
1062
+ Returns:
1063
+ Tuple of (is_allowed, message)
1064
+ """
1065
+ if self.tool_policy.mode == ToolValidationMode.DISABLED:
1066
+ return True, "Tool validation disabled"
1067
+
1068
+ is_allowed = self.tool_policy.is_tool_allowed(tool_name)
1069
+
1070
+ if not is_allowed:
1071
+ if self.tool_policy.mode == ToolValidationMode.ALLOWLIST:
1072
+ return False, f"Tool '{tool_name}' not in allowlist"
1073
+ else:
1074
+ return False, f"Tool '{tool_name}' is blocked"
1075
+
1076
+ return True, "Tool allowed"
1077
+
1078
+ def scan_tool_call(
1079
+ self,
1080
+ tool_name: str,
1081
+ tool_args: dict[str, Any] | str,
1082
+ *,
1083
+ metadata: dict[str, Any] | None = None,
1084
+ ) -> AgentScanResult:
1085
+ """Scan a tool call before execution.
1086
+
1087
+ This method:
1088
+ 1. Validates tool against policy (allowlist/blocklist)
1089
+ 2. Scans tool arguments for threats
1090
+ 3. Checks arguments against forbidden patterns
1091
+
1092
+ Args:
1093
+ tool_name: Name of the tool being called
1094
+ tool_args: Tool arguments (dict or string)
1095
+ metadata: Optional metadata about the tool call
1096
+
1097
+ Returns:
1098
+ AgentScanResult with validation and scan results
1099
+
1100
+ Raises:
1101
+ SecurityException: If blocking enabled and threat/violation detected
1102
+ """
1103
+ start = time.perf_counter()
1104
+ config = self._scan_configs[ScanType.TOOL_CALL]
1105
+ details = {"tool_name": tool_name, **(metadata or {})}
1106
+
1107
+ # Step 1: Validate tool against policy
1108
+ is_allowed, policy_message = self.validate_tool(tool_name)
1109
+
1110
+ if not is_allowed:
1111
+ duration_ms = (time.perf_counter() - start) * 1000
1112
+ agent_result = self._build_result(
1113
+ scan_type=ScanType.TOOL_CALL,
1114
+ has_threats=True,
1115
+ should_block=self.tool_policy.block_on_violation,
1116
+ severity="CRITICAL",
1117
+ detection_count=1,
1118
+ duration_ms=duration_ms,
1119
+ message=policy_message,
1120
+ details=details,
1121
+ policy_violation=True,
1122
+ content=tool_name, # Hash tool name for policy violations
1123
+ )
1124
+
1125
+ if self.on_threat:
1126
+ self.on_threat(agent_result)
1127
+
1128
+ if self.tool_policy.block_on_violation:
1129
+ # Log and raise - create a minimal scan result for the exception
1130
+ logger.warning(
1131
+ f"Tool policy violation: {policy_message}",
1132
+ extra={"tool_name": tool_name},
1133
+ )
1134
+ # We need to create a ScanPipelineResult for the exception
1135
+ # For now, just log and return the result
1136
+ # The caller should check should_block and handle accordingly
1137
+
1138
+ return agent_result
1139
+
1140
+ # Step 2: Convert args to string for scanning
1141
+ if isinstance(tool_args, dict):
1142
+ # Scan values, not keys (to avoid false positives on key names)
1143
+ args_text = " ".join(str(v) for v in tool_args.values() if v)
1144
+ else:
1145
+ args_text = str(tool_args)
1146
+
1147
+ if not args_text or not args_text.strip():
1148
+ return self._build_result(
1149
+ scan_type=ScanType.TOOL_CALL,
1150
+ has_threats=False,
1151
+ should_block=False,
1152
+ severity=None,
1153
+ detection_count=0,
1154
+ duration_ms=(time.perf_counter() - start) * 1000,
1155
+ message=f"Tool '{tool_name}' call: clean (no args)",
1156
+ details=details,
1157
+ content=tool_name, # Hash tool name for empty args case
1158
+ )
1159
+
1160
+ # Step 3: Check forbidden argument patterns for this tool
1161
+ if tool_name in self.tool_policy.argument_patterns:
1162
+ for pattern in self.tool_policy.argument_patterns[tool_name]:
1163
+ if pattern.search(args_text):
1164
+ duration_ms = (time.perf_counter() - start) * 1000
1165
+ return self._build_result(
1166
+ scan_type=ScanType.TOOL_CALL,
1167
+ has_threats=True,
1168
+ should_block=self.tool_policy.block_on_violation,
1169
+ severity="HIGH",
1170
+ detection_count=1,
1171
+ duration_ms=duration_ms,
1172
+ message=f"Tool '{tool_name}' argument matches forbidden pattern",
1173
+ details=details,
1174
+ policy_violation=True,
1175
+ content=args_text, # Hash args for forbidden pattern match
1176
+ )
1177
+
1178
+ # Step 4: Scan arguments for threats
1179
+ try:
1180
+ result = self.raxe.scan(
1181
+ args_text,
1182
+ block_on_threat=config.block_on_threat,
1183
+ )
1184
+
1185
+ duration_ms = (time.perf_counter() - start) * 1000
1186
+ should_block = self._should_block(ScanType.TOOL_CALL, result.severity)
1187
+
1188
+ agent_result = self._build_result(
1189
+ scan_type=ScanType.TOOL_CALL,
1190
+ has_threats=result.has_threats,
1191
+ should_block=should_block,
1192
+ severity=result.severity,
1193
+ detection_count=result.total_detections,
1194
+ duration_ms=duration_ms,
1195
+ message=f"Tool '{tool_name}' call: {result.severity or 'clean'}"
1196
+ if result.has_threats
1197
+ else f"Tool '{tool_name}' call: clean",
1198
+ details=details,
1199
+ content=args_text, # Hash args for main scan result
1200
+ )
1201
+
1202
+ if result.has_threats and self.on_threat:
1203
+ self.on_threat(agent_result)
1204
+
1205
+ return agent_result
1206
+
1207
+ except SecurityException:
1208
+ duration_ms = (time.perf_counter() - start) * 1000
1209
+ raise
1210
+
1211
+ def scan_tool_result(
1212
+ self,
1213
+ tool_name: str,
1214
+ result: str,
1215
+ *,
1216
+ metadata: dict[str, Any] | None = None,
1217
+ ) -> AgentScanResult:
1218
+ """Scan a tool result after execution.
1219
+
1220
+ Args:
1221
+ tool_name: Name of the tool that produced the result
1222
+ result: The tool's output to scan
1223
+ metadata: Optional metadata about the tool result
1224
+
1225
+ Returns:
1226
+ AgentScanResult with scan results
1227
+
1228
+ Raises:
1229
+ SecurityException: If blocking enabled and threat detected
1230
+ """
1231
+ if not result or not result.strip():
1232
+ return self._build_result(
1233
+ scan_type=ScanType.TOOL_RESULT,
1234
+ has_threats=False,
1235
+ should_block=False,
1236
+ severity=None,
1237
+ detection_count=0,
1238
+ duration_ms=0.0,
1239
+ message=f"Tool '{tool_name}' result: empty",
1240
+ details={"tool_name": tool_name, **(metadata or {})},
1241
+ content=tool_name, # Hash tool name for empty result case
1242
+ )
1243
+
1244
+ start = time.perf_counter()
1245
+ config = self._scan_configs[ScanType.TOOL_RESULT]
1246
+ details = {"tool_name": tool_name, **(metadata or {})}
1247
+
1248
+ try:
1249
+ scan_result = self.raxe.scan(
1250
+ result,
1251
+ block_on_threat=config.block_on_threat,
1252
+ )
1253
+
1254
+ duration_ms = (time.perf_counter() - start) * 1000
1255
+ should_block = self._should_block(ScanType.TOOL_RESULT, scan_result.severity)
1256
+
1257
+ agent_result = self._build_result(
1258
+ scan_type=ScanType.TOOL_RESULT,
1259
+ has_threats=scan_result.has_threats,
1260
+ should_block=should_block,
1261
+ severity=scan_result.severity,
1262
+ detection_count=scan_result.total_detections,
1263
+ duration_ms=duration_ms,
1264
+ message=f"Tool '{tool_name}' result: {scan_result.severity or 'clean'}"
1265
+ if scan_result.has_threats
1266
+ else f"Tool '{tool_name}' result: clean",
1267
+ details=details,
1268
+ content=result, # Hash tool result content
1269
+ )
1270
+
1271
+ if scan_result.has_threats and self.on_threat:
1272
+ self.on_threat(agent_result)
1273
+
1274
+ return agent_result
1275
+
1276
+ except SecurityException:
1277
+ duration_ms = (time.perf_counter() - start) * 1000
1278
+ raise
1279
+
1280
+ def scan_agent_action(
1281
+ self,
1282
+ action_type: str,
1283
+ action_input: str | dict[str, Any],
1284
+ *,
1285
+ tool_name: str | None = None,
1286
+ metadata: dict[str, Any] | None = None,
1287
+ ) -> AgentScanResult:
1288
+ """Scan an agent action.
1289
+
1290
+ Args:
1291
+ action_type: Type of action (e.g., "tool_call", "final_answer")
1292
+ action_input: The action's input to scan
1293
+ tool_name: Optional tool name if action is a tool call
1294
+ metadata: Optional metadata about the action
1295
+
1296
+ Returns:
1297
+ AgentScanResult with scan results
1298
+
1299
+ Raises:
1300
+ SecurityException: If blocking enabled and threat detected
1301
+ """
1302
+ # If this is a tool call, delegate to scan_tool_call
1303
+ if tool_name and action_type == "tool_call":
1304
+ args = action_input if isinstance(action_input, dict) else {"input": action_input}
1305
+ return self.scan_tool_call(tool_name, args, metadata=metadata)
1306
+
1307
+ # Otherwise, scan the action input as text
1308
+ if isinstance(action_input, dict):
1309
+ input_text = " ".join(str(v) for v in action_input.values() if v)
1310
+ else:
1311
+ input_text = str(action_input)
1312
+
1313
+ if not input_text or not input_text.strip():
1314
+ return self._build_result(
1315
+ scan_type=ScanType.AGENT_ACTION,
1316
+ has_threats=False,
1317
+ should_block=False,
1318
+ severity=None,
1319
+ detection_count=0,
1320
+ duration_ms=0.0,
1321
+ message=f"Agent action '{action_type}': empty input",
1322
+ details={"action_type": action_type, **(metadata or {})},
1323
+ content=action_type, # Hash action type for empty input case
1324
+ )
1325
+
1326
+ start = time.perf_counter()
1327
+ config = self._scan_configs[ScanType.AGENT_ACTION]
1328
+ details = {"action_type": action_type, "tool_name": tool_name, **(metadata or {})}
1329
+
1330
+ try:
1331
+ result = self.raxe.scan(
1332
+ input_text,
1333
+ block_on_threat=config.block_on_threat,
1334
+ )
1335
+
1336
+ duration_ms = (time.perf_counter() - start) * 1000
1337
+ should_block = self._should_block(ScanType.AGENT_ACTION, result.severity)
1338
+
1339
+ agent_result = self._build_result(
1340
+ scan_type=ScanType.AGENT_ACTION,
1341
+ has_threats=result.has_threats,
1342
+ should_block=should_block,
1343
+ severity=result.severity,
1344
+ detection_count=result.total_detections,
1345
+ duration_ms=duration_ms,
1346
+ message=f"Agent action '{action_type}': {result.severity or 'clean'}"
1347
+ if result.has_threats
1348
+ else f"Agent action '{action_type}': clean",
1349
+ details=details,
1350
+ content=input_text, # Hash action input
1351
+ )
1352
+
1353
+ if result.has_threats and self.on_threat:
1354
+ self.on_threat(agent_result)
1355
+
1356
+ return agent_result
1357
+
1358
+ except SecurityException:
1359
+ duration_ms = (time.perf_counter() - start) * 1000
1360
+ raise
1361
+
1362
+ def get_config(self, scan_type: ScanType) -> ScanConfig:
1363
+ """Get configuration for a scan type.
1364
+
1365
+ Args:
1366
+ scan_type: The scan type to get config for
1367
+
1368
+ Returns:
1369
+ ScanConfig for the specified type
1370
+ """
1371
+ return self._scan_configs.get(scan_type, ScanConfig())
1372
+
1373
+ def set_config(self, scan_type: ScanType, config: ScanConfig) -> None:
1374
+ """Set configuration for a scan type.
1375
+
1376
+ Args:
1377
+ scan_type: The scan type to configure
1378
+ config: The configuration to apply
1379
+ """
1380
+ self._scan_configs[scan_type] = config
1381
+
1382
+ # =========================================================================
1383
+ # New validate_tool_call method (returns ToolValidationResponse)
1384
+ # =========================================================================
1385
+
1386
+ def validate_tool_call(
1387
+ self,
1388
+ tool_name: str,
1389
+ arguments: dict[str, Any] | None = None,
1390
+ *,
1391
+ context: dict[str, Any] | None = None,
1392
+ raise_on_block: bool = False,
1393
+ ) -> ToolValidationResponse:
1394
+ """Validate a tool call before execution.
1395
+
1396
+ This is a new method that returns ToolValidationResponse with detailed
1397
+ validation information. Checks tool against allowlist/blocklist and
1398
+ optionally scans arguments for threats.
1399
+
1400
+ Validation order:
1401
+ 1. Check blocklist (blocked if present)
1402
+ 2. Check allowlist (if non-empty, must be present)
1403
+ 3. Check if tool is dangerous (requires extra scrutiny)
1404
+ 4. Scan arguments if configured
1405
+
1406
+ Args:
1407
+ tool_name: Name of the tool being called
1408
+ arguments: Tool arguments to validate (optional)
1409
+ context: Optional context metadata
1410
+ raise_on_block: Raise ToolBlockedError if blocked
1411
+
1412
+ Returns:
1413
+ ToolValidationResponse with validation outcome
1414
+
1415
+ Raises:
1416
+ ToolBlockedError: If raise_on_block=True and tool is blocked
1417
+
1418
+ Example:
1419
+ response = scanner.validate_tool_call(
1420
+ tool_name="shell_execute",
1421
+ arguments={"command": "ls -la"},
1422
+ )
1423
+ if not response.is_allowed:
1424
+ print(f"Tool blocked: {response.reason}")
1425
+ """
1426
+ # Get tool validation config (use ToolValidationConfig if available via AgentScannerConfig)
1427
+ # For backward compatibility, also check tool_policy
1428
+ tool_name_lower = tool_name.lower()
1429
+
1430
+ # Check against existing tool_policy (backward compatibility)
1431
+ if self.tool_policy.mode != ToolValidationMode.DISABLED:
1432
+ if not self.tool_policy.is_tool_allowed(tool_name):
1433
+ reason = (
1434
+ f"Tool '{tool_name}' not in allowlist"
1435
+ if self.tool_policy.mode == ToolValidationMode.ALLOWLIST
1436
+ else f"Tool '{tool_name}' is blocked"
1437
+ )
1438
+ response = ToolValidationResponse(
1439
+ is_allowed=False,
1440
+ result=ToolValidationResult.BLOCKED,
1441
+ reason=reason,
1442
+ tool_name=tool_name,
1443
+ metadata={"blocked_by": self.tool_policy.mode.value},
1444
+ )
1445
+ if raise_on_block:
1446
+ raise ToolBlockedError(response)
1447
+ return response
1448
+
1449
+ # Check if tool is dangerous (based on common dangerous tool names)
1450
+ dangerous_tools = [
1451
+ "shell", "bash", "exec", "execute", "run_command",
1452
+ "shell_execute", "code_interpreter", "python_repl",
1453
+ "eval", "subprocess", "terminal", "ssh",
1454
+ ]
1455
+ is_dangerous = any(dt in tool_name_lower for dt in dangerous_tools)
1456
+
1457
+ # Scan arguments if provided
1458
+ scan_result: AgentScanResult | None = None
1459
+ arguments_scanned = False
1460
+
1461
+ if arguments:
1462
+ # Convert args to string for scanning
1463
+ if isinstance(arguments, dict):
1464
+ args_text = " ".join(str(v) for v in arguments.values() if v)
1465
+ else:
1466
+ args_text = str(arguments)
1467
+
1468
+ if args_text and args_text.strip():
1469
+ # Scan the arguments
1470
+ scan_result = self.scan_tool_call(
1471
+ tool_name,
1472
+ arguments,
1473
+ metadata=context,
1474
+ )
1475
+ arguments_scanned = True
1476
+
1477
+ # Block if scan result says to block
1478
+ if scan_result.should_block:
1479
+ response = ToolValidationResponse(
1480
+ is_allowed=False,
1481
+ result=ToolValidationResult.BLOCKED,
1482
+ reason=f"Threat detected in {tool_name} arguments: {scan_result.severity}",
1483
+ tool_name=tool_name,
1484
+ is_dangerous=is_dangerous,
1485
+ arguments_scanned=True,
1486
+ scan_result=scan_result,
1487
+ metadata={
1488
+ "blocked_by": "argument_scan",
1489
+ "severity": scan_result.severity,
1490
+ },
1491
+ )
1492
+ if raise_on_block:
1493
+ raise ToolBlockedError(response)
1494
+ return response
1495
+
1496
+ # Mark as suspicious if threats found but not blocking
1497
+ if scan_result.has_threats:
1498
+ return ToolValidationResponse(
1499
+ is_allowed=True,
1500
+ result=ToolValidationResult.SUSPICIOUS,
1501
+ reason=f"Suspicious content in arguments: {scan_result.severity}",
1502
+ tool_name=tool_name,
1503
+ is_dangerous=is_dangerous,
1504
+ arguments_scanned=True,
1505
+ scan_result=scan_result,
1506
+ )
1507
+
1508
+ # Tool allowed
1509
+ return ToolValidationResponse(
1510
+ is_allowed=True,
1511
+ result=ToolValidationResult.ALLOWED,
1512
+ reason="Tool validation passed",
1513
+ tool_name=tool_name,
1514
+ is_dangerous=is_dangerous,
1515
+ arguments_scanned=arguments_scanned,
1516
+ scan_result=scan_result,
1517
+ )
1518
+
1519
+ # =========================================================================
1520
+ # Async Variants
1521
+ # =========================================================================
1522
+
1523
+ async def scan_prompt_async(
1524
+ self,
1525
+ prompt: str,
1526
+ *,
1527
+ metadata: dict[str, Any] | None = None,
1528
+ ) -> AgentScanResult:
1529
+ """Async wrapper for scan_prompt.
1530
+
1531
+ Runs the synchronous scan in a thread pool executor.
1532
+
1533
+ Args:
1534
+ prompt: The prompt text to scan
1535
+ metadata: Optional metadata about the prompt
1536
+
1537
+ Returns:
1538
+ AgentScanResult with scan results
1539
+ """
1540
+ loop = asyncio.get_event_loop()
1541
+ return await loop.run_in_executor(
1542
+ None,
1543
+ lambda: self.scan_prompt(prompt, metadata=metadata),
1544
+ )
1545
+
1546
+ async def scan_response_async(
1547
+ self,
1548
+ response: str,
1549
+ *,
1550
+ metadata: dict[str, Any] | None = None,
1551
+ ) -> AgentScanResult:
1552
+ """Async wrapper for scan_response."""
1553
+ loop = asyncio.get_event_loop()
1554
+ return await loop.run_in_executor(
1555
+ None,
1556
+ lambda: self.scan_response(response, metadata=metadata),
1557
+ )
1558
+
1559
+ async def scan_tool_call_async(
1560
+ self,
1561
+ tool_name: str,
1562
+ tool_args: dict[str, Any] | str,
1563
+ *,
1564
+ metadata: dict[str, Any] | None = None,
1565
+ ) -> AgentScanResult:
1566
+ """Async wrapper for scan_tool_call."""
1567
+ loop = asyncio.get_event_loop()
1568
+ return await loop.run_in_executor(
1569
+ None,
1570
+ lambda: self.scan_tool_call(tool_name, tool_args, metadata=metadata),
1571
+ )
1572
+
1573
+ async def validate_tool_call_async(
1574
+ self,
1575
+ tool_name: str,
1576
+ arguments: dict[str, Any] | None = None,
1577
+ *,
1578
+ context: dict[str, Any] | None = None,
1579
+ raise_on_block: bool = False,
1580
+ ) -> ToolValidationResponse:
1581
+ """Async wrapper for validate_tool_call."""
1582
+ loop = asyncio.get_event_loop()
1583
+ return await loop.run_in_executor(
1584
+ None,
1585
+ lambda: self.validate_tool_call(
1586
+ tool_name,
1587
+ arguments,
1588
+ context=context,
1589
+ raise_on_block=raise_on_block,
1590
+ ),
1591
+ )
1592
+
1593
+ async def scan_agent_action_async(
1594
+ self,
1595
+ action_type: str,
1596
+ action_input: str | dict[str, Any],
1597
+ *,
1598
+ tool_name: str | None = None,
1599
+ metadata: dict[str, Any] | None = None,
1600
+ ) -> AgentScanResult:
1601
+ """Async wrapper for scan_agent_action."""
1602
+ loop = asyncio.get_event_loop()
1603
+ return await loop.run_in_executor(
1604
+ None,
1605
+ lambda: self.scan_agent_action(
1606
+ action_type,
1607
+ action_input,
1608
+ tool_name=tool_name,
1609
+ metadata=metadata,
1610
+ ),
1611
+ )
1612
+
1613
+ # =========================================================================
1614
+ # Telemetry Methods
1615
+ # =========================================================================
1616
+
1617
+ def _emit_agent_telemetry(
1618
+ self,
1619
+ result: AgentScanResult,
1620
+ context: dict[str, Any] | None = None,
1621
+ ) -> None:
1622
+ """Emit agent-specific telemetry (non-blocking).
1623
+
1624
+ Telemetry is privacy-preserving:
1625
+ - prompt_hash (SHA256)
1626
+ - metadata and counts only
1627
+ - NO actual prompt content
1628
+
1629
+ Args:
1630
+ result: The scan result to track
1631
+ context: Optional context metadata
1632
+ """
1633
+ try:
1634
+ from raxe.application.telemetry_orchestrator import get_orchestrator
1635
+
1636
+ orchestrator = get_orchestrator()
1637
+ if orchestrator is None or not orchestrator.is_enabled():
1638
+ return
1639
+
1640
+ # Track as feature usage for agent-specific metrics
1641
+ orchestrator.track_feature_usage(
1642
+ feature="integration_agent",
1643
+ action="completed" if not result.has_threats else "invoked",
1644
+ duration_ms=result.duration_ms,
1645
+ success=True,
1646
+ metadata={
1647
+ "scan_type": result.scan_type.value,
1648
+ "has_threats": result.has_threats,
1649
+ "severity": result.severity,
1650
+ "action_taken": result.action_taken,
1651
+ "trace_id": result.trace_id,
1652
+ "step_id": result.step_id,
1653
+ "framework": context.get("framework") if context else None,
1654
+ },
1655
+ )
1656
+ except Exception as e:
1657
+ # Never let telemetry break scanning
1658
+ logger.debug(f"Agent telemetry error (non-blocking): {e}")
1659
+
1660
+ # =========================================================================
1661
+ # Convenience Methods
1662
+ # =========================================================================
1663
+
1664
+ def should_block_result(self, result: AgentScanResult) -> bool:
1665
+ """Determine if a scan result should trigger blocking.
1666
+
1667
+ Useful for checking results from external scans.
1668
+
1669
+ Args:
1670
+ result: Scan result to check
1671
+
1672
+ Returns:
1673
+ True if the result should trigger blocking
1674
+ """
1675
+ return result.should_block or result.policy_violation
1676
+
1677
+ def scan_rag_context(
1678
+ self,
1679
+ documents: list[str],
1680
+ *,
1681
+ metadata: dict[str, Any] | None = None,
1682
+ ) -> list[AgentScanResult]:
1683
+ """Scan RAG-retrieved documents for threats.
1684
+
1685
+ Args:
1686
+ documents: List of document texts to scan
1687
+ metadata: Optional context metadata
1688
+
1689
+ Returns:
1690
+ List of AgentScanResult, one per document
1691
+ """
1692
+ results = []
1693
+ for i, doc in enumerate(documents):
1694
+ doc_metadata = {**(metadata or {}), "document_index": i}
1695
+ # Use scan_prompt with RAG_CONTEXT config
1696
+ result = self._build_result(
1697
+ scan_type=ScanType.RAG_CONTEXT,
1698
+ has_threats=False,
1699
+ should_block=False,
1700
+ severity=None,
1701
+ detection_count=0,
1702
+ duration_ms=0.0,
1703
+ message="RAG context scan",
1704
+ details=doc_metadata,
1705
+ content=doc if doc else f"doc_{i}", # Hash document content
1706
+ )
1707
+
1708
+ if doc and doc.strip():
1709
+ start = time.perf_counter()
1710
+ try:
1711
+ scan_result = self.raxe.scan(doc)
1712
+ duration_ms = (time.perf_counter() - start) * 1000
1713
+ result = self._build_result(
1714
+ scan_type=ScanType.RAG_CONTEXT,
1715
+ has_threats=scan_result.has_threats,
1716
+ should_block=self._should_block(ScanType.RAG_CONTEXT, scan_result.severity),
1717
+ severity=scan_result.severity,
1718
+ detection_count=scan_result.total_detections,
1719
+ duration_ms=duration_ms,
1720
+ message=f"RAG context: {scan_result.severity or 'clean'}",
1721
+ details=doc_metadata,
1722
+ content=doc, # Hash document content
1723
+ )
1724
+ except Exception as e:
1725
+ logger.warning(f"RAG context scan failed: {e}")
1726
+
1727
+ results.append(result)
1728
+ return results
1729
+
1730
+ async def scan_rag_context_async(
1731
+ self,
1732
+ documents: list[str],
1733
+ *,
1734
+ metadata: dict[str, Any] | None = None,
1735
+ ) -> list[AgentScanResult]:
1736
+ """Async version of scan_rag_context with parallel scanning."""
1737
+ loop = asyncio.get_event_loop()
1738
+ return await loop.run_in_executor(
1739
+ None,
1740
+ lambda: self.scan_rag_context(documents, metadata=metadata),
1741
+ )
1742
+
1743
+ def scan_message(
1744
+ self,
1745
+ text: str,
1746
+ *,
1747
+ context: ScanContext | None = None,
1748
+ ) -> AgentScanResult:
1749
+ """Scan a message with context-aware routing.
1750
+
1751
+ This method provides a simplified interface for scanning messages
1752
+ in multi-agent systems. It routes to the appropriate scan method
1753
+ based on the message type in the context.
1754
+
1755
+ Args:
1756
+ text: Message text to scan
1757
+ context: Optional context with message type and metadata
1758
+
1759
+ Returns:
1760
+ AgentScanResult with scan results
1761
+
1762
+ Example:
1763
+ >>> result = scanner.scan_message(
1764
+ ... "User input here",
1765
+ ... context=ScanContext(
1766
+ ... message_type=MessageType.HUMAN_INPUT,
1767
+ ... sender_name="user",
1768
+ ... )
1769
+ ... )
1770
+ """
1771
+ if context is None:
1772
+ context = ScanContext(message_type=MessageType.AGENT_TO_AGENT)
1773
+
1774
+ metadata = {
1775
+ "sender_name": context.sender_name,
1776
+ "receiver_name": context.receiver_name,
1777
+ "conversation_id": context.conversation_id,
1778
+ "message_index": context.message_index,
1779
+ **context.metadata,
1780
+ }
1781
+
1782
+ # Route to appropriate method based on message type
1783
+ if context.message_type == MessageType.HUMAN_INPUT:
1784
+ return self.scan_prompt(text, metadata=metadata)
1785
+ elif context.message_type == MessageType.AGENT_RESPONSE:
1786
+ return self.scan_response(text, metadata=metadata)
1787
+ elif context.message_type == MessageType.FUNCTION_CALL:
1788
+ return self.scan_agent_action("function_call", text, metadata=metadata)
1789
+ elif context.message_type == MessageType.FUNCTION_RESULT:
1790
+ return self.scan_tool_result("unknown", text, metadata=metadata)
1791
+ else:
1792
+ # Default to prompt scanning for other message types
1793
+ return self.scan_prompt(text, metadata=metadata)
1794
+
1795
+ def get_stats(self) -> dict[str, Any]:
1796
+ """Get scanner statistics.
1797
+
1798
+ Returns:
1799
+ Dictionary with scan metrics
1800
+ """
1801
+ return {
1802
+ "trace_id": self._current_trace_id,
1803
+ "step_count": self._step_counter,
1804
+ "tool_policy_mode": self.tool_policy.mode.value,
1805
+ "default_block": self.default_block,
1806
+ }
1807
+
1808
+ def __repr__(self) -> str:
1809
+ """String representation of AgentScanner."""
1810
+ return (
1811
+ f"AgentScanner("
1812
+ f"tool_policy={self.tool_policy.mode.value}, "
1813
+ f"default_block={self.default_block}, "
1814
+ f"trace_id={self._current_trace_id})"
1815
+ )
1816
+
1817
+
1818
+ # =============================================================================
1819
+ # Factory Function for AgentScannerConfig-based initialization
1820
+ # =============================================================================
1821
+
1822
+
1823
+ def create_agent_scanner(
1824
+ raxe: Raxe | None = None,
1825
+ config: AgentScannerConfig | None = None,
1826
+ *,
1827
+ integration_type: str | None = None,
1828
+ ) -> AgentScanner:
1829
+ """Factory function to create AgentScanner with AgentScannerConfig.
1830
+
1831
+ This is the recommended way to create an AgentScanner with the new
1832
+ configuration system.
1833
+
1834
+ Args:
1835
+ raxe: Optional Raxe client (creates default if None)
1836
+ config: AgentScannerConfig (uses defaults if None)
1837
+ integration_type: Framework identifier for telemetry (langchain, crewai,
1838
+ llamaindex, autogen, mcp). Used to differentiate scan sources in BQ.
1839
+
1840
+ Returns:
1841
+ Configured AgentScanner instance
1842
+
1843
+ Example:
1844
+ config = AgentScannerConfig(
1845
+ on_threat="block",
1846
+ scan_tool_calls=True,
1847
+ tool_validation=ToolValidationConfig(
1848
+ blocklist=["dangerous_tool"],
1849
+ ),
1850
+ )
1851
+ scanner = create_agent_scanner(config=config)
1852
+ """
1853
+ config = config or AgentScannerConfig()
1854
+
1855
+ # Build ToolPolicy from ToolValidationConfig
1856
+ tool_config = config.tool_validation
1857
+ if tool_config.blocklist:
1858
+ tool_policy = ToolPolicy.block_tools(
1859
+ *tool_config.blocklist,
1860
+ block=config.on_threat == "block",
1861
+ )
1862
+ elif tool_config.allowlist:
1863
+ tool_policy = ToolPolicy.allow_only(
1864
+ *tool_config.allowlist,
1865
+ block=config.on_threat == "block",
1866
+ )
1867
+ else:
1868
+ tool_policy = ToolPolicy()
1869
+
1870
+ # Build scan configs from AgentScannerConfig
1871
+ scan_configs: dict[ScanType, ScanConfig] = {}
1872
+ for scan_type in ScanType:
1873
+ enabled = config.should_scan(scan_type)
1874
+ block_on_threat = config.on_threat == "block"
1875
+ scan_configs[scan_type] = ScanConfig(
1876
+ enabled=enabled,
1877
+ block_on_threat=block_on_threat,
1878
+ min_severity_to_block=config.block_severity_threshold,
1879
+ )
1880
+
1881
+ return AgentScanner(
1882
+ raxe_client=raxe,
1883
+ tool_policy=tool_policy,
1884
+ default_block=config.on_threat == "block",
1885
+ scan_configs=scan_configs,
1886
+ on_threat=config.on_threat_callback,
1887
+ timeout_ms=config.timeout_ms,
1888
+ fail_open=config.fail_open,
1889
+ integration_type=integration_type,
1890
+ )
1891
+
1892
+
1893
+ # =============================================================================
1894
+ # Module Exports
1895
+ # =============================================================================
1896
+
1897
+ __all__ = [
1898
+ # Core class
1899
+ "AgentScanner",
1900
+ # Factory
1901
+ "create_agent_scanner",
1902
+ # Configuration
1903
+ "AgentScannerConfig",
1904
+ "ToolValidationConfig",
1905
+ "ScanConfig",
1906
+ "ToolPolicy",
1907
+ # Result types
1908
+ "AgentScanResult",
1909
+ "ToolValidationResponse",
1910
+ # Enums
1911
+ "ScanType",
1912
+ "ThreatAction",
1913
+ "ToolValidationMode",
1914
+ "ToolValidationResult",
1915
+ # Exceptions
1916
+ "ThreatDetectedError",
1917
+ "ToolBlockedError",
1918
+ ]