guard-core 2.2.2__tar.gz → 3.1.0__tar.gz
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.
- {guard_core-2.2.2/guard_core.egg-info → guard_core-3.1.0}/PKG-INFO +18 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/README.md +17 -0
- {guard_core-2.2.2/guard_core/sync → guard_core-3.1.0/guard_core}/core/events/event_types.py +2 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/initialization/handler_initializer.py +30 -12
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/access_control.py +16 -5
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/base.py +2 -2
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/cloud_handler.py +98 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/cloud_ip_stores.py +1 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/dynamic_rule_handler.py +25 -8
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/ratelimit_handler.py +43 -9
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/models.py +76 -20
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/cloud_ip_store_protocol.py +6 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/initialization/handler_initializer.py +32 -12
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/access_control.py +16 -5
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/base.py +2 -2
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/cloud_handler.py +96 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/cloud_ip_stores.py +1 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/dynamic_rule_handler.py +25 -8
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/ratelimit_handler.py +41 -9
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/cloud_ip_store_protocol.py +6 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/utils.py +16 -3
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/utils.py +16 -3
- {guard_core-2.2.2 → guard_core-3.1.0/guard_core.egg-info}/PKG-INFO +18 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/pyproject.toml +1 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_cloud_ip_refresh_on_demand.py +1 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_cloud_ip_stores.py +23 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_dynamic_rule_atomicity.py +1 -1
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_event_types.py +2 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_handler_initializer_lazy_init.py +18 -2
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_pipeline_fail_secure.py +5 -5
- {guard_core-2.2.2 → guard_core-3.1.0}/LICENSE +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/MANIFEST.in +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/behavioral/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/behavioral/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/behavioral/processor.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/bypass/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/bypass/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/bypass/handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/base.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/helpers.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/authentication.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/cloud_ip_refresh.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/cloud_provider.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/custom_request.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/custom_validators.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/emergency_mode.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/https_enforcement.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/ip_security.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/rate_limit.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/referrer.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/request_logging.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/request_size_content.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/required_headers.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/route_config.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/suspicious_activity.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/time_window.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/implementations/user_agent.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/checks/pipeline.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/events/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/events/composite_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/events/enricher.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/events/logfire_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/events/metrics.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/events/middleware_events.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/events/otel_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/initialization/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/responses/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/responses/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/responses/factory.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/routing/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/routing/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/routing/resolver.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/validation/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/validation/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/core/validation/validator.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/advanced.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/authentication.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/behavioral.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/content_filtering.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/decorators/rate_limiting.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/detection_engine/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/detection_engine/compiler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/detection_engine/monitor.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/detection_engine/preprocessor.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/detection_engine/semantic.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/detection_result.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/exceptions.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/behavior_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/cors_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/ipban_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/ipinfo_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/redis_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/security_headers_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/handlers/suspatterns_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/agent_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/geo_ip_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/middleware_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/redis_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/request_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/protocols/response_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/py.typed +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/scripts/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/scripts/rate_lua.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/behavioral/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/behavioral/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/behavioral/processor.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/bypass/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/bypass/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/bypass/handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/base.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/helpers.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/authentication.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/cloud_ip_refresh.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/cloud_provider.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/custom_request.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/custom_validators.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/emergency_mode.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/https_enforcement.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/ip_security.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/rate_limit.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/referrer.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/request_logging.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/request_size_content.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/required_headers.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/route_config.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/suspicious_activity.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/time_window.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/implementations/user_agent.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/checks/pipeline.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/events/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/events/composite_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/events/enricher.py +0 -0
- {guard_core-2.2.2/guard_core → guard_core-3.1.0/guard_core/sync}/core/events/event_types.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/events/logfire_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/events/metrics.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/events/middleware_events.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/events/otel_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/initialization/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/responses/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/responses/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/responses/factory.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/routing/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/routing/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/routing/resolver.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/validation/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/validation/context.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/core/validation/validator.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/advanced.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/authentication.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/behavioral.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/content_filtering.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/decorators/rate_limiting.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/detection_engine/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/detection_engine/compiler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/detection_engine/monitor.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/detection_engine/preprocessor.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/detection_engine/semantic.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/detection_result.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/behavior_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/cors_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/ipban_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/ipinfo_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/redis_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/security_headers_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/handlers/suspatterns_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/agent_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/geo_ip_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/middleware_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/redis_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/request_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/protocols/response_protocol.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/py.typed +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/scripts/__init__.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core/sync/scripts/rate_lua.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core.egg-info/SOURCES.txt +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core.egg-info/dependency_links.txt +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core.egg-info/requires.txt +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/guard_core.egg-info/top_level.txt +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/setup.cfg +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/setup.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_behavior_rule_ban_duration.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_check_log_muting.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_compiler_cache.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_composite_enricher.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_composite_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_composite_handler_extra.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_cors_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_detection_categories.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_detection_exclusion_integration.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_detection_result.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_detection_result_propagation.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher_behavior_correlation.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher_end_to_end.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher_identity.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher_logfire_integration.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher_otel_integration.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher_rule_correlation.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_enricher_threat_score.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_event_bus_filtering.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_handler_edge_cases.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_handler_initializer.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_handler_initializer_enricher.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_handler_initializer_factories.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_handlers_integration.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_ipban_cidr.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_ipban_eviction.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_ipban_lifecycle.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_ipban_ttl.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_ipinfo_lifecycle.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_logfire_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_logfire_handler_metric.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_otel_handler.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_otel_handler_resource_attrs.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_preprocessor_attack_regions.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_preprocessor_encodings.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_suspicious_counts_per_type.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_telemetry_integration.py +0 -0
- {guard_core-2.2.2 → guard_core-3.1.0}/tests/test_threat_ban_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: guard-core
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Framework-agnostic security engine for the Guard ecosystem.
|
|
5
5
|
Author-email: Renzo Franceschini <rennf93@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -308,6 +308,23 @@ See the [SecurityConfig Reference](https://rennf93.github.io/guard-core/latest/c
|
|
|
308
308
|
|
|
309
309
|
___
|
|
310
310
|
|
|
311
|
+
Migration: fail_secure default flipped
|
|
312
|
+
--------------------------------------
|
|
313
|
+
|
|
314
|
+
`SecurityConfig.fail_secure` now defaults to `True`. When a security check raises an unexpected exception, the request is blocked with HTTP 500 instead of falling through.
|
|
315
|
+
|
|
316
|
+
**Why**: the old fail-open default silently masked check bugs. The new default surfaces them so they can be fixed instead of leaking past the security layer.
|
|
317
|
+
|
|
318
|
+
**Migration**: to restore the previous fail-open behavior, opt in explicitly:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
config = SecurityConfig(fail_secure=False)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Recommended path: keep the new default and fix any check exceptions that surface. The old default could mask genuine bugs.
|
|
325
|
+
|
|
326
|
+
___
|
|
327
|
+
|
|
311
328
|
Detection Engine
|
|
312
329
|
----------------
|
|
313
330
|
|
|
@@ -250,6 +250,23 @@ See the [SecurityConfig Reference](https://rennf93.github.io/guard-core/latest/c
|
|
|
250
250
|
|
|
251
251
|
___
|
|
252
252
|
|
|
253
|
+
Migration: fail_secure default flipped
|
|
254
|
+
--------------------------------------
|
|
255
|
+
|
|
256
|
+
`SecurityConfig.fail_secure` now defaults to `True`. When a security check raises an unexpected exception, the request is blocked with HTTP 500 instead of falling through.
|
|
257
|
+
|
|
258
|
+
**Why**: the old fail-open default silently masked check bugs. The new default surfaces them so they can be fixed instead of leaking past the security layer.
|
|
259
|
+
|
|
260
|
+
**Migration**: to restore the previous fail-open behavior, opt in explicitly:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
config = SecurityConfig(fail_secure=False)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Recommended path: keep the new default and fix any check exceptions that surface. The old default could mask genuine bugs.
|
|
267
|
+
|
|
268
|
+
___
|
|
269
|
+
|
|
253
270
|
Detection Engine
|
|
254
271
|
----------------
|
|
255
272
|
|
|
@@ -28,6 +28,7 @@ EVENT_PATH_EXCLUDED = "path_excluded"
|
|
|
28
28
|
EVENT_PATTERN_ADDED = "pattern_added"
|
|
29
29
|
EVENT_PATTERN_REMOVED = "pattern_removed"
|
|
30
30
|
EVENT_RATE_LIMITED = "rate_limited"
|
|
31
|
+
EVENT_RATE_LIMIT_SCRIPT_RELOADED = "rate_limit_script_reloaded"
|
|
31
32
|
EVENT_REDIS_CONNECTION = "redis_connection"
|
|
32
33
|
EVENT_REDIS_ERROR = "redis_error"
|
|
33
34
|
EVENT_SECURITY_BYPASS = "security_bypass"
|
|
@@ -61,6 +62,7 @@ EVENT_TYPE_VALUES: frozenset[str] = frozenset(
|
|
|
61
62
|
EVENT_PATTERN_ADDED,
|
|
62
63
|
EVENT_PATTERN_REMOVED,
|
|
63
64
|
EVENT_RATE_LIMITED,
|
|
65
|
+
EVENT_RATE_LIMIT_SCRIPT_RELOADED,
|
|
64
66
|
EVENT_REDIS_CONNECTION,
|
|
65
67
|
EVENT_REDIS_ERROR,
|
|
66
68
|
EVENT_SECURITY_BYPASS,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
4
|
+
|
|
5
|
+
from guard_core.protocols.cloud_ip_store_protocol import CloudIpStoreProtocol
|
|
4
6
|
|
|
5
7
|
if TYPE_CHECKING:
|
|
6
8
|
from guard_core.core.events.metrics import MetricsCollector
|
|
@@ -116,21 +118,37 @@ class HandlerInitializer:
|
|
|
116
118
|
)
|
|
117
119
|
|
|
118
120
|
async def _run_lazy_init(self) -> None:
|
|
119
|
-
|
|
120
|
-
from guard_core.handlers.cloud_handler import cloud_handler
|
|
121
|
+
from guard_core.handlers.cloud_handler import cloud_handler
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
if self.config.block_cloud_providers:
|
|
124
|
+
try:
|
|
123
125
|
await cloud_handler.initialize_redis(
|
|
124
126
|
self.redis_handler,
|
|
125
|
-
self.config.block_cloud_providers,
|
|
127
|
+
cast(set[str], self.config.block_cloud_providers),
|
|
126
128
|
ttl=self.config.cloud_ip_refresh_interval,
|
|
127
129
|
)
|
|
128
|
-
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self.logger.warning(
|
|
132
|
+
"Lazy cloud-IP initialization failed: %s", e, exc_info=True
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if self.geo_ip_handler is not None:
|
|
136
|
+
try:
|
|
129
137
|
await self.geo_ip_handler.initialize_redis(self.redis_handler)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
except Exception as e:
|
|
139
|
+
self.logger.warning(
|
|
140
|
+
"Lazy geo-IP initialization failed: %s", e, exc_info=True
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _resolve_cloud_ip_store(self) -> CloudIpStoreProtocol:
|
|
144
|
+
store = self.config.cloud_ip_store
|
|
145
|
+
needs_invocation = isinstance(store, type) or (
|
|
146
|
+
callable(store) and not isinstance(store, CloudIpStoreProtocol)
|
|
147
|
+
)
|
|
148
|
+
if needs_invocation:
|
|
149
|
+
factory = cast(Any, store)
|
|
150
|
+
return cast(CloudIpStoreProtocol, factory(self.redis_handler))
|
|
151
|
+
return cast(CloudIpStoreProtocol, store)
|
|
134
152
|
|
|
135
153
|
async def initialize_redis_handlers(self) -> None:
|
|
136
154
|
if not (self.config.enable_redis and self.redis_handler):
|
|
@@ -143,7 +161,7 @@ class HandlerInitializer:
|
|
|
143
161
|
from guard_core.handlers.suspatterns_handler import sus_patterns_handler
|
|
144
162
|
|
|
145
163
|
if self.config.cloud_ip_store is not None:
|
|
146
|
-
cloud_handler.set_store(self.
|
|
164
|
+
cloud_handler.set_store(self._resolve_cloud_ip_store())
|
|
147
165
|
|
|
148
166
|
if self.config.lazy_init:
|
|
149
167
|
self._lazy_init_task = asyncio.create_task(self._run_lazy_init())
|
|
@@ -151,7 +169,7 @@ class HandlerInitializer:
|
|
|
151
169
|
if self.config.block_cloud_providers:
|
|
152
170
|
await cloud_handler.initialize_redis(
|
|
153
171
|
self.redis_handler,
|
|
154
|
-
self.config.block_cloud_providers,
|
|
172
|
+
cast(set[str], self.config.block_cloud_providers),
|
|
155
173
|
ttl=self.config.cloud_ip_refresh_interval,
|
|
156
174
|
)
|
|
157
175
|
if self.geo_ip_handler is not None:
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from collections.abc import Callable
|
|
2
|
-
from typing import Any
|
|
3
|
+
from typing import Any, cast
|
|
3
4
|
|
|
4
5
|
from guard_core.decorators.base import BaseSecurityMixin, DecoratedFunction
|
|
6
|
+
from guard_core.models import VALID_CLOUD_PROVIDERS, CloudProvider
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class AccessControlMixin(BaseSecurityMixin):
|
|
@@ -25,7 +27,7 @@ class AccessControlMixin(BaseSecurityMixin):
|
|
|
25
27
|
) -> Callable[[Callable[..., Any]], DecoratedFunction]:
|
|
26
28
|
def decorator(func: Callable[..., Any]) -> DecoratedFunction:
|
|
27
29
|
route_config = self._ensure_route_config(func)
|
|
28
|
-
route_config.blocked_countries = countries
|
|
30
|
+
route_config.blocked_countries = [c.upper() for c in countries]
|
|
29
31
|
return self._apply_route_config(func)
|
|
30
32
|
|
|
31
33
|
return decorator
|
|
@@ -35,7 +37,7 @@ class AccessControlMixin(BaseSecurityMixin):
|
|
|
35
37
|
) -> Callable[[Callable[..., Any]], DecoratedFunction]:
|
|
36
38
|
def decorator(func: Callable[..., Any]) -> DecoratedFunction:
|
|
37
39
|
route_config = self._ensure_route_config(func)
|
|
38
|
-
route_config.whitelist_countries = countries
|
|
40
|
+
route_config.whitelist_countries = [c.upper() for c in countries]
|
|
39
41
|
return self._apply_route_config(func)
|
|
40
42
|
|
|
41
43
|
return decorator
|
|
@@ -46,9 +48,18 @@ class AccessControlMixin(BaseSecurityMixin):
|
|
|
46
48
|
def decorator(func: Callable[..., Any]) -> DecoratedFunction:
|
|
47
49
|
route_config = self._ensure_route_config(func)
|
|
48
50
|
if providers is None:
|
|
49
|
-
route_config.block_cloud_providers =
|
|
51
|
+
route_config.block_cloud_providers = cast(
|
|
52
|
+
set[CloudProvider], {"AWS", "GCP", "Azure"}
|
|
53
|
+
)
|
|
50
54
|
else:
|
|
51
|
-
|
|
55
|
+
valid = {p for p in providers if p in VALID_CLOUD_PROVIDERS}
|
|
56
|
+
route_config.block_cloud_providers = cast(set[CloudProvider], valid)
|
|
57
|
+
invalid = set(providers) - valid
|
|
58
|
+
if invalid:
|
|
59
|
+
logging.getLogger("guard_core.decorators").warning(
|
|
60
|
+
"@block_clouds: ignored unknown cloud providers %s",
|
|
61
|
+
sorted(invalid),
|
|
62
|
+
)
|
|
52
63
|
return self._apply_route_config(func)
|
|
53
64
|
|
|
54
65
|
return decorator
|
|
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
|
|
|
3
3
|
from typing import Any, Protocol, cast, runtime_checkable
|
|
4
4
|
|
|
5
5
|
from guard_core.handlers.behavior_handler import BehaviorRule, BehaviorTracker
|
|
6
|
-
from guard_core.models import SecurityConfig
|
|
6
|
+
from guard_core.models import CloudProvider, SecurityConfig
|
|
7
7
|
from guard_core.protocols.request_protocol import GuardRequest
|
|
8
8
|
|
|
9
9
|
|
|
@@ -29,7 +29,7 @@ class RouteConfig:
|
|
|
29
29
|
self.blocked_user_agents: list[str] = []
|
|
30
30
|
self.required_headers: dict[str, str] = {}
|
|
31
31
|
self.behavior_rules: list[BehaviorRule] = []
|
|
32
|
-
self.block_cloud_providers: set[
|
|
32
|
+
self.block_cloud_providers: set[CloudProvider] = set()
|
|
33
33
|
self.max_request_size: int | None = None
|
|
34
34
|
self.allowed_content_types: list[str] | None = None
|
|
35
35
|
self.time_restrictions: dict[str, str] | None = None
|
|
@@ -94,7 +94,92 @@ async def fetch_azure_ip_ranges() -> set[ipaddress.IPv4Network | ipaddress.IPv6N
|
|
|
94
94
|
return set()
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
async def fetch_digitalocean_ip_ranges() -> set[
|
|
98
|
+
ipaddress.IPv4Network | ipaddress.IPv6Network
|
|
99
|
+
]:
|
|
100
|
+
try:
|
|
101
|
+
async with aiohttp.ClientSession() as session:
|
|
102
|
+
response = await session.get(
|
|
103
|
+
"https://www.digitalocean.com/geo/google.csv",
|
|
104
|
+
timeout=aiohttp.ClientTimeout(total=10),
|
|
105
|
+
)
|
|
106
|
+
response.raise_for_status()
|
|
107
|
+
body = await response.text()
|
|
108
|
+
|
|
109
|
+
networks: set[ipaddress.IPv4Network | ipaddress.IPv6Network] = set()
|
|
110
|
+
for raw_line in body.splitlines():
|
|
111
|
+
line = raw_line.strip()
|
|
112
|
+
if not line or line.startswith("#"):
|
|
113
|
+
continue
|
|
114
|
+
prefix = line.split(",", 1)[0].strip()
|
|
115
|
+
if not prefix:
|
|
116
|
+
continue
|
|
117
|
+
try:
|
|
118
|
+
networks.add(ipaddress.ip_network(prefix))
|
|
119
|
+
except ValueError:
|
|
120
|
+
continue
|
|
121
|
+
return networks
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logging.error(f"Failed to fetch DigitalOcean IP ranges: {str(e)}")
|
|
124
|
+
return set()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def fetch_linode_ip_ranges() -> set[
|
|
128
|
+
ipaddress.IPv4Network | ipaddress.IPv6Network
|
|
129
|
+
]:
|
|
130
|
+
try:
|
|
131
|
+
async with aiohttp.ClientSession() as session:
|
|
132
|
+
response = await session.get(
|
|
133
|
+
"https://geoip.linode.com/",
|
|
134
|
+
timeout=aiohttp.ClientTimeout(total=10),
|
|
135
|
+
)
|
|
136
|
+
response.raise_for_status()
|
|
137
|
+
body = await response.text()
|
|
138
|
+
|
|
139
|
+
networks: set[ipaddress.IPv4Network | ipaddress.IPv6Network] = set()
|
|
140
|
+
for raw_line in body.splitlines():
|
|
141
|
+
line = raw_line.strip()
|
|
142
|
+
if not line or line.startswith("#"):
|
|
143
|
+
continue
|
|
144
|
+
prefix = line.split(",", 1)[0].strip()
|
|
145
|
+
if not prefix:
|
|
146
|
+
continue
|
|
147
|
+
try:
|
|
148
|
+
networks.add(ipaddress.ip_network(prefix))
|
|
149
|
+
except ValueError:
|
|
150
|
+
continue
|
|
151
|
+
return networks
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logging.error(f"Failed to fetch Linode IP ranges: {str(e)}")
|
|
154
|
+
return set()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def fetch_vultr_ip_ranges() -> set[ipaddress.IPv4Network | ipaddress.IPv6Network]:
|
|
158
|
+
try:
|
|
159
|
+
async with aiohttp.ClientSession() as session:
|
|
160
|
+
response = await session.get(
|
|
161
|
+
"https://geofeed.constant.com/?json",
|
|
162
|
+
timeout=aiohttp.ClientTimeout(total=10),
|
|
163
|
+
)
|
|
164
|
+
response.raise_for_status()
|
|
165
|
+
data = await response.json(content_type=None)
|
|
166
|
+
|
|
167
|
+
networks: set[ipaddress.IPv4Network | ipaddress.IPv6Network] = set()
|
|
168
|
+
for entry in data.get("subnets", []):
|
|
169
|
+
prefix = entry.get("ip_prefix")
|
|
170
|
+
if not prefix:
|
|
171
|
+
continue
|
|
172
|
+
try:
|
|
173
|
+
networks.add(ipaddress.ip_network(prefix))
|
|
174
|
+
except ValueError:
|
|
175
|
+
continue
|
|
176
|
+
return networks
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logging.error(f"Failed to fetch Vultr IP ranges: {str(e)}")
|
|
179
|
+
return set()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
_ALL_PROVIDERS = set({"AWS", "GCP", "Azure", "DigitalOcean", "Linode", "Vultr"})
|
|
98
183
|
|
|
99
184
|
|
|
100
185
|
class CloudManager:
|
|
@@ -113,6 +198,9 @@ class CloudManager:
|
|
|
113
198
|
"AWS": set(),
|
|
114
199
|
"GCP": set(),
|
|
115
200
|
"Azure": set(),
|
|
201
|
+
"DigitalOcean": set(),
|
|
202
|
+
"Linode": set(),
|
|
203
|
+
"Vultr": set(),
|
|
116
204
|
}
|
|
117
205
|
cls._instance.last_updated = {provider: None for provider in _ALL_PROVIDERS}
|
|
118
206
|
cls._instance.redis_handler = None
|
|
@@ -146,6 +234,9 @@ class CloudManager:
|
|
|
146
234
|
"AWS": fetch_aws_ip_ranges,
|
|
147
235
|
"GCP": fetch_gcp_ip_ranges,
|
|
148
236
|
"Azure": fetch_azure_ip_ranges,
|
|
237
|
+
"DigitalOcean": fetch_digitalocean_ip_ranges,
|
|
238
|
+
"Linode": fetch_linode_ip_ranges,
|
|
239
|
+
"Vultr": fetch_vultr_ip_ranges,
|
|
149
240
|
}[provider]()
|
|
150
241
|
if ranges:
|
|
151
242
|
old_ranges = self.ip_ranges.get(provider, set())
|
|
@@ -195,6 +286,9 @@ class CloudManager:
|
|
|
195
286
|
"AWS": fetch_aws_ip_ranges,
|
|
196
287
|
"GCP": fetch_gcp_ip_ranges,
|
|
197
288
|
"Azure": fetch_azure_ip_ranges,
|
|
289
|
+
"DigitalOcean": fetch_digitalocean_ip_ranges,
|
|
290
|
+
"Linode": fetch_linode_ip_ranges,
|
|
291
|
+
"Vultr": fetch_vultr_ip_ranges,
|
|
198
292
|
}[provider]
|
|
199
293
|
|
|
200
294
|
ranges = await fetch_func()
|
|
@@ -233,6 +327,9 @@ class CloudManager:
|
|
|
233
327
|
"AWS": fetch_aws_ip_ranges,
|
|
234
328
|
"GCP": fetch_gcp_ip_ranges,
|
|
235
329
|
"Azure": fetch_azure_ip_ranges,
|
|
330
|
+
"DigitalOcean": fetch_digitalocean_ip_ranges,
|
|
331
|
+
"Linode": fetch_linode_ip_ranges,
|
|
332
|
+
"Vultr": fetch_vultr_ip_ranges,
|
|
236
333
|
}[provider]
|
|
237
334
|
ranges = await fetch_func()
|
|
238
335
|
if ranges:
|
|
@@ -3,9 +3,14 @@ import logging
|
|
|
3
3
|
import time
|
|
4
4
|
from copy import deepcopy
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, cast
|
|
7
7
|
|
|
8
|
-
from guard_core.models import
|
|
8
|
+
from guard_core.models import (
|
|
9
|
+
VALID_CLOUD_PROVIDERS,
|
|
10
|
+
CloudProvider,
|
|
11
|
+
DynamicRules,
|
|
12
|
+
SecurityConfig,
|
|
13
|
+
)
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class DynamicRuleManager:
|
|
@@ -247,12 +252,18 @@ class DynamicRuleManager:
|
|
|
247
252
|
self, blocked: list[str], allowed: list[str]
|
|
248
253
|
) -> None:
|
|
249
254
|
if blocked:
|
|
250
|
-
|
|
251
|
-
self.
|
|
255
|
+
normalized_blocked = frozenset(c.upper() for c in blocked)
|
|
256
|
+
self.config.blocked_countries = normalized_blocked
|
|
257
|
+
self.logger.info(
|
|
258
|
+
f"Dynamic rule: Blocked countries {sorted(normalized_blocked)}"
|
|
259
|
+
)
|
|
252
260
|
|
|
253
261
|
if allowed:
|
|
254
|
-
|
|
255
|
-
self.
|
|
262
|
+
normalized_allowed = frozenset(c.upper() for c in allowed)
|
|
263
|
+
self.config.whitelist_countries = normalized_allowed
|
|
264
|
+
self.logger.info(
|
|
265
|
+
f"Dynamic rule: Whitelisted countries {sorted(normalized_allowed)}"
|
|
266
|
+
)
|
|
256
267
|
|
|
257
268
|
async def _apply_rate_limit_rules(self, rules: DynamicRules) -> None:
|
|
258
269
|
if rules.global_rate_limit:
|
|
@@ -272,8 +283,14 @@ class DynamicRuleManager:
|
|
|
272
283
|
)
|
|
273
284
|
|
|
274
285
|
async def _apply_cloud_provider_rules(self, providers: set[str]) -> None:
|
|
275
|
-
|
|
276
|
-
self.
|
|
286
|
+
valid = {p for p in providers if p in VALID_CLOUD_PROVIDERS}
|
|
287
|
+
self.config.block_cloud_providers = cast(set[CloudProvider], valid)
|
|
288
|
+
invalid = providers - valid
|
|
289
|
+
if invalid:
|
|
290
|
+
self.logger.warning(
|
|
291
|
+
f"Dynamic rule: ignored unknown cloud providers {sorted(invalid)}"
|
|
292
|
+
)
|
|
293
|
+
self.logger.info(f"Dynamic rule: Blocked cloud providers {valid}")
|
|
277
294
|
|
|
278
295
|
async def _apply_user_agent_rules(self, user_agents: list[str]) -> None:
|
|
279
296
|
self.config.blocked_user_agents = user_agents
|
|
@@ -5,7 +5,7 @@ from collections.abc import Awaitable, Callable
|
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
from typing import Any, Optional
|
|
7
7
|
|
|
8
|
-
from redis.exceptions import RedisError
|
|
8
|
+
from redis.exceptions import NoScriptError, RedisError
|
|
9
9
|
|
|
10
10
|
from guard_core.models import SecurityConfig
|
|
11
11
|
from guard_core.protocols.request_protocol import GuardRequest
|
|
@@ -54,6 +54,23 @@ class RateLimitManager:
|
|
|
54
54
|
async def initialize_agent(self, agent_handler: Any) -> None:
|
|
55
55
|
self.agent_handler = agent_handler
|
|
56
56
|
|
|
57
|
+
async def _emit_script_reloaded_event(self) -> None:
|
|
58
|
+
if not self.agent_handler:
|
|
59
|
+
return
|
|
60
|
+
try:
|
|
61
|
+
from guard_agent import SecurityEvent
|
|
62
|
+
|
|
63
|
+
event = SecurityEvent(
|
|
64
|
+
timestamp=datetime.now(timezone.utc),
|
|
65
|
+
event_type="rate_limit_script_reloaded",
|
|
66
|
+
ip_address="system",
|
|
67
|
+
action_taken="script_reloaded",
|
|
68
|
+
reason="NOSCRIPT recovery: Lua script re-cached on Redis",
|
|
69
|
+
)
|
|
70
|
+
await self.agent_handler.send_event(event)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
self.logger.error(f"Failed to send script-reload event: {e}")
|
|
73
|
+
|
|
57
74
|
async def _get_redis_request_count(
|
|
58
75
|
self,
|
|
59
76
|
client_ip: str,
|
|
@@ -78,14 +95,31 @@ class RateLimitManager:
|
|
|
78
95
|
try:
|
|
79
96
|
if self.rate_limit_script_sha:
|
|
80
97
|
async with self.redis_handler.get_connection() as conn:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
try:
|
|
99
|
+
count = await conn.evalsha(
|
|
100
|
+
self.rate_limit_script_sha,
|
|
101
|
+
1,
|
|
102
|
+
key_name,
|
|
103
|
+
current_time,
|
|
104
|
+
window,
|
|
105
|
+
limit,
|
|
106
|
+
)
|
|
107
|
+
except NoScriptError:
|
|
108
|
+
self.rate_limit_script_sha = await conn.script_load(
|
|
109
|
+
RATE_LIMIT_SCRIPT
|
|
110
|
+
)
|
|
111
|
+
self.logger.info(
|
|
112
|
+
"Rate limit Lua script reloaded after NOSCRIPT"
|
|
113
|
+
)
|
|
114
|
+
await self._emit_script_reloaded_event()
|
|
115
|
+
count = await conn.evalsha(
|
|
116
|
+
self.rate_limit_script_sha,
|
|
117
|
+
1,
|
|
118
|
+
key_name,
|
|
119
|
+
current_time,
|
|
120
|
+
window,
|
|
121
|
+
limit,
|
|
122
|
+
)
|
|
89
123
|
return int(count)
|
|
90
124
|
else:
|
|
91
125
|
async with self.redis_handler.get_connection() as conn:
|