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,1059 @@
1
+ """Complete scan pipeline orchestrator.
2
+
3
+ Application layer - integrates all components into a unified scanning pipeline:
4
+ - L1 rule-based detection (Phase 1b)
5
+ - L2 ML-based detection (Phase 1c stub)
6
+ - Result merging (Phase 1c)
7
+ - Pack loading (Phase 2a)
8
+ - Policy evaluation (Phase 3a - if available)
9
+ - Privacy-first telemetry (Phase 3b)
10
+ - Schema validation (Sprint 3)
11
+
12
+ Performance targets:
13
+ - P95 end-to-end latency: <10ms
14
+ - Component breakdown: L1 <5ms, L2 <1ms, overhead <4ms
15
+ """
16
+ import hashlib
17
+ import time
18
+ from dataclasses import dataclass
19
+ from datetime import datetime, timezone
20
+
21
+ from raxe.application.apply_policy import ApplyPolicyUseCase
22
+ from raxe.application.scan_merger import CombinedScanResult, ScanMerger
23
+ from raxe.application.telemetry_orchestrator import get_orchestrator
24
+ from raxe.domain.engine.executor import Detection, RuleExecutor
25
+ from raxe.domain.ml.protocol import L2Detector, L2Result
26
+ from raxe.domain.policies.models import PolicyAction
27
+ from raxe.infrastructure.packs.registry import PackRegistry
28
+ from raxe.infrastructure.telemetry.hook import TelemetryHook
29
+ from raxe.utils.logging import get_logger
30
+
31
+ # Temporary BlockAction enum for backward compatibility
32
+ from enum import Enum
33
+
34
+ class BlockAction(Enum):
35
+ """Temporary backward compatibility - maps to PolicyAction."""
36
+ ALLOW = "ALLOW"
37
+ WARN = "WARN"
38
+ BLOCK = "BLOCK"
39
+ CHALLENGE = "CHALLENGE"
40
+
41
+ # Import metrics collector
42
+ try:
43
+ from raxe.monitoring.metrics import collector
44
+ METRICS_AVAILABLE = True
45
+ except ImportError:
46
+ METRICS_AVAILABLE = False
47
+ collector = None # type: ignore
48
+
49
+ logger = get_logger(__name__)
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class ScanPipelineResult:
54
+ """Complete result from full scan pipeline.
55
+
56
+ Attributes:
57
+ scan_result: Combined L1+L2 detection results
58
+ policy_decision: Action determined by policy (ALLOW/WARN/BLOCK)
59
+ should_block: True if request should be blocked
60
+ duration_ms: Total pipeline execution time
61
+ text_hash: SHA256 hash of scanned text (privacy-preserving)
62
+ metadata: Additional pipeline metadata
63
+ l1_detections: Count of L1 detections
64
+ l2_detections: Count of L2 predictions
65
+ plugin_detections: Count of plugin detections
66
+ l1_duration_ms: L1 processing time
67
+ l2_duration_ms: L2 processing time
68
+ """
69
+ scan_result: CombinedScanResult
70
+ policy_decision: BlockAction
71
+ should_block: bool
72
+ duration_ms: float
73
+ text_hash: str
74
+ metadata: dict[str, object]
75
+ l1_detections: int = 0
76
+ l2_detections: int = 0
77
+ plugin_detections: int = 0
78
+ l1_duration_ms: float = 0.0
79
+ l2_duration_ms: float = 0.0
80
+
81
+ def __post_init__(self) -> None:
82
+ """Validate pipeline result."""
83
+ if self.duration_ms < 0:
84
+ raise ValueError(f"duration_ms cannot be negative: {self.duration_ms}")
85
+
86
+ @property
87
+ def has_threats(self) -> bool:
88
+ """True if any threats detected."""
89
+ return self.scan_result.has_threats
90
+
91
+ @property
92
+ def severity(self) -> str | None:
93
+ """Highest severity across all detections."""
94
+ if self.scan_result.combined_severity:
95
+ return self.scan_result.combined_severity.value
96
+ return None
97
+
98
+ @property
99
+ def total_detections(self) -> int:
100
+ """Total detections across L1 and L2."""
101
+ return self.scan_result.total_threat_count
102
+
103
+ @property
104
+ def detections(self) -> list:
105
+ """All detections from L1 rules as a flat list.
106
+
107
+ Convenience property that provides direct access to L1 detection objects
108
+ without requiring deep nesting into scan_result.l1_result.detections.
109
+
110
+ Note: L2 predictions are not included here as they have a different
111
+ structure (L2Prediction vs Detection). Use scan_result.l2_predictions
112
+ for ML predictions.
113
+
114
+ Returns:
115
+ List of Detection objects from L1 rule matching
116
+
117
+ Example:
118
+ # Instead of:
119
+ result.scan_result.l1_result.detections
120
+
121
+ # Use:
122
+ result.detections
123
+ """
124
+ return self.scan_result.l1_detections
125
+
126
+ def __bool__(self) -> bool:
127
+ """Boolean evaluation: True when safe, False when threats detected.
128
+
129
+ Enables intuitive conditional checks:
130
+ if result: # True when safe (no threats)
131
+ if not result: # True when threats detected
132
+
133
+ This follows the principle that a "good" scan result is truthy.
134
+
135
+ Returns:
136
+ True if no threats detected, False if threats present
137
+
138
+ Example:
139
+ result = pipeline.scan("Hello world")
140
+ if result:
141
+ print("Safe to proceed")
142
+ else:
143
+ print("Threats detected, blocking")
144
+ """
145
+ return not self.has_threats
146
+
147
+ def layer_breakdown(self) -> dict[str, int]:
148
+ """Return detection count by layer.
149
+
150
+ Returns:
151
+ Dictionary with layer names and detection counts
152
+ """
153
+ return {
154
+ "L1": self.l1_detections,
155
+ "L2": self.l2_detections,
156
+ "PLUGIN": self.plugin_detections,
157
+ }
158
+
159
+ def to_dict(self) -> dict[str, object]:
160
+ """Convert to dictionary for serialization.
161
+
162
+ Returns:
163
+ Dictionary representation of pipeline result
164
+ """
165
+ return {
166
+ "has_threats": self.has_threats,
167
+ "should_block": self.should_block,
168
+ "policy_decision": self.policy_decision.value,
169
+ "severity": self.severity,
170
+ "total_detections": self.total_detections,
171
+ "duration_ms": self.duration_ms,
172
+ "text_hash": self.text_hash,
173
+ "scan_result": self.scan_result.to_dict(),
174
+ "metadata": self.metadata,
175
+ "layer_breakdown": self.layer_breakdown(),
176
+ "l1_detections": self.l1_detections,
177
+ "l2_detections": self.l2_detections,
178
+ "plugin_detections": self.plugin_detections,
179
+ "l1_duration_ms": self.l1_duration_ms,
180
+ "l2_duration_ms": self.l2_duration_ms,
181
+ }
182
+
183
+
184
+ class ScanPipeline:
185
+ """Complete scan pipeline orchestrator.
186
+
187
+ Integrates all scanning components into a unified workflow:
188
+ 1. Load rules from pack registry
189
+ 2. Execute L1 rule-based detection
190
+ 3. Execute L2 ML-based analysis (optional, can skip on CRITICAL)
191
+ 4. Merge L1+L2 results
192
+ 5. Evaluate policy to determine action
193
+ 6. Record telemetry (privacy-preserving)
194
+
195
+ This is the main entry point for all scanning operations.
196
+
197
+ Example usage:
198
+ pipeline = ScanPipeline(
199
+ pack_registry=registry,
200
+ rule_executor=executor,
201
+ l2_detector=detector,
202
+ scan_merger=merger,
203
+ apply_policy=ApplyPolicyUseCase(),
204
+ )
205
+
206
+ result = pipeline.scan("Ignore all previous instructions")
207
+ if result.should_block:
208
+ raise BlockedError(result.policy_decision)
209
+ """
210
+
211
+ def __init__(
212
+ self,
213
+ pack_registry: PackRegistry,
214
+ rule_executor: RuleExecutor,
215
+ l2_detector: L2Detector,
216
+ scan_merger: ScanMerger,
217
+ *,
218
+ apply_policy: ApplyPolicyUseCase | None = None,
219
+ telemetry_hook: TelemetryHook | None = None,
220
+ plugin_manager: object | None = None, # PluginManager type hint circular
221
+ suppression_manager: object | None = None, # SuppressionManager type hint
222
+ enable_l2: bool = True,
223
+ fail_fast_on_critical: bool = True,
224
+ min_confidence_for_skip: float = 0.7,
225
+ enable_schema_validation: bool = False,
226
+ schema_validation_mode: str = "log_only",
227
+ ):
228
+ """Initialize scan pipeline.
229
+
230
+ Args:
231
+ pack_registry: Pack registry for loading rules
232
+ rule_executor: L1 rule execution engine
233
+ l2_detector: L2 ML detector (protocol implementation)
234
+ scan_merger: Result merger
235
+ apply_policy: Policy application use case (None = no policy enforcement)
236
+ telemetry_hook: Optional telemetry sender (legacy)
237
+ plugin_manager: Optional plugin manager for extensibility
238
+ suppression_manager: Optional suppression manager for false positives
239
+ enable_l2: Enable L2 analysis (default: True)
240
+ fail_fast_on_critical: Skip L2 if CRITICAL detected (optimization)
241
+ min_confidence_for_skip: Minimum L1 confidence to skip L2 on CRITICAL (default: 0.7)
242
+ enable_schema_validation: Enable runtime schema validation
243
+ schema_validation_mode: Validation mode (log_only, warn, enforce)
244
+ """
245
+ self.pack_registry = pack_registry
246
+ self.rule_executor = rule_executor
247
+ self.l2_detector = l2_detector
248
+ self.scan_merger = scan_merger
249
+ self.apply_policy = apply_policy or ApplyPolicyUseCase() # Default policy
250
+ self.telemetry_hook = telemetry_hook
251
+ self.plugin_manager = plugin_manager # NEW: Plugin system integration
252
+ self.suppression_manager = suppression_manager # NEW: Suppression system
253
+ self.enable_l2 = enable_l2
254
+ self.fail_fast_on_critical = fail_fast_on_critical
255
+ self.min_confidence_for_skip = min_confidence_for_skip
256
+ self.enable_schema_validation = enable_schema_validation
257
+ self.schema_validation_mode = schema_validation_mode
258
+
259
+ # Initialize schema validator if needed
260
+ self._validator = None
261
+ if self.enable_schema_validation:
262
+ try:
263
+ from raxe.infrastructure.schemas.validator import get_validator
264
+ self._validator = get_validator()
265
+ logger.info(f"Schema validation enabled (mode={schema_validation_mode})")
266
+ except Exception as e:
267
+ logger.warning(f"Failed to initialize schema validator: {e}")
268
+ self._validator = None
269
+
270
+ # Performance tracking
271
+ self._scan_count = 0
272
+ self._total_duration_ms = 0.0
273
+ self._validation_errors = 0
274
+
275
+ def scan(
276
+ self,
277
+ text: str,
278
+ *,
279
+ customer_id: str | None = None,
280
+ context: dict[str, object] | None = None,
281
+ l1_enabled: bool = True,
282
+ l2_enabled: bool = True,
283
+ mode: str = "balanced",
284
+ confidence_threshold: float = 0.5,
285
+ explain: bool = False,
286
+ ) -> ScanPipelineResult:
287
+ """Execute complete scan pipeline with layer control.
288
+
289
+ Args:
290
+ text: Text to scan for threats
291
+ customer_id: Optional customer ID for policy lookup
292
+ context: Optional context metadata
293
+ l1_enabled: Run L1 (regex) detection (default: True)
294
+ l2_enabled: Run L2 (ML) detection (default: True)
295
+ mode: Performance mode - "fast", "balanced", or "thorough" (default: "balanced")
296
+ - fast: L1 only, skip expensive rules (<3ms target)
297
+ - balanced: L1 + L2 with default rules (<10ms target)
298
+ - thorough: All layers, all rules (<100ms acceptable)
299
+ confidence_threshold: Minimum confidence to report detections (default: 0.5)
300
+ explain: Include explanation in detections (default: False)
301
+
302
+ Returns:
303
+ ScanPipelineResult with complete analysis and policy decision
304
+
305
+ Raises:
306
+ ValueError: If text is empty or invalid or mode is invalid
307
+ """
308
+ # Validate mode
309
+ if mode not in ("fast", "balanced", "thorough"):
310
+ error = ValueError(f"mode must be 'fast', 'balanced', or 'thorough', got '{mode}'")
311
+ self._track_scan_error(error, error_code="SCAN_002", is_recoverable=False)
312
+ raise error
313
+ if not text:
314
+ error = ValueError("Text cannot be empty")
315
+ self._track_scan_error(error, error_code="SCAN_003", is_recoverable=False)
316
+ raise error
317
+
318
+ # Apply mode-specific configurations
319
+ if mode == "fast":
320
+ # Fast mode: L1 only, no L2
321
+ l1_enabled = True
322
+ l2_enabled = False
323
+ elif mode == "balanced":
324
+ # Balanced mode: use provided settings or defaults
325
+ pass # Use l1_enabled and l2_enabled as provided
326
+ elif mode == "thorough":
327
+ # Thorough mode: all layers enabled
328
+ l1_enabled = True
329
+ l2_enabled = True
330
+
331
+ # Initialize telemetry orchestrator (lazy init, fires installation event if needed)
332
+ try:
333
+ orchestrator = get_orchestrator()
334
+ if orchestrator:
335
+ orchestrator.ensure_installation()
336
+ except Exception as e:
337
+ # Never let telemetry errors break scanning
338
+ logger.debug(f"Telemetry orchestrator init error (non-blocking): {e}")
339
+ orchestrator = None
340
+
341
+ start_time = time.perf_counter()
342
+ scan_timestamp = datetime.now(timezone.utc).isoformat()
343
+
344
+ # Record input length for metrics
345
+ input_length = len(text.encode('utf-8'))
346
+
347
+ # PLUGIN HOOK: on_scan_start (allow text transformation)
348
+ if self.plugin_manager:
349
+ try:
350
+ transformed_results = self.plugin_manager.execute_hook(
351
+ "on_scan_start", text, context
352
+ )
353
+ # Use first transformation if any plugins returned one
354
+ if transformed_results:
355
+ text = transformed_results[0]
356
+ logger.debug("Plugin transformed input text")
357
+ except Exception as e:
358
+ logger.error(f"Plugin on_scan_start hook failed: {e}")
359
+
360
+ # 1. Load rules from pack registry
361
+ rules = self.pack_registry.get_all_rules()
362
+
363
+ # 2. Execute L1 rule-based detection (if enabled)
364
+ # NOTE: L1 and L2 are NOT run in parallel because:
365
+ # - L1 is very fast (~1ms) while L2 dominates (~110ms)
366
+ # - Thread pool overhead (~0.5ms) cancels out parallelism benefit
367
+ # - Sequential execution is simpler and easier to debug
368
+ # - If L1 becomes slower in future, reconsider parallelization
369
+ l1_duration_ms = 0.0
370
+ if l1_enabled:
371
+ l1_start = time.perf_counter()
372
+ if METRICS_AVAILABLE and collector:
373
+ with collector.measure_scan("regex"):
374
+ l1_result = self.rule_executor.execute_rules(text, rules)
375
+ else:
376
+ l1_result = self.rule_executor.execute_rules(text, rules)
377
+ l1_duration_ms = (time.perf_counter() - l1_start) * 1000
378
+ else:
379
+ # L1 disabled - create empty result
380
+ from raxe.domain.engine.executor import ScanResult
381
+ l1_result = ScanResult(
382
+ detections=[],
383
+ scanned_at=scan_timestamp,
384
+ text_length=len(text),
385
+ rules_checked=0,
386
+ scan_duration_ms=0.0,
387
+ )
388
+
389
+ # PLUGIN HOOK: run detector plugins (merge with L1)
390
+ plugin_detection_count = 0
391
+ if self.plugin_manager:
392
+ try:
393
+ plugin_detections = self.plugin_manager.run_detectors(text, context)
394
+ if plugin_detections:
395
+ # Merge plugin detections into L1 result
396
+ from raxe.domain.engine.executor import ScanResult
397
+ l1_result = ScanResult(
398
+ detections=l1_result.detections + plugin_detections,
399
+ has_detections=l1_result.has_detections or len(plugin_detections) > 0,
400
+ highest_severity=l1_result.highest_severity, # Will be recalculated
401
+ total_rules_checked=l1_result.total_rules_checked + len(plugin_detections),
402
+ execution_time_ms=l1_result.execution_time_ms,
403
+ )
404
+ plugin_detection_count = len(plugin_detections)
405
+ logger.debug(f"Plugins detected {plugin_detection_count} additional threats")
406
+ except Exception as e:
407
+ logger.error(f"Plugin detectors failed: {e}")
408
+
409
+ # 3. Execute L2 analysis (with optimizations and layer control)
410
+ l2_result = None
411
+ l2_duration_ms = 0.0
412
+ if l2_enabled and self.enable_l2:
413
+ # Optimization: skip L2 if CRITICAL already detected with high confidence
414
+ should_skip_l2 = False
415
+ if self.fail_fast_on_critical and l1_result.highest_severity:
416
+ from raxe.domain.rules.models import Severity
417
+ if l1_result.highest_severity == Severity.CRITICAL:
418
+ # Check confidence of CRITICAL detections
419
+ max_confidence = max(
420
+ (d.confidence for d in l1_result.detections
421
+ if d.severity == Severity.CRITICAL),
422
+ default=0.0
423
+ )
424
+
425
+ if max_confidence >= self.min_confidence_for_skip:
426
+ # High confidence CRITICAL - skip L2 for performance
427
+ should_skip_l2 = True
428
+ logger.info(
429
+ "l2_scan_skipped",
430
+ reason="critical_l1_detection_high_confidence",
431
+ l1_severity="CRITICAL",
432
+ l1_max_confidence=max_confidence,
433
+ skip_threshold=self.min_confidence_for_skip,
434
+ text_hash=self._hash_text(text),
435
+ )
436
+ else:
437
+ # Low confidence CRITICAL - run L2 for validation
438
+ logger.debug(
439
+ f"Running L2 despite CRITICAL: low confidence {max_confidence:.2%} "
440
+ f"(threshold: {self.min_confidence_for_skip:.2%})"
441
+ )
442
+
443
+ if not should_skip_l2:
444
+ l2_start = time.perf_counter()
445
+ if METRICS_AVAILABLE and collector:
446
+ with collector.measure_scan("ml"):
447
+ l2_result = self.l2_detector.analyze(text, l1_result, context)
448
+ else:
449
+ l2_result = self.l2_detector.analyze(text, l1_result, context)
450
+ l2_duration_ms = (time.perf_counter() - l2_start) * 1000
451
+
452
+ # Log L2 inference results
453
+ if l2_result and l2_result.has_predictions:
454
+ # Log each L2 prediction with full context (including new bundle schema fields)
455
+ for prediction in l2_result.predictions:
456
+ # Extract bundle schema fields if available
457
+ log_data = {
458
+ "threat_type": prediction.threat_type.value,
459
+ "confidence": prediction.confidence,
460
+ "explanation": prediction.explanation or "No explanation provided",
461
+ "features_used": prediction.features_used or [],
462
+ "text_hash": self._hash_text(text),
463
+ "processing_time_ms": l2_result.processing_time_ms,
464
+ "model_version": l2_result.model_version,
465
+ }
466
+
467
+ # Add new bundle schema fields (is_attack, family, sub_family, etc.)
468
+ if "is_attack" in prediction.metadata:
469
+ log_data["is_attack"] = prediction.metadata["is_attack"]
470
+ if "family" in prediction.metadata:
471
+ log_data["family"] = prediction.metadata["family"]
472
+ if "sub_family" in prediction.metadata:
473
+ log_data["sub_family"] = prediction.metadata["sub_family"]
474
+ if "scores" in prediction.metadata:
475
+ log_data["scores"] = prediction.metadata["scores"]
476
+ if "why_it_hit" in prediction.metadata:
477
+ log_data["why_it_hit"] = prediction.metadata["why_it_hit"]
478
+ if "recommended_action" in prediction.metadata:
479
+ log_data["recommended_action"] = prediction.metadata["recommended_action"]
480
+ if "trigger_matches" in prediction.metadata:
481
+ log_data["trigger_matches"] = prediction.metadata["trigger_matches"]
482
+ if "uncertain" in prediction.metadata:
483
+ log_data["uncertain"] = prediction.metadata["uncertain"]
484
+
485
+ # Log with all available data
486
+ logger.info("l2_threat_detected", **log_data)
487
+ elif l2_result:
488
+ # Log clean L2 scan
489
+ logger.debug(
490
+ "l2_scan_clean",
491
+ processing_time_ms=l2_result.processing_time_ms,
492
+ model_version=l2_result.model_version,
493
+ confidence=l2_result.confidence,
494
+ text_hash=self._hash_text(text),
495
+ )
496
+
497
+ # 4. Apply confidence threshold filtering
498
+ if confidence_threshold > 0:
499
+ filtered_detections = [
500
+ d for d in l1_result.detections
501
+ if d.confidence >= confidence_threshold
502
+ ]
503
+ from raxe.domain.engine.executor import ScanResult
504
+ l1_result = ScanResult(
505
+ detections=filtered_detections,
506
+ scanned_at=l1_result.scanned_at,
507
+ text_length=l1_result.text_length,
508
+ rules_checked=l1_result.rules_checked,
509
+ scan_duration_ms=l1_result.scan_duration_ms,
510
+ )
511
+
512
+ # 4.5. Apply suppressions (Filter, flag, or log detections based on action)
513
+ suppressed_count = 0
514
+ flagged_count = 0
515
+ logged_count = 0
516
+ if self.suppression_manager:
517
+ from raxe.domain.suppression import SuppressionAction
518
+
519
+ processed_detections = []
520
+ for detection in l1_result.detections:
521
+ check_result = self.suppression_manager.check_suppression(detection.rule_id)
522
+
523
+ if check_result.is_suppressed:
524
+ if check_result.action == SuppressionAction.SUPPRESS:
525
+ # Fully suppress - remove from results
526
+ suppressed_count += 1
527
+ logger.debug(f"Suppressed {detection.rule_id}: {check_result.reason}")
528
+ elif check_result.action == SuppressionAction.FLAG:
529
+ # Flag for review - keep in results but mark as flagged
530
+ flagged_count += 1
531
+ logger.debug(f"Flagged {detection.rule_id}: {check_result.reason}")
532
+ # Use Detection.with_flag() to create flagged copy
533
+ flagged_detection = detection.with_flag(check_result.reason)
534
+ processed_detections.append(flagged_detection)
535
+ elif check_result.action == SuppressionAction.LOG:
536
+ # Log only - keep in results (for metrics/logging)
537
+ logged_count += 1
538
+ logger.debug(f"Logged {detection.rule_id}: {check_result.reason}")
539
+ processed_detections.append(detection)
540
+
541
+ # Log suppression application to audit log
542
+ self.suppression_manager.log_suppression_applied(
543
+ rule_id=detection.rule_id,
544
+ reason=check_result.reason,
545
+ action=check_result.action,
546
+ )
547
+ else:
548
+ processed_detections.append(detection)
549
+
550
+ # Update l1_result with processed detections
551
+ from raxe.domain.engine.executor import ScanResult
552
+ l1_result = ScanResult(
553
+ detections=processed_detections,
554
+ scanned_at=l1_result.scanned_at,
555
+ text_length=l1_result.text_length,
556
+ rules_checked=l1_result.rules_checked,
557
+ scan_duration_ms=l1_result.scan_duration_ms,
558
+ )
559
+
560
+ # 5. Merge L1+L2 results
561
+ metadata: dict[str, object] = {
562
+ "customer_id": customer_id,
563
+ "scan_timestamp": scan_timestamp,
564
+ "rules_loaded": len(rules),
565
+ "l2_skipped": self.enable_l2 and l2_result is None,
566
+ "l1_duration_ms": l1_duration_ms,
567
+ "l2_duration_ms": l2_duration_ms,
568
+ "input_length": input_length,
569
+ "mode": mode,
570
+ "l1_enabled": l1_enabled,
571
+ "l2_enabled": l2_enabled,
572
+ "confidence_threshold": confidence_threshold,
573
+ "explain": explain,
574
+ "suppressed_count": suppressed_count, # Track fully suppressed
575
+ "flagged_count": flagged_count, # Track flagged for review
576
+ "logged_count": logged_count, # Track logged for metrics
577
+ }
578
+ if context:
579
+ metadata["context"] = context
580
+
581
+ combined_result = self.scan_merger.merge(
582
+ l1_result=l1_result,
583
+ l2_result=l2_result,
584
+ metadata=metadata,
585
+ )
586
+
587
+ # 6. Evaluate policy to determine action
588
+ # CRITICAL: Policy must consider BOTH L1 and L2 detections
589
+ # We evaluate using the combined result to include L2 predictions
590
+ policy_decision, should_block = self._evaluate_policy(
591
+ l1_result=l1_result,
592
+ l2_result=l2_result,
593
+ combined_severity=combined_result.combined_severity
594
+ )
595
+
596
+ # 7. Calculate text hash (privacy-preserving)
597
+ text_hash = self._hash_text(text)
598
+
599
+ # Calculate total duration
600
+ duration_ms = (time.perf_counter() - start_time) * 1000
601
+
602
+ # Calculate layer statistics
603
+ breakdown = combined_result.layer_breakdown()
604
+ l1_count = breakdown.get("L1", 0)
605
+ l2_count = breakdown.get("L2", 0)
606
+ plugin_count = breakdown.get("PLUGIN", 0)
607
+
608
+ # Create final result with layer attribution
609
+ result = ScanPipelineResult(
610
+ scan_result=combined_result,
611
+ policy_decision=policy_decision,
612
+ should_block=should_block,
613
+ duration_ms=duration_ms,
614
+ text_hash=text_hash,
615
+ metadata=metadata,
616
+ l1_detections=l1_count,
617
+ l2_detections=l2_count,
618
+ plugin_detections=plugin_count,
619
+ l1_duration_ms=l1_duration_ms,
620
+ l2_duration_ms=l2_duration_ms,
621
+ )
622
+
623
+ # Record Prometheus metrics
624
+ if METRICS_AVAILABLE and collector:
625
+ try:
626
+ # Record scan metrics
627
+ severity = result.severity or "none"
628
+ collector.record_scan_simple(
629
+ severity=severity,
630
+ blocked=result.should_block,
631
+ detection_count=result.total_detections,
632
+ input_length=input_length,
633
+ )
634
+
635
+ # Record individual detections
636
+ for detection in l1_result.detections:
637
+ if METRICS_AVAILABLE:
638
+ from raxe.monitoring.metrics import detections_total, rule_matches
639
+ detections_total.labels(
640
+ rule_id=detection.rule_id,
641
+ severity=detection.severity.value,
642
+ category=getattr(detection, "category", "unknown"),
643
+ ).inc()
644
+ rule_matches.labels(
645
+ rule_id=detection.rule_id,
646
+ severity=detection.severity.value,
647
+ ).inc()
648
+ except Exception as e:
649
+ # Never fail scan due to metrics errors
650
+ logger.debug(f"Metrics recording error (non-blocking): {e}")
651
+
652
+ # 7. Record telemetry (privacy-preserving) - legacy hook only
653
+ # NOTE: Primary telemetry is handled by SDK client via TelemetryOrchestrator
654
+ if self.telemetry_hook:
655
+ self._send_telemetry(result, customer_id)
656
+
657
+ # NOTE: Scan telemetry tracking moved to SDK client (sdk/client.py:_track_scan)
658
+ # to avoid duplicate tracking. The SDK is the canonical location for telemetry
659
+ # because it:
660
+ # 1. Generates event_id for portal-CLI correlation
661
+ # 2. Is the "outer layer" with full context
662
+ # 3. Can be called by multiple entry points (CLI, SDK, wrappers)
663
+ #
664
+ # The orchestrator initialization above (lines 335-343) is kept for:
665
+ # - ensure_installation() on first scan
666
+ # - Error tracking via _track_scan_error()
667
+
668
+ # PLUGIN HOOK: on_scan_complete
669
+ if self.plugin_manager:
670
+ try:
671
+ self.plugin_manager.execute_hook("on_scan_complete", result)
672
+ except Exception as e:
673
+ logger.error(f"Plugin on_scan_complete hook failed: {e}")
674
+
675
+ # PLUGIN HOOK: on_threat_detected (if threats found)
676
+ if self.plugin_manager and result.has_threats:
677
+ try:
678
+ self.plugin_manager.execute_hook("on_threat_detected", result)
679
+ except Exception as e:
680
+ logger.error(f"Plugin on_threat_detected hook failed: {e}")
681
+
682
+ # PLUGIN HOOK: run action plugins
683
+ if self.plugin_manager:
684
+ try:
685
+ self.plugin_manager.run_actions(result)
686
+ except Exception as e:
687
+ logger.error(f"Plugin actions failed: {e}")
688
+
689
+ # Track performance
690
+ self._scan_count += 1
691
+ self._total_duration_ms += duration_ms
692
+
693
+ return result
694
+
695
+ def scan_batch(
696
+ self,
697
+ texts: list[str],
698
+ *,
699
+ customer_id: str | None = None,
700
+ context: dict[str, object] | None = None,
701
+ ) -> list[ScanPipelineResult]:
702
+ """Scan multiple texts.
703
+
704
+ Args:
705
+ texts: List of texts to scan
706
+ customer_id: Optional customer ID
707
+ context: Optional context metadata
708
+
709
+ Returns:
710
+ List of scan results (one per text)
711
+ """
712
+ results = []
713
+ for text in texts:
714
+ result = self.scan(
715
+ text,
716
+ customer_id=customer_id,
717
+ context=context,
718
+ )
719
+ results.append(result)
720
+ return results
721
+
722
+ def _evaluate_policy(
723
+ self,
724
+ l1_result: object,
725
+ l2_result: L2Result | None,
726
+ combined_severity: object | None,
727
+ ) -> tuple[BlockAction, bool]:
728
+ """Evaluate policy using ApplyPolicyUseCase for both L1 and L2 detections.
729
+
730
+ Applies advanced policies to all detections:
731
+ 1. L1 detections (real rules with versioned IDs)
732
+ 2. L2 predictions (mapped to virtual rules)
733
+
734
+ Args:
735
+ l1_result: L1 scan result with rule detections
736
+ l2_result: L2 scan result with ML predictions (optional)
737
+ combined_severity: Maximum severity across L1 and L2
738
+
739
+ Returns:
740
+ Tuple of (policy_decision, should_block)
741
+ """
742
+ from raxe.application.apply_policy import PolicySource
743
+ from raxe.domain.policies.models import PolicyAction as PA
744
+
745
+ # Collect all detections (L1 + mapped L2)
746
+ all_detections = []
747
+
748
+ # Add L1 detections
749
+ if l1_result.has_detections:
750
+ all_detections.extend(l1_result.detections)
751
+
752
+ # Map L2 predictions to virtual detections
753
+ if l2_result and l2_result.has_predictions:
754
+ l2_detections = self._map_l2_to_virtual_rules(l2_result)
755
+ all_detections.extend(l2_detections)
756
+
757
+ # If no detections, return ALLOW (passive monitoring)
758
+ if not all_detections:
759
+ return BlockAction.ALLOW, False
760
+
761
+ # Apply policies to all detections
762
+ policy_decisions = {}
763
+ for detection in all_detections:
764
+ decision = self.apply_policy.apply_to_detection(
765
+ detection,
766
+ policy_source=PolicySource.LOCAL_FILE,
767
+ )
768
+ policy_decisions[detection.versioned_rule_id] = decision
769
+
770
+ # Determine highest action across all decisions
771
+ highest_action = PA.ALLOW
772
+ for decision in policy_decisions.values():
773
+ if decision.action == PA.BLOCK:
774
+ highest_action = PA.BLOCK
775
+ break # BLOCK is highest priority
776
+ elif decision.action == PA.FLAG and highest_action != PA.BLOCK:
777
+ highest_action = PA.FLAG
778
+ elif decision.action == PA.LOG and highest_action == PA.ALLOW:
779
+ highest_action = PA.LOG
780
+
781
+ # Map PolicyAction to BlockAction for backward compatibility
782
+ if highest_action == PA.BLOCK:
783
+ policy_decision = BlockAction.BLOCK
784
+ should_block = True
785
+ elif highest_action == PA.FLAG:
786
+ policy_decision = BlockAction.WARN
787
+ should_block = False
788
+ elif highest_action == PA.LOG:
789
+ policy_decision = BlockAction.WARN
790
+ should_block = False
791
+ else: # ALLOW
792
+ policy_decision = BlockAction.ALLOW
793
+ should_block = False
794
+
795
+ return policy_decision, should_block
796
+
797
+ def _map_l2_to_virtual_rules(self, l2_result: L2Result) -> list:
798
+ """Map L2 ML predictions to virtual rule detections.
799
+
800
+ Creates Detection objects for L2 predictions with virtual rule IDs
801
+ like "l2-jailbreak" or "l2-prompt-injection".
802
+
803
+ Args:
804
+ l2_result: L2 scan result with ML predictions
805
+
806
+ Returns:
807
+ List of Detection objects representing L2 predictions
808
+ """
809
+ from datetime import datetime, timezone
810
+ from raxe.domain.engine.executor import Detection
811
+ from raxe.domain.engine.matcher import Match
812
+
813
+ detections = []
814
+
815
+ for prediction in l2_result.predictions:
816
+ # Create virtual rule ID based on threat type
817
+ rule_id = f"l2-{prediction.threat_type.value.lower().replace('_', '-')}"
818
+
819
+ # Map L2 confidence to severity
820
+ severity = self._map_l2_severity(prediction.confidence)
821
+
822
+ # Create a single Match object for L2 detection (no actual pattern match)
823
+ match = Match(
824
+ pattern_index=0,
825
+ start=0,
826
+ end=0,
827
+ matched_text="[L2 ML Detection]", # Privacy: don't expose actual text
828
+ groups=(),
829
+ context_before="",
830
+ context_after="",
831
+ )
832
+
833
+ # Create virtual detection
834
+ detection = Detection(
835
+ rule_id=rule_id,
836
+ rule_version="0.0.1",
837
+ severity=severity,
838
+ confidence=prediction.confidence,
839
+ matches=[match],
840
+ detected_at=datetime.now(timezone.utc).isoformat(),
841
+ detection_layer="L2",
842
+ category=prediction.threat_type.value,
843
+ message=f"L2 ML detection: {prediction.threat_type.value}",
844
+ explanation=prediction.explanation if prediction.explanation else f"ML model detected {prediction.threat_type.value}",
845
+ )
846
+ detections.append(detection)
847
+
848
+ return detections
849
+
850
+ def _map_l2_severity(self, confidence: float) -> object:
851
+ """Map L2 confidence score to severity level.
852
+
853
+ Uses conservative thresholds - L2 must be confident to trigger high severity.
854
+
855
+ Args:
856
+ confidence: L2 confidence score (0.0 to 1.0)
857
+
858
+ Returns:
859
+ Severity level
860
+ """
861
+ from raxe.domain.rules.models import Severity
862
+
863
+ if confidence >= 0.95:
864
+ return Severity.CRITICAL
865
+ elif confidence >= 0.85:
866
+ return Severity.HIGH
867
+ elif confidence >= 0.70:
868
+ return Severity.MEDIUM
869
+ elif confidence >= 0.50:
870
+ return Severity.LOW
871
+ else:
872
+ return Severity.INFO
873
+
874
+ def _hash_text(self, text: str) -> str:
875
+ """Create privacy-preserving hash of text.
876
+
877
+ Uses SHA256 to create non-reversible hash.
878
+ This allows telemetry without exposing PII.
879
+
880
+ Args:
881
+ text: Text to hash
882
+
883
+ Returns:
884
+ Hex-encoded SHA256 hash
885
+ """
886
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
887
+
888
+ def _validate_telemetry_event(self, payload: dict[str, object]) -> bool:
889
+ """Validate telemetry event against schema.
890
+
891
+ Args:
892
+ payload: Telemetry event payload
893
+
894
+ Returns:
895
+ True if valid or validation disabled, False if invalid
896
+ """
897
+ if not self._validator:
898
+ return True # Validation disabled or failed to init
899
+
900
+ try:
901
+ is_valid, errors = self._validator.validate_scan_event(payload)
902
+
903
+ if not is_valid:
904
+ self._validation_errors += 1
905
+
906
+ if self.schema_validation_mode == "log_only":
907
+ # Just log errors, don't block
908
+ logger.debug(f"Telemetry validation failed: {errors}")
909
+ return True # Allow send anyway
910
+
911
+ elif self.schema_validation_mode == "warn":
912
+ # Log warning but allow send
913
+ logger.warning(
914
+ f"Telemetry validation failed: {errors}. "
915
+ f"Sending anyway (mode=warn)"
916
+ )
917
+ return True
918
+
919
+ elif self.schema_validation_mode == "enforce":
920
+ # Block invalid data
921
+ logger.error(
922
+ f"Telemetry validation failed: {errors}. "
923
+ f"Blocked (mode=enforce)"
924
+ )
925
+ return False
926
+
927
+ return True
928
+
929
+ except Exception as e:
930
+ logger.warning(f"Schema validation error: {e}")
931
+ return True # Don't block on validation errors
932
+
933
+ def _send_telemetry(
934
+ self,
935
+ result: ScanPipelineResult,
936
+ customer_id: str | None,
937
+ ) -> None:
938
+ """Send privacy-preserving telemetry.
939
+
940
+ Sends only:
941
+ - Text hash (NOT the actual text)
942
+ - Detection counts
943
+ - Severity levels
944
+ - Performance metrics
945
+ - Customer ID (for analytics)
946
+
947
+ NEVER sends:
948
+ - Actual text content
949
+ - Pattern matches
950
+ - Any PII
951
+
952
+ Args:
953
+ result: Scan pipeline result
954
+ customer_id: Customer ID
955
+ """
956
+ # Build telemetry payload (privacy-first)
957
+ payload: dict[str, object] = {
958
+ "event_name": "scan_performed",
959
+ "prompt_hash": result.text_hash,
960
+ "timestamp": result.metadata.get("scan_timestamp"),
961
+ "max_severity": result.severity or "none",
962
+ "detection_count": result.total_detections,
963
+ "l1_detection_count": result.scan_result.l1_detection_count,
964
+ "l2_prediction_count": result.scan_result.l2_prediction_count,
965
+ "scan_duration_ms": result.duration_ms,
966
+ "policy_action": result.policy_decision.value,
967
+ "blocked": result.should_block,
968
+ }
969
+
970
+ # Add optional fields
971
+ if customer_id:
972
+ payload["customer_id"] = customer_id
973
+
974
+ # Validate if enabled
975
+ if self.enable_schema_validation:
976
+ if not self._validate_telemetry_event(payload):
977
+ # Validation failed in enforce mode - don't send
978
+ logger.warning("Telemetry blocked due to schema validation failure")
979
+ return
980
+
981
+ # Send via telemetry hook
982
+ try:
983
+ self.telemetry_hook.send(payload)
984
+ except Exception:
985
+ # Never fail scan due to telemetry errors
986
+ # Just log and continue (logging happens in hook)
987
+ pass
988
+
989
+ @property
990
+ def average_scan_time_ms(self) -> float:
991
+ """Average scan time across all scans.
992
+
993
+ Returns:
994
+ Average duration in milliseconds
995
+ """
996
+ if self._scan_count == 0:
997
+ return 0.0
998
+ return self._total_duration_ms / self._scan_count
999
+
1000
+ @property
1001
+ def scan_count(self) -> int:
1002
+ """Total number of scans performed."""
1003
+ return self._scan_count
1004
+
1005
+ def get_stats(self) -> dict[str, object]:
1006
+ """Get pipeline statistics.
1007
+
1008
+ Returns:
1009
+ Dictionary with performance metrics
1010
+ """
1011
+ return {
1012
+ "scan_count": self._scan_count,
1013
+ "average_scan_time_ms": self.average_scan_time_ms,
1014
+ "total_duration_ms": self._total_duration_ms,
1015
+ "enable_l2": self.enable_l2,
1016
+ "fail_fast_on_critical": self.fail_fast_on_critical,
1017
+ }
1018
+
1019
+ def _track_scan_error(
1020
+ self,
1021
+ error: Exception,
1022
+ error_code: str = "SCAN_001",
1023
+ is_recoverable: bool = False,
1024
+ ) -> None:
1025
+ """Track a scan error via telemetry orchestrator.
1026
+
1027
+ This is a helper method that safely tracks errors without letting
1028
+ telemetry failures break the main scan flow.
1029
+
1030
+ Args:
1031
+ error: The exception that occurred
1032
+ error_code: Error code for categorization
1033
+ is_recoverable: Whether the error is recoverable
1034
+ """
1035
+ try:
1036
+ orchestrator = get_orchestrator()
1037
+ if orchestrator and orchestrator.is_enabled():
1038
+ # Determine error type based on exception type
1039
+ error_type = "internal_error"
1040
+ if isinstance(error, ValueError):
1041
+ error_type = "validation_error"
1042
+ elif isinstance(error, TimeoutError):
1043
+ error_type = "timeout_error"
1044
+ elif isinstance(error, PermissionError):
1045
+ error_type = "permission_error"
1046
+ elif isinstance(error, OSError):
1047
+ error_type = "network_error"
1048
+
1049
+ orchestrator.track_error(
1050
+ error_type=error_type, # type: ignore[arg-type]
1051
+ error_code=error_code,
1052
+ component="engine",
1053
+ error_message=str(error),
1054
+ is_recoverable=is_recoverable,
1055
+ operation="scan",
1056
+ )
1057
+ except Exception as e:
1058
+ # Never let telemetry errors break scanning
1059
+ logger.debug(f"Error tracking failed (non-blocking): {e}")