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