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,947 @@
|
|
|
1
|
+
"""Secure credential storage for RAXE API keys.
|
|
2
|
+
|
|
3
|
+
Provides secure storage for API credentials with:
|
|
4
|
+
- chmod 600 protection (owner read/write only)
|
|
5
|
+
- Temporary key generation for zero-friction onboarding
|
|
6
|
+
- Key upgrade support from temporary to permanent
|
|
7
|
+
- Expiry tracking for temporary keys (14 days)
|
|
8
|
+
|
|
9
|
+
File format (credentials.json):
|
|
10
|
+
{
|
|
11
|
+
"api_key": "raxe_temp_...",
|
|
12
|
+
"key_type": "temporary",
|
|
13
|
+
"installation_id": "inst_...",
|
|
14
|
+
"created_at": "2025-01-26T00:00:00Z",
|
|
15
|
+
"expires_at": "2025-02-09T00:00:00Z",
|
|
16
|
+
"first_seen_at": null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Security:
|
|
20
|
+
- File created with chmod 600 (Unix)
|
|
21
|
+
- Warns if permissions too permissive on load
|
|
22
|
+
- API key values never logged
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
import logging
|
|
30
|
+
import os
|
|
31
|
+
import platform
|
|
32
|
+
import re
|
|
33
|
+
import secrets
|
|
34
|
+
import stat
|
|
35
|
+
from dataclasses import asdict, dataclass
|
|
36
|
+
from datetime import datetime, timezone
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Literal
|
|
39
|
+
from uuid import uuid4
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
# Key format validation patterns (from spec section 2.2)
|
|
44
|
+
# Allows alphanumeric, hyphens, and underscores in the random suffix
|
|
45
|
+
# (underscores are valid in base64url encoding used by backend)
|
|
46
|
+
KEY_PATTERNS: dict[str, re.Pattern[str]] = {
|
|
47
|
+
"temporary": re.compile(r"^raxe_temp_[a-zA-Z0-9_\-]{32}$"),
|
|
48
|
+
"live": re.compile(r"^raxe_live_[a-zA-Z0-9_\-]{32}$"),
|
|
49
|
+
"test": re.compile(r"^raxe_test_[a-zA-Z0-9_\-]{32}$"),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Combined pattern for any valid key
|
|
53
|
+
ANY_KEY_PATTERN = re.compile(r"^raxe_(live|test|temp)_[a-zA-Z0-9_\-]{32}$")
|
|
54
|
+
|
|
55
|
+
# Temporary key expiry (14 days from creation)
|
|
56
|
+
TEMP_KEY_EXPIRY_DAYS = 14
|
|
57
|
+
|
|
58
|
+
# Default credential file location
|
|
59
|
+
DEFAULT_CREDENTIAL_DIR = Path.home() / ".raxe"
|
|
60
|
+
DEFAULT_CREDENTIAL_FILE = DEFAULT_CREDENTIAL_DIR / "credentials.json"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def compute_key_id(api_key: str) -> str:
|
|
64
|
+
"""Compute BigQuery-compatible key ID from API key.
|
|
65
|
+
|
|
66
|
+
The key ID is a truncated SHA256 hash of the API key, prefixed with "key_".
|
|
67
|
+
This allows the server to link historical events across key upgrades
|
|
68
|
+
without storing the actual API key values.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
api_key: The full API key (e.g., "raxe_temp_b32a43d2...")
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The key ID (e.g., "key_23cc2f9f21f9")
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
>>> compute_key_id("raxe_temp_abc123def456789012345678901234")
|
|
78
|
+
'key_...' # 12 hex chars after "key_" prefix
|
|
79
|
+
"""
|
|
80
|
+
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
|
|
81
|
+
return f"key_{key_hash[:12]}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class CredentialError(Exception):
|
|
85
|
+
"""Error related to credential storage or validation."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _get_default_console_keys_url() -> str:
|
|
89
|
+
"""Get default console keys URL from centralized endpoints."""
|
|
90
|
+
from raxe.infrastructure.config.endpoints import get_console_url
|
|
91
|
+
return f"{get_console_url()}/keys"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CredentialExpiredError(CredentialError):
|
|
95
|
+
"""API key has expired.
|
|
96
|
+
|
|
97
|
+
This exception is raised when a temporary API key has exceeded its
|
|
98
|
+
14-day expiry period. Users should obtain a permanent key from the
|
|
99
|
+
RAXE console.
|
|
100
|
+
|
|
101
|
+
Attributes:
|
|
102
|
+
message: Human-readable message explaining the expiry.
|
|
103
|
+
console_url: URL where users can get a new permanent key.
|
|
104
|
+
days_expired: Number of days past the expiry date.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def get_default_console_url() -> str:
|
|
109
|
+
"""Get default console URL from centralized endpoints."""
|
|
110
|
+
return _get_default_console_keys_url()
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
message: str,
|
|
115
|
+
*,
|
|
116
|
+
console_url: str | None = None,
|
|
117
|
+
days_expired: int = 0,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Initialize CredentialExpiredError.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
message: Human-readable error message.
|
|
123
|
+
console_url: URL to get a new key (uses centralized endpoint if None).
|
|
124
|
+
days_expired: Number of days past expiry (0 if just expired).
|
|
125
|
+
"""
|
|
126
|
+
super().__init__(message)
|
|
127
|
+
self.console_url = console_url or _get_default_console_keys_url()
|
|
128
|
+
self.days_expired = days_expired
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class InvalidKeyFormatError(CredentialError):
|
|
132
|
+
"""API key format is invalid."""
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class KeyUpgradeInfo:
|
|
137
|
+
"""Information about a key upgrade for telemetry event creation.
|
|
138
|
+
|
|
139
|
+
Contains both the old and new key information needed to create
|
|
140
|
+
a key_upgrade telemetry event with proper key IDs for server-side
|
|
141
|
+
event linking.
|
|
142
|
+
|
|
143
|
+
Attributes:
|
|
144
|
+
previous_key_id: BigQuery-compatible ID for previous key (e.g., "key_23cc2f9f21f9").
|
|
145
|
+
new_key_id: BigQuery-compatible ID for new key (e.g., "key_7ce219b525f1").
|
|
146
|
+
previous_key_type: Previous key type ("temporary", "live", or "test").
|
|
147
|
+
new_key_type: New key type ("live" or "test").
|
|
148
|
+
days_on_previous: Number of days the previous key was in use (if known).
|
|
149
|
+
new_credentials: The newly saved Credentials object.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
previous_key_id: str | None
|
|
153
|
+
new_key_id: str
|
|
154
|
+
previous_key_type: str | None
|
|
155
|
+
new_key_type: str
|
|
156
|
+
days_on_previous: int | None
|
|
157
|
+
new_credentials: "Credentials"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class Credentials:
|
|
162
|
+
"""Credential data model.
|
|
163
|
+
|
|
164
|
+
Represents stored API credentials with type and expiry information.
|
|
165
|
+
|
|
166
|
+
Attributes:
|
|
167
|
+
api_key: The RAXE API key (raxe_temp_*, raxe_live_*, or raxe_test_*)
|
|
168
|
+
key_type: Type of key - temporary, live, or test
|
|
169
|
+
installation_id: Unique installation identifier (inst_{hex16})
|
|
170
|
+
created_at: ISO 8601 timestamp when credentials were created
|
|
171
|
+
expires_at: ISO 8601 timestamp when key expires (temporary keys only)
|
|
172
|
+
first_seen_at: Server-provided timestamp of first use (optional)
|
|
173
|
+
can_disable_telemetry: Whether the key tier allows disabling telemetry
|
|
174
|
+
offline_mode: Whether offline mode is permitted for this tier
|
|
175
|
+
tier: Key tier (temporary, community, pro, enterprise)
|
|
176
|
+
last_health_check: ISO 8601 timestamp of last server health check
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
api_key: str
|
|
180
|
+
key_type: Literal["temporary", "live", "test"]
|
|
181
|
+
installation_id: str
|
|
182
|
+
created_at: str # ISO 8601
|
|
183
|
+
expires_at: str | None # For temp keys (14 days from creation)
|
|
184
|
+
first_seen_at: str | None # Server-provided
|
|
185
|
+
|
|
186
|
+
# Cached server permissions (from /v1/health response)
|
|
187
|
+
can_disable_telemetry: bool = False
|
|
188
|
+
offline_mode: bool = False
|
|
189
|
+
tier: str = "temporary"
|
|
190
|
+
last_health_check: str | None = None # ISO 8601 timestamp
|
|
191
|
+
|
|
192
|
+
def is_expired(self) -> bool:
|
|
193
|
+
"""Check if the credential has expired.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
True if the key has expired, False otherwise.
|
|
197
|
+
Permanent keys (live/test) never expire.
|
|
198
|
+
"""
|
|
199
|
+
if self.expires_at is None:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
expiry = datetime.fromisoformat(self.expires_at.replace("Z", "+00:00"))
|
|
204
|
+
now = datetime.now(timezone.utc)
|
|
205
|
+
return now >= expiry
|
|
206
|
+
except (ValueError, TypeError):
|
|
207
|
+
# If we can't parse expiry, assume not expired
|
|
208
|
+
logger.warning("Could not parse expires_at, assuming not expired")
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
def is_temporary(self) -> bool:
|
|
212
|
+
"""Check if this is a temporary key.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
True if key_type is "temporary", False otherwise.
|
|
216
|
+
"""
|
|
217
|
+
return self.key_type == "temporary"
|
|
218
|
+
|
|
219
|
+
def days_until_expiry(self) -> int | None:
|
|
220
|
+
"""Calculate days remaining until expiry.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Number of days until expiry (0 if expired, None if no expiry).
|
|
224
|
+
"""
|
|
225
|
+
if self.expires_at is None:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
expiry = datetime.fromisoformat(self.expires_at.replace("Z", "+00:00"))
|
|
230
|
+
now = datetime.now(timezone.utc)
|
|
231
|
+
delta = expiry - now
|
|
232
|
+
|
|
233
|
+
if delta.total_seconds() <= 0:
|
|
234
|
+
return 0
|
|
235
|
+
|
|
236
|
+
return delta.days
|
|
237
|
+
except (ValueError, TypeError):
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
def days_since_expiry(self) -> int | None:
|
|
241
|
+
"""Calculate days since expiry (for expired keys).
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Number of days past expiry (0 if not yet expired, None if no expiry).
|
|
245
|
+
"""
|
|
246
|
+
if self.expires_at is None:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
expiry = datetime.fromisoformat(self.expires_at.replace("Z", "+00:00"))
|
|
251
|
+
now = datetime.now(timezone.utc)
|
|
252
|
+
delta = now - expiry
|
|
253
|
+
|
|
254
|
+
if delta.total_seconds() <= 0:
|
|
255
|
+
return 0
|
|
256
|
+
|
|
257
|
+
return delta.days
|
|
258
|
+
except (ValueError, TypeError):
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
def is_health_check_stale(self, max_age_hours: int = 24) -> bool:
|
|
262
|
+
"""Check if the cached health check is stale.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
max_age_hours: Maximum age in hours before health check is stale.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
True if health check is stale or missing, False otherwise.
|
|
269
|
+
"""
|
|
270
|
+
if self.last_health_check is None:
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
from datetime import timedelta
|
|
275
|
+
|
|
276
|
+
last_check = datetime.fromisoformat(
|
|
277
|
+
self.last_health_check.replace("Z", "+00:00")
|
|
278
|
+
)
|
|
279
|
+
now = datetime.now(timezone.utc)
|
|
280
|
+
age = now - last_check
|
|
281
|
+
|
|
282
|
+
return age > timedelta(hours=max_age_hours)
|
|
283
|
+
except (ValueError, TypeError):
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
def to_dict(self) -> dict[str, str | None]:
|
|
287
|
+
"""Convert credentials to dictionary.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Dictionary representation suitable for JSON serialization.
|
|
291
|
+
"""
|
|
292
|
+
return asdict(self)
|
|
293
|
+
|
|
294
|
+
@classmethod
|
|
295
|
+
def from_dict(cls, data: dict[str, str | None]) -> Credentials:
|
|
296
|
+
"""Create Credentials from dictionary.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
data: Dictionary with credential fields.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Credentials instance.
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
ValueError: If required fields are missing.
|
|
306
|
+
"""
|
|
307
|
+
required_fields = ["api_key", "key_type", "installation_id", "created_at"]
|
|
308
|
+
for field in required_fields:
|
|
309
|
+
if field not in data:
|
|
310
|
+
raise ValueError(f"Missing required field: {field}")
|
|
311
|
+
|
|
312
|
+
return cls(
|
|
313
|
+
api_key=data["api_key"], # type: ignore[arg-type]
|
|
314
|
+
key_type=data["key_type"], # type: ignore[arg-type]
|
|
315
|
+
installation_id=data["installation_id"], # type: ignore[arg-type]
|
|
316
|
+
created_at=data["created_at"], # type: ignore[arg-type]
|
|
317
|
+
expires_at=data.get("expires_at"),
|
|
318
|
+
first_seen_at=data.get("first_seen_at"),
|
|
319
|
+
# Server permission fields (with defaults for backward compatibility)
|
|
320
|
+
can_disable_telemetry=data.get("can_disable_telemetry", False), # type: ignore[arg-type]
|
|
321
|
+
offline_mode=data.get("offline_mode", False), # type: ignore[arg-type]
|
|
322
|
+
tier=data.get("tier", "temporary"), # type: ignore[arg-type]
|
|
323
|
+
last_health_check=data.get("last_health_check"),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def validate_key_format(api_key: str) -> Literal["temporary", "live", "test"]:
|
|
328
|
+
"""Validate API key format and return type.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
api_key: The API key to validate.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
The key type (temporary, live, or test).
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
InvalidKeyFormatError: If the key format is invalid.
|
|
338
|
+
"""
|
|
339
|
+
if not api_key:
|
|
340
|
+
raise InvalidKeyFormatError("API key cannot be empty")
|
|
341
|
+
|
|
342
|
+
if not ANY_KEY_PATTERN.match(api_key):
|
|
343
|
+
raise InvalidKeyFormatError(
|
|
344
|
+
"Invalid API key format. Expected: raxe_{live|test|temp}_{32 chars}"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# Determine key type
|
|
348
|
+
if api_key.startswith("raxe_temp_"):
|
|
349
|
+
return "temporary"
|
|
350
|
+
elif api_key.startswith("raxe_live_"):
|
|
351
|
+
return "live"
|
|
352
|
+
elif api_key.startswith("raxe_test_"):
|
|
353
|
+
return "test"
|
|
354
|
+
else:
|
|
355
|
+
raise InvalidKeyFormatError("Unknown key type prefix")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _generate_installation_id() -> str:
|
|
359
|
+
"""Generate a unique installation identifier.
|
|
360
|
+
|
|
361
|
+
Format: inst_{uuid4_hex[:16]}
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
Installation ID string.
|
|
365
|
+
"""
|
|
366
|
+
hex_id = uuid4().hex[:16]
|
|
367
|
+
return f"inst_{hex_id}"
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _generate_temp_key() -> str:
|
|
371
|
+
"""Generate a temporary API key.
|
|
372
|
+
|
|
373
|
+
Format: raxe_temp_{secrets.token_hex(16)}
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Temporary API key string (32 hex chars after prefix).
|
|
377
|
+
"""
|
|
378
|
+
# token_hex(16) generates 32 hex characters
|
|
379
|
+
return f"raxe_temp_{secrets.token_hex(16)}"
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _get_utc_now_iso() -> str:
|
|
383
|
+
"""Get current UTC time in ISO 8601 format.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
ISO 8601 formatted timestamp with Z suffix.
|
|
387
|
+
"""
|
|
388
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _is_windows() -> bool:
|
|
392
|
+
"""Check if running on Windows.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
True if running on Windows, False otherwise.
|
|
396
|
+
"""
|
|
397
|
+
return platform.system() == "Windows"
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _set_secure_permissions(path: Path) -> None:
|
|
401
|
+
"""Set secure file permissions (chmod 600).
|
|
402
|
+
|
|
403
|
+
On Unix-like systems, sets file to owner read/write only.
|
|
404
|
+
On Windows, this is a no-op (Windows uses ACLs).
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
path: Path to the file.
|
|
408
|
+
"""
|
|
409
|
+
if _is_windows():
|
|
410
|
+
# Windows uses ACLs, skip chmod
|
|
411
|
+
logger.debug("Skipping chmod on Windows")
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) # 0o600
|
|
416
|
+
logger.debug("Set file permissions to 600")
|
|
417
|
+
except OSError as e:
|
|
418
|
+
logger.warning(f"Failed to set file permissions: {e}")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _check_permissions(path: Path) -> bool:
|
|
422
|
+
"""Check if file permissions are secure.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
path: Path to the file to check.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
True if permissions are secure (600 or better), False otherwise.
|
|
429
|
+
"""
|
|
430
|
+
if _is_windows():
|
|
431
|
+
# Windows uses ACLs, assume OK
|
|
432
|
+
return True
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
mode = path.stat().st_mode
|
|
436
|
+
# Check if group or world readable/writable
|
|
437
|
+
insecure_bits = (
|
|
438
|
+
stat.S_IRGRP # Group read
|
|
439
|
+
| stat.S_IWGRP # Group write
|
|
440
|
+
| stat.S_IROTH # World read
|
|
441
|
+
| stat.S_IWOTH # World write
|
|
442
|
+
)
|
|
443
|
+
return (mode & insecure_bits) == 0
|
|
444
|
+
except OSError:
|
|
445
|
+
return True # Can't check, assume OK
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class CredentialStore:
|
|
449
|
+
"""Secure credential storage with chmod 600 protection.
|
|
450
|
+
|
|
451
|
+
Manages API credentials for RAXE telemetry with secure storage
|
|
452
|
+
and support for temporary key generation.
|
|
453
|
+
|
|
454
|
+
Location: ~/.raxe/credentials.json (default)
|
|
455
|
+
|
|
456
|
+
Example:
|
|
457
|
+
>>> store = CredentialStore()
|
|
458
|
+
>>> creds = store.get_or_create()
|
|
459
|
+
>>> print(f"Using key type: {creds.key_type}")
|
|
460
|
+
Using key type: temporary
|
|
461
|
+
|
|
462
|
+
>>> # Upgrade to permanent key
|
|
463
|
+
>>> creds = store.upgrade_key("raxe_live_abc123...", "live")
|
|
464
|
+
>>> print(f"Upgraded to: {creds.key_type}")
|
|
465
|
+
Upgraded to: live
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
def __init__(self, credential_path: Path | None = None) -> None:
|
|
469
|
+
"""Initialize credential store.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
credential_path: Custom path to credentials file.
|
|
473
|
+
Defaults to ~/.raxe/credentials.json
|
|
474
|
+
"""
|
|
475
|
+
self._credential_path = credential_path or DEFAULT_CREDENTIAL_FILE
|
|
476
|
+
self._cached_credentials: Credentials | None = None
|
|
477
|
+
|
|
478
|
+
@property
|
|
479
|
+
def credential_path(self) -> Path:
|
|
480
|
+
"""Get the credential file path.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Path to the credentials file.
|
|
484
|
+
"""
|
|
485
|
+
return self._credential_path
|
|
486
|
+
|
|
487
|
+
def load(self) -> Credentials | None:
|
|
488
|
+
"""Load credentials from file.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
Credentials if file exists and is valid, None otherwise.
|
|
492
|
+
|
|
493
|
+
Raises:
|
|
494
|
+
CredentialError: If file exists but cannot be parsed.
|
|
495
|
+
"""
|
|
496
|
+
if not self._credential_path.exists():
|
|
497
|
+
logger.debug("Credential file does not exist")
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
# Check permissions and warn if insecure
|
|
501
|
+
if not _check_permissions(self._credential_path):
|
|
502
|
+
logger.warning(
|
|
503
|
+
"Credential file has insecure permissions. Consider running: chmod 600 %s",
|
|
504
|
+
self._credential_path,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
try:
|
|
508
|
+
with open(self._credential_path, encoding="utf-8") as f:
|
|
509
|
+
data = json.load(f)
|
|
510
|
+
|
|
511
|
+
credentials = Credentials.from_dict(data)
|
|
512
|
+
|
|
513
|
+
# Validate key format
|
|
514
|
+
try:
|
|
515
|
+
validate_key_format(credentials.api_key)
|
|
516
|
+
except InvalidKeyFormatError as e:
|
|
517
|
+
raise CredentialError(f"Invalid stored API key: {e}") from e
|
|
518
|
+
|
|
519
|
+
# Log without exposing key value
|
|
520
|
+
logger.debug(
|
|
521
|
+
"Loaded credentials: type=%s, installation=%s",
|
|
522
|
+
credentials.key_type,
|
|
523
|
+
credentials.installation_id,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
self._cached_credentials = credentials
|
|
527
|
+
return credentials
|
|
528
|
+
|
|
529
|
+
except json.JSONDecodeError as e:
|
|
530
|
+
raise CredentialError(f"Invalid JSON in credential file: {e}") from e
|
|
531
|
+
except ValueError as e:
|
|
532
|
+
raise CredentialError(f"Invalid credential format: {e}") from e
|
|
533
|
+
|
|
534
|
+
def save(self, credentials: Credentials) -> None:
|
|
535
|
+
"""Save credentials with chmod 600.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
credentials: Credentials to save.
|
|
539
|
+
|
|
540
|
+
Raises:
|
|
541
|
+
CredentialError: If file cannot be written.
|
|
542
|
+
"""
|
|
543
|
+
# Validate key format before saving
|
|
544
|
+
try:
|
|
545
|
+
validate_key_format(credentials.api_key)
|
|
546
|
+
except InvalidKeyFormatError as e:
|
|
547
|
+
raise CredentialError(f"Cannot save invalid API key: {e}") from e
|
|
548
|
+
|
|
549
|
+
# Ensure parent directory exists
|
|
550
|
+
self._credential_path.parent.mkdir(parents=True, exist_ok=True)
|
|
551
|
+
|
|
552
|
+
try:
|
|
553
|
+
# Write to file
|
|
554
|
+
with open(self._credential_path, "w", encoding="utf-8") as f:
|
|
555
|
+
json.dump(credentials.to_dict(), f, indent=2)
|
|
556
|
+
|
|
557
|
+
# Set secure permissions
|
|
558
|
+
_set_secure_permissions(self._credential_path)
|
|
559
|
+
|
|
560
|
+
# Log without exposing key value
|
|
561
|
+
logger.info(
|
|
562
|
+
"Saved credentials: type=%s, installation=%s",
|
|
563
|
+
credentials.key_type,
|
|
564
|
+
credentials.installation_id,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
self._cached_credentials = credentials
|
|
568
|
+
|
|
569
|
+
except OSError as e:
|
|
570
|
+
raise CredentialError(f"Failed to write credential file: {e}") from e
|
|
571
|
+
|
|
572
|
+
def generate_temp_credentials(self) -> Credentials:
|
|
573
|
+
"""Generate temporary credentials for zero-friction onboarding.
|
|
574
|
+
|
|
575
|
+
Creates new temporary credentials with:
|
|
576
|
+
- installation_id: inst_{uuid4_hex[:16]}
|
|
577
|
+
- temp key: raxe_temp_{secrets.token_hex(16)}
|
|
578
|
+
- expiry: now + 14 days
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
Newly generated temporary credentials.
|
|
582
|
+
"""
|
|
583
|
+
now = _get_utc_now_iso()
|
|
584
|
+
expiry = datetime.now(timezone.utc)
|
|
585
|
+
expiry = expiry.replace(day=expiry.day) # Keep same time
|
|
586
|
+
|
|
587
|
+
# Calculate expiry (14 days from now)
|
|
588
|
+
from datetime import timedelta
|
|
589
|
+
|
|
590
|
+
expiry_dt = datetime.now(timezone.utc) + timedelta(days=TEMP_KEY_EXPIRY_DAYS)
|
|
591
|
+
expires_at = expiry_dt.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
592
|
+
|
|
593
|
+
credentials = Credentials(
|
|
594
|
+
api_key=_generate_temp_key(),
|
|
595
|
+
key_type="temporary",
|
|
596
|
+
installation_id=_generate_installation_id(),
|
|
597
|
+
created_at=now,
|
|
598
|
+
expires_at=expires_at,
|
|
599
|
+
first_seen_at=None,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# Log without exposing key value
|
|
603
|
+
logger.info(
|
|
604
|
+
"Generated temporary credentials: installation=%s, expires=%s",
|
|
605
|
+
credentials.installation_id,
|
|
606
|
+
credentials.expires_at,
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
return credentials
|
|
610
|
+
|
|
611
|
+
def upgrade_key(
|
|
612
|
+
self,
|
|
613
|
+
new_api_key: str,
|
|
614
|
+
key_type: Literal["live", "test"],
|
|
615
|
+
) -> Credentials:
|
|
616
|
+
"""Upgrade from temporary to permanent key.
|
|
617
|
+
|
|
618
|
+
Preserves installation_id from existing credentials if available.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
new_api_key: The new permanent API key.
|
|
622
|
+
key_type: Type of the new key (live or test).
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
Updated credentials with the new key.
|
|
626
|
+
|
|
627
|
+
Raises:
|
|
628
|
+
InvalidKeyFormatError: If the new key format is invalid.
|
|
629
|
+
CredentialError: If key_type doesn't match the new key prefix.
|
|
630
|
+
|
|
631
|
+
Note:
|
|
632
|
+
For telemetry event creation with key IDs, use upgrade_key_with_info()
|
|
633
|
+
instead, which returns KeyUpgradeInfo containing both old and new key IDs.
|
|
634
|
+
"""
|
|
635
|
+
upgrade_info = self.upgrade_key_with_info(new_api_key, key_type)
|
|
636
|
+
return upgrade_info.new_credentials
|
|
637
|
+
|
|
638
|
+
def upgrade_key_with_info(
|
|
639
|
+
self,
|
|
640
|
+
new_api_key: str,
|
|
641
|
+
key_type: Literal["live", "test"],
|
|
642
|
+
) -> KeyUpgradeInfo:
|
|
643
|
+
"""Upgrade from temporary to permanent key with full upgrade information.
|
|
644
|
+
|
|
645
|
+
Preserves installation_id from existing credentials if available.
|
|
646
|
+
Returns KeyUpgradeInfo containing both old and new key IDs for
|
|
647
|
+
telemetry event creation.
|
|
648
|
+
|
|
649
|
+
Args:
|
|
650
|
+
new_api_key: The new permanent API key.
|
|
651
|
+
key_type: Type of the new key (live or test).
|
|
652
|
+
|
|
653
|
+
Returns:
|
|
654
|
+
KeyUpgradeInfo with old/new key IDs and the new credentials.
|
|
655
|
+
|
|
656
|
+
Raises:
|
|
657
|
+
InvalidKeyFormatError: If the new key format is invalid.
|
|
658
|
+
CredentialError: If key_type doesn't match the new key prefix.
|
|
659
|
+
|
|
660
|
+
Example:
|
|
661
|
+
>>> store = CredentialStore()
|
|
662
|
+
>>> info = store.upgrade_key_with_info("raxe_live_abc...", "live")
|
|
663
|
+
>>> event = create_key_upgrade_event(
|
|
664
|
+
... previous_key_type="temp",
|
|
665
|
+
... new_key_type="community",
|
|
666
|
+
... previous_key_id=info.previous_key_id,
|
|
667
|
+
... new_key_id=info.new_key_id,
|
|
668
|
+
... days_on_previous=info.days_on_previous,
|
|
669
|
+
... )
|
|
670
|
+
"""
|
|
671
|
+
# Validate key format
|
|
672
|
+
detected_type = validate_key_format(new_api_key)
|
|
673
|
+
|
|
674
|
+
# Check for temporary key first (cannot upgrade to temp)
|
|
675
|
+
if detected_type == "temporary":
|
|
676
|
+
raise CredentialError("Cannot upgrade to a temporary key")
|
|
677
|
+
|
|
678
|
+
if detected_type != key_type:
|
|
679
|
+
raise CredentialError(
|
|
680
|
+
f"Key type mismatch: provided '{key_type}' but key is '{detected_type}'"
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# Load existing credentials to preserve installation_id and capture old key info
|
|
684
|
+
existing = self.load()
|
|
685
|
+
installation_id = existing.installation_id if existing else _generate_installation_id()
|
|
686
|
+
|
|
687
|
+
# Capture old key information for telemetry event
|
|
688
|
+
previous_key_id: str | None = None
|
|
689
|
+
previous_key_type: str | None = None
|
|
690
|
+
days_on_previous: int | None = None
|
|
691
|
+
|
|
692
|
+
if existing:
|
|
693
|
+
previous_key_id = compute_key_id(existing.api_key)
|
|
694
|
+
previous_key_type = existing.key_type
|
|
695
|
+
# Calculate days on previous key
|
|
696
|
+
try:
|
|
697
|
+
created = datetime.fromisoformat(
|
|
698
|
+
existing.created_at.replace("Z", "+00:00")
|
|
699
|
+
)
|
|
700
|
+
now = datetime.now(timezone.utc)
|
|
701
|
+
days_on_previous = (now - created).days
|
|
702
|
+
except (ValueError, TypeError):
|
|
703
|
+
days_on_previous = None
|
|
704
|
+
|
|
705
|
+
# Determine tier based on key type (live keys default to community tier)
|
|
706
|
+
tier = "community" if key_type == "live" else "test"
|
|
707
|
+
|
|
708
|
+
credentials = Credentials(
|
|
709
|
+
api_key=new_api_key,
|
|
710
|
+
key_type=key_type,
|
|
711
|
+
installation_id=installation_id,
|
|
712
|
+
created_at=_get_utc_now_iso(),
|
|
713
|
+
expires_at=None, # Permanent keys don't expire
|
|
714
|
+
first_seen_at=None,
|
|
715
|
+
tier=tier,
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
# Save the upgraded credentials
|
|
719
|
+
self.save(credentials)
|
|
720
|
+
|
|
721
|
+
# Compute new key ID
|
|
722
|
+
new_key_id = compute_key_id(new_api_key)
|
|
723
|
+
|
|
724
|
+
logger.info(
|
|
725
|
+
"Upgraded to permanent key: type=%s, installation=%s",
|
|
726
|
+
key_type,
|
|
727
|
+
installation_id,
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
return KeyUpgradeInfo(
|
|
731
|
+
previous_key_id=previous_key_id,
|
|
732
|
+
new_key_id=new_key_id,
|
|
733
|
+
previous_key_type=previous_key_type,
|
|
734
|
+
new_key_type=key_type,
|
|
735
|
+
days_on_previous=days_on_previous,
|
|
736
|
+
new_credentials=credentials,
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
def get_or_create(self, *, raise_on_expired: bool = True) -> Credentials:
|
|
740
|
+
"""Load existing or generate new temp credentials.
|
|
741
|
+
|
|
742
|
+
Priority chain:
|
|
743
|
+
1. RAXE_API_KEY environment variable (highest - explicit override)
|
|
744
|
+
2. ~/.raxe/credentials.json file
|
|
745
|
+
3. Generate new temp credentials (last resort)
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
raise_on_expired: If True, raises CredentialExpiredError when
|
|
749
|
+
credentials are expired. If False, returns expired credentials
|
|
750
|
+
for caller to handle. Defaults to True.
|
|
751
|
+
|
|
752
|
+
Returns:
|
|
753
|
+
Existing credentials if valid, or newly generated temp credentials.
|
|
754
|
+
|
|
755
|
+
Raises:
|
|
756
|
+
CredentialExpiredError: If credentials are expired and raise_on_expired is True.
|
|
757
|
+
"""
|
|
758
|
+
# Priority 1: Check RAXE_API_KEY environment variable first
|
|
759
|
+
env_api_key = os.environ.get("RAXE_API_KEY", "").strip()
|
|
760
|
+
if env_api_key:
|
|
761
|
+
try:
|
|
762
|
+
key_type = validate_key_format(env_api_key)
|
|
763
|
+
# Create credentials from env var (don't persist to file)
|
|
764
|
+
# Try to preserve installation_id from existing file if available
|
|
765
|
+
existing = self.load()
|
|
766
|
+
installation_id = (
|
|
767
|
+
existing.installation_id if existing else _generate_installation_id()
|
|
768
|
+
)
|
|
769
|
+
return Credentials(
|
|
770
|
+
api_key=env_api_key,
|
|
771
|
+
key_type=key_type,
|
|
772
|
+
installation_id=installation_id,
|
|
773
|
+
created_at=_get_utc_now_iso(),
|
|
774
|
+
expires_at=None, # Env var keys don't have local expiry tracking
|
|
775
|
+
first_seen_at=None,
|
|
776
|
+
)
|
|
777
|
+
except InvalidKeyFormatError:
|
|
778
|
+
logger.warning(
|
|
779
|
+
"Invalid RAXE_API_KEY format in environment, falling back to file"
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
# Priority 2: Try to load from credentials.json file
|
|
783
|
+
try:
|
|
784
|
+
credentials = self.load()
|
|
785
|
+
if credentials is not None:
|
|
786
|
+
# Check if expired
|
|
787
|
+
if credentials.is_expired():
|
|
788
|
+
days_expired = credentials.days_since_expiry() or 0
|
|
789
|
+
console_url = CredentialExpiredError.get_default_console_url()
|
|
790
|
+
|
|
791
|
+
# Build helpful error message
|
|
792
|
+
if days_expired == 0:
|
|
793
|
+
expiry_text = "today"
|
|
794
|
+
elif days_expired == 1:
|
|
795
|
+
expiry_text = "1 day ago"
|
|
796
|
+
else:
|
|
797
|
+
expiry_text = f"{days_expired} days ago"
|
|
798
|
+
|
|
799
|
+
message = (
|
|
800
|
+
f"Your temporary API key expired {expiry_text}. "
|
|
801
|
+
f"Get a permanent key at: {console_url}\n"
|
|
802
|
+
"Or run: raxe auth login"
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
logger.warning(
|
|
806
|
+
"credentials_expired",
|
|
807
|
+
extra={
|
|
808
|
+
"days_expired": days_expired,
|
|
809
|
+
"key_type": credentials.key_type,
|
|
810
|
+
"installation_id": credentials.installation_id,
|
|
811
|
+
},
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
if raise_on_expired:
|
|
815
|
+
raise CredentialExpiredError(
|
|
816
|
+
message,
|
|
817
|
+
console_url=console_url,
|
|
818
|
+
days_expired=days_expired,
|
|
819
|
+
)
|
|
820
|
+
# Return expired credentials if caller wants to handle it
|
|
821
|
+
return credentials
|
|
822
|
+
except CredentialError as e:
|
|
823
|
+
# Re-raise CredentialExpiredError, catch other CredentialErrors
|
|
824
|
+
if isinstance(e, CredentialExpiredError):
|
|
825
|
+
raise
|
|
826
|
+
logger.warning("Failed to load credentials: %s", e)
|
|
827
|
+
|
|
828
|
+
# Generate new temp credentials
|
|
829
|
+
credentials = self.generate_temp_credentials()
|
|
830
|
+
|
|
831
|
+
# Save them
|
|
832
|
+
self.save(credentials)
|
|
833
|
+
|
|
834
|
+
return credentials
|
|
835
|
+
|
|
836
|
+
def delete(self) -> bool:
|
|
837
|
+
"""Delete credentials file.
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
True if file was deleted, False if it didn't exist.
|
|
841
|
+
"""
|
|
842
|
+
if not self._credential_path.exists():
|
|
843
|
+
logger.debug("Credential file does not exist, nothing to delete")
|
|
844
|
+
return False
|
|
845
|
+
|
|
846
|
+
try:
|
|
847
|
+
self._credential_path.unlink()
|
|
848
|
+
self._cached_credentials = None
|
|
849
|
+
logger.info("Deleted credential file")
|
|
850
|
+
return True
|
|
851
|
+
except OSError as e:
|
|
852
|
+
logger.error("Failed to delete credential file: %s", e)
|
|
853
|
+
return False
|
|
854
|
+
|
|
855
|
+
def update_first_seen(self, first_seen_at: str) -> Credentials | None:
|
|
856
|
+
"""Update the first_seen_at timestamp from server.
|
|
857
|
+
|
|
858
|
+
Args:
|
|
859
|
+
first_seen_at: ISO 8601 timestamp from server health check.
|
|
860
|
+
|
|
861
|
+
Returns:
|
|
862
|
+
Updated credentials, or None if no credentials loaded.
|
|
863
|
+
"""
|
|
864
|
+
credentials = self.load()
|
|
865
|
+
if credentials is None:
|
|
866
|
+
return None
|
|
867
|
+
|
|
868
|
+
if credentials.first_seen_at is not None:
|
|
869
|
+
# Already set, don't overwrite
|
|
870
|
+
return credentials
|
|
871
|
+
|
|
872
|
+
# Create updated credentials (preserve all fields)
|
|
873
|
+
updated = Credentials(
|
|
874
|
+
api_key=credentials.api_key,
|
|
875
|
+
key_type=credentials.key_type,
|
|
876
|
+
installation_id=credentials.installation_id,
|
|
877
|
+
created_at=credentials.created_at,
|
|
878
|
+
expires_at=credentials.expires_at,
|
|
879
|
+
first_seen_at=first_seen_at,
|
|
880
|
+
can_disable_telemetry=credentials.can_disable_telemetry,
|
|
881
|
+
offline_mode=credentials.offline_mode,
|
|
882
|
+
tier=credentials.tier,
|
|
883
|
+
last_health_check=credentials.last_health_check,
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
self.save(updated)
|
|
887
|
+
return updated
|
|
888
|
+
|
|
889
|
+
def update_from_health(self, health_response: dict) -> Credentials | None:
|
|
890
|
+
"""Update cached permissions from /v1/health response.
|
|
891
|
+
|
|
892
|
+
This method updates the server-side permission cache from a health
|
|
893
|
+
check response. It should be called after a successful health check
|
|
894
|
+
to keep the local cache in sync with server-side permissions.
|
|
895
|
+
|
|
896
|
+
Args:
|
|
897
|
+
health_response: Dictionary from /v1/health response containing:
|
|
898
|
+
- can_disable_telemetry: bool
|
|
899
|
+
- offline_mode: bool (optional)
|
|
900
|
+
- tier: str (temporary, community, pro, enterprise)
|
|
901
|
+
- server_time: str (ISO 8601)
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
Updated credentials, or None if no credentials loaded.
|
|
905
|
+
|
|
906
|
+
Example:
|
|
907
|
+
>>> store = CredentialStore()
|
|
908
|
+
>>> health = shipper.check_health()
|
|
909
|
+
>>> store.update_from_health({
|
|
910
|
+
... "can_disable_telemetry": True,
|
|
911
|
+
... "tier": "pro",
|
|
912
|
+
... "server_time": "2025-01-26T12:00:00Z"
|
|
913
|
+
... })
|
|
914
|
+
"""
|
|
915
|
+
credentials = self.load()
|
|
916
|
+
if credentials is None:
|
|
917
|
+
return None
|
|
918
|
+
|
|
919
|
+
# Extract permissions from health response
|
|
920
|
+
can_disable = health_response.get("can_disable_telemetry", False)
|
|
921
|
+
offline_mode = health_response.get("offline_mode", False)
|
|
922
|
+
tier = health_response.get("tier", credentials.tier)
|
|
923
|
+
server_time = health_response.get("server_time", _get_utc_now_iso())
|
|
924
|
+
|
|
925
|
+
# Create updated credentials with new permissions
|
|
926
|
+
updated = Credentials(
|
|
927
|
+
api_key=credentials.api_key,
|
|
928
|
+
key_type=credentials.key_type,
|
|
929
|
+
installation_id=credentials.installation_id,
|
|
930
|
+
created_at=credentials.created_at,
|
|
931
|
+
expires_at=credentials.expires_at,
|
|
932
|
+
first_seen_at=credentials.first_seen_at,
|
|
933
|
+
can_disable_telemetry=can_disable,
|
|
934
|
+
offline_mode=offline_mode,
|
|
935
|
+
tier=tier,
|
|
936
|
+
last_health_check=server_time,
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
self.save(updated)
|
|
940
|
+
|
|
941
|
+
logger.debug(
|
|
942
|
+
"Updated credentials from health check: tier=%s, can_disable_telemetry=%s",
|
|
943
|
+
tier,
|
|
944
|
+
can_disable,
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
return updated
|