guard-core 2.2.1__tar.gz → 2.2.2__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.1/guard_core.egg-info → guard_core-2.2.2}/PKG-INFO +16 -4
- {guard_core-2.2.1 → guard_core-2.2.2}/README.md +15 -3
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/initialization/handler_initializer.py +8 -1
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/responses/factory.py +12 -4
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/detection_engine/preprocessor.py +1 -1
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/ipban_handler.py +36 -8
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/ipinfo_handler.py +4 -1
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/initialization/handler_initializer.py +8 -1
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/responses/factory.py +12 -4
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/detection_engine/preprocessor.py +1 -1
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/ipban_handler.py +36 -8
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/ipinfo_handler.py +4 -1
- {guard_core-2.2.1 → guard_core-2.2.2/guard_core.egg-info}/PKG-INFO +16 -4
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core.egg-info/SOURCES.txt +2 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/pyproject.toml +1 -1
- guard_core-2.2.2/tests/test_ipban_eviction.py +85 -0
- guard_core-2.2.2/tests/test_ipban_lifecycle.py +246 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/LICENSE +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/MANIFEST.in +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/behavioral/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/behavioral/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/behavioral/processor.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/bypass/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/bypass/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/bypass/handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/base.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/helpers.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/authentication.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/cloud_ip_refresh.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/cloud_provider.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/custom_request.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/custom_validators.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/emergency_mode.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/https_enforcement.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/ip_security.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/rate_limit.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/referrer.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/request_logging.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/request_size_content.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/required_headers.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/route_config.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/suspicious_activity.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/time_window.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/implementations/user_agent.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/checks/pipeline.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/composite_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/enricher.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/event_types.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/logfire_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/metrics.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/middleware_events.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/events/otel_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/initialization/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/responses/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/responses/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/routing/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/routing/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/routing/resolver.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/validation/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/validation/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/core/validation/validator.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/access_control.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/advanced.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/authentication.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/base.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/behavioral.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/content_filtering.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/decorators/rate_limiting.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/detection_engine/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/detection_engine/compiler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/detection_engine/monitor.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/detection_engine/semantic.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/detection_result.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/exceptions.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/behavior_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/cloud_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/cloud_ip_stores.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/cors_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/dynamic_rule_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/ratelimit_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/redis_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/security_headers_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/handlers/suspatterns_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/models.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/agent_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/cloud_ip_store_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/geo_ip_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/middleware_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/redis_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/request_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/protocols/response_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/py.typed +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/scripts/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/scripts/rate_lua.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/behavioral/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/behavioral/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/behavioral/processor.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/bypass/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/bypass/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/bypass/handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/base.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/helpers.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/authentication.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/cloud_ip_refresh.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/cloud_provider.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/custom_request.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/custom_validators.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/emergency_mode.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/https_enforcement.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/ip_security.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/rate_limit.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/referrer.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/request_logging.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/request_size_content.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/required_headers.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/route_config.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/suspicious_activity.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/time_window.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/user_agent.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/checks/pipeline.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/composite_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/enricher.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/event_types.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/logfire_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/metrics.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/middleware_events.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/events/otel_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/initialization/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/responses/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/responses/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/routing/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/routing/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/routing/resolver.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/validation/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/validation/context.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/validation/validator.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/access_control.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/advanced.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/authentication.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/base.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/behavioral.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/content_filtering.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/decorators/rate_limiting.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/detection_engine/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/detection_engine/compiler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/detection_engine/monitor.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/detection_engine/semantic.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/detection_result.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/behavior_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/cloud_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/cloud_ip_stores.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/cors_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/dynamic_rule_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/ratelimit_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/redis_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/security_headers_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/handlers/suspatterns_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/agent_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/cloud_ip_store_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/geo_ip_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/middleware_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/redis_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/request_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/protocols/response_protocol.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/py.typed +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/scripts/__init__.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/scripts/rate_lua.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/utils.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core/utils.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core.egg-info/dependency_links.txt +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core.egg-info/requires.txt +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/guard_core.egg-info/top_level.txt +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/setup.cfg +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/setup.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_behavior_rule_ban_duration.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_check_log_muting.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_cloud_ip_refresh_on_demand.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_cloud_ip_stores.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_compiler_cache.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_composite_enricher.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_composite_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_composite_handler_extra.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_cors_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_detection_categories.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_detection_exclusion_integration.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_detection_result.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_detection_result_propagation.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_dynamic_rule_atomicity.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher_behavior_correlation.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher_end_to_end.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher_identity.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher_logfire_integration.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher_otel_integration.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher_rule_correlation.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_enricher_threat_score.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_event_bus_filtering.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_event_types.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_handler_edge_cases.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_handler_initializer.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_handler_initializer_enricher.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_handler_initializer_factories.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_handler_initializer_lazy_init.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_handlers_integration.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_ipban_cidr.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_ipban_ttl.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_ipinfo_lifecycle.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_logfire_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_logfire_handler_metric.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_otel_handler.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_otel_handler_resource_attrs.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_pipeline_fail_secure.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_preprocessor_attack_regions.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_preprocessor_encodings.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_suspicious_counts_per_type.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/tests/test_telemetry_integration.py +0 -0
- {guard_core-2.2.1 → guard_core-2.2.2}/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: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
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
|
|
@@ -65,7 +65,7 @@ Dynamic: license-file
|
|
|
65
65
|
___
|
|
66
66
|
|
|
67
67
|
<p align="center">
|
|
68
|
-
<strong>guard-core is the framework-agnostic security engine that powers the Guard ecosystem. It provides IP control, rate limiting,
|
|
68
|
+
<strong>guard-core is the framework-agnostic security engine that powers the Guard ecosystem. It provides IP control, rate limiting, signature-based attack-pattern detection, security headers, and threshold-based behavior tracking through a protocol-based architecture. Framework-specific adapters (fastapi-guard, flaskapi-guard, djapi-guard) consume this library.</strong>
|
|
69
69
|
</p>
|
|
70
70
|
|
|
71
71
|
<p align="center">
|
|
@@ -178,7 +178,7 @@ Features
|
|
|
178
178
|
- **HTTP Security Headers**: CSP, HSTS, X-Frame-Options, and OWASP best practices.
|
|
179
179
|
- **Cloud Provider IP Blocking**: Block requests from AWS, GCP, Azure IP ranges.
|
|
180
180
|
- **IP Geolocation**: Country-based access control via GeoIP databases.
|
|
181
|
-
- **
|
|
181
|
+
- **Threshold-Based Behavior Tracking**: Per-IP request counting, response-pattern matching, suspicious-frequency triggers (deterministic threshold matching, not learning-based).
|
|
182
182
|
- **Security Decorators**: Route-level security with composable decorator mixins.
|
|
183
183
|
- **Detection Engine**: Multi-layered threat detection with regex, semantic analysis, and performance monitoring.
|
|
184
184
|
- **Distributed State Management**: Redis integration for shared state across instances.
|
|
@@ -186,6 +186,18 @@ Features
|
|
|
186
186
|
|
|
187
187
|
___
|
|
188
188
|
|
|
189
|
+
How Detection Works
|
|
190
|
+
-------------------
|
|
191
|
+
|
|
192
|
+
1. Request inputs (query, headers, body) are decoded through up to 7 layers covering URL, HTML entities, base64, hex, Unicode escapes, and SQL comments.
|
|
193
|
+
2. Decoded content is matched against ~64 regex patterns across 16 attack categories, with patterns context-filtered to relevant input zones.
|
|
194
|
+
3. Matched payloads receive a multi-metric semantic score combining keyword overlap, Shannon entropy, encoding-layer count, and obfuscation indicators.
|
|
195
|
+
4. ReDoS protection enforces a 0.1s pattern-validation timeout and a 2-5s match timeout per pattern.
|
|
196
|
+
|
|
197
|
+
The engine is signature-based with multi-metric semantic scoring on top. It is not machine-learning-based and does not learn from traffic.
|
|
198
|
+
|
|
199
|
+
___
|
|
200
|
+
|
|
189
201
|
Installation
|
|
190
202
|
------------
|
|
191
203
|
|
|
@@ -304,7 +316,7 @@ Multi-layered threat detection:
|
|
|
304
316
|
- **PatternCompiler**: ReDoS-safe regex compilation with LRU caching and timeout protection.
|
|
305
317
|
- **ContentPreprocessor**: Unicode normalization, encoding detection, attack-region-aware truncation.
|
|
306
318
|
- **SemanticAnalyzer**: Attack probability scoring, entropy analysis, obfuscation detection.
|
|
307
|
-
- **PerformanceMonitor**:
|
|
319
|
+
- **PerformanceMonitor**: Slow-pattern detection via execution-time statistics (mean/stddev thresholds), not anomaly learning.
|
|
308
320
|
|
|
309
321
|
See the [Detection Engine Internals](https://rennf93.github.io/guard-core/latest/internals/detection-engine/) for details.
|
|
310
322
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
___
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<strong>guard-core is the framework-agnostic security engine that powers the Guard ecosystem. It provides IP control, rate limiting,
|
|
10
|
+
<strong>guard-core is the framework-agnostic security engine that powers the Guard ecosystem. It provides IP control, rate limiting, signature-based attack-pattern detection, security headers, and threshold-based behavior tracking through a protocol-based architecture. Framework-specific adapters (fastapi-guard, flaskapi-guard, djapi-guard) consume this library.</strong>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
@@ -120,7 +120,7 @@ Features
|
|
|
120
120
|
- **HTTP Security Headers**: CSP, HSTS, X-Frame-Options, and OWASP best practices.
|
|
121
121
|
- **Cloud Provider IP Blocking**: Block requests from AWS, GCP, Azure IP ranges.
|
|
122
122
|
- **IP Geolocation**: Country-based access control via GeoIP databases.
|
|
123
|
-
- **
|
|
123
|
+
- **Threshold-Based Behavior Tracking**: Per-IP request counting, response-pattern matching, suspicious-frequency triggers (deterministic threshold matching, not learning-based).
|
|
124
124
|
- **Security Decorators**: Route-level security with composable decorator mixins.
|
|
125
125
|
- **Detection Engine**: Multi-layered threat detection with regex, semantic analysis, and performance monitoring.
|
|
126
126
|
- **Distributed State Management**: Redis integration for shared state across instances.
|
|
@@ -128,6 +128,18 @@ Features
|
|
|
128
128
|
|
|
129
129
|
___
|
|
130
130
|
|
|
131
|
+
How Detection Works
|
|
132
|
+
-------------------
|
|
133
|
+
|
|
134
|
+
1. Request inputs (query, headers, body) are decoded through up to 7 layers covering URL, HTML entities, base64, hex, Unicode escapes, and SQL comments.
|
|
135
|
+
2. Decoded content is matched against ~64 regex patterns across 16 attack categories, with patterns context-filtered to relevant input zones.
|
|
136
|
+
3. Matched payloads receive a multi-metric semantic score combining keyword overlap, Shannon entropy, encoding-layer count, and obfuscation indicators.
|
|
137
|
+
4. ReDoS protection enforces a 0.1s pattern-validation timeout and a 2-5s match timeout per pattern.
|
|
138
|
+
|
|
139
|
+
The engine is signature-based with multi-metric semantic scoring on top. It is not machine-learning-based and does not learn from traffic.
|
|
140
|
+
|
|
141
|
+
___
|
|
142
|
+
|
|
131
143
|
Installation
|
|
132
144
|
------------
|
|
133
145
|
|
|
@@ -246,7 +258,7 @@ Multi-layered threat detection:
|
|
|
246
258
|
- **PatternCompiler**: ReDoS-safe regex compilation with LRU caching and timeout protection.
|
|
247
259
|
- **ContentPreprocessor**: Unicode normalization, encoding detection, attack-region-aware truncation.
|
|
248
260
|
- **SemanticAnalyzer**: Attack probability scoring, entropy analysis, obfuscation detection.
|
|
249
|
-
- **PerformanceMonitor**:
|
|
261
|
+
- **PerformanceMonitor**: Slow-pattern detection via execution-time statistics (mean/stddev thresholds), not anomaly learning.
|
|
250
262
|
|
|
251
263
|
See the [Detection Engine Internals](https://rennf93.github.io/guard-core/latest/internals/detection-engine/) for details.
|
|
252
264
|
|
|
@@ -184,7 +184,14 @@ class HandlerInitializer:
|
|
|
184
184
|
await self.geo_ip_handler.initialize_agent(telemetry)
|
|
185
185
|
|
|
186
186
|
async def initialize_dynamic_rule_manager(self) -> None:
|
|
187
|
-
if not
|
|
187
|
+
if not self.config.enable_dynamic_rules:
|
|
188
|
+
return
|
|
189
|
+
if not self.agent_handler:
|
|
190
|
+
self.logger.warning(
|
|
191
|
+
"Dynamic rules enabled but agent unavailable; falling back to "
|
|
192
|
+
"static config. Dashboard rule updates will not propagate until "
|
|
193
|
+
"agent connectivity is restored."
|
|
194
|
+
)
|
|
188
195
|
return
|
|
189
196
|
|
|
190
197
|
from guard_core.handlers.dynamic_rule_handler import DynamicRuleManager
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from collections.abc import Awaitable, Callable
|
|
2
3
|
|
|
3
4
|
from guard_core.core.responses.context import ResponseContext
|
|
@@ -12,6 +13,7 @@ from guard_core.utils import extract_client_ip
|
|
|
12
13
|
class ErrorResponseFactory:
|
|
13
14
|
def __init__(self, context: ResponseContext):
|
|
14
15
|
self.context = context
|
|
16
|
+
self.logger = logging.getLogger("guard_core.core.responses.factory")
|
|
15
17
|
|
|
16
18
|
async def create_error_response(
|
|
17
19
|
self, status_code: int, default_message: str
|
|
@@ -61,10 +63,16 @@ class ErrorResponseFactory:
|
|
|
61
63
|
|
|
62
64
|
async def apply_modifier(self, response: GuardResponse) -> GuardResponse:
|
|
63
65
|
if self.context.config.custom_response_modifier:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
try:
|
|
67
|
+
result: GuardResponse = (
|
|
68
|
+
await self.context.config.custom_response_modifier(response)
|
|
69
|
+
)
|
|
70
|
+
return result
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
self.logger.exception(
|
|
73
|
+
"custom_response_modifier raised %s; returning unmodified response",
|
|
74
|
+
exc,
|
|
75
|
+
)
|
|
68
76
|
return response
|
|
69
77
|
|
|
70
78
|
async def process_response(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ipaddress
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from datetime import datetime, timezone
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
@@ -9,12 +10,31 @@ from cachetools import TTLCache
|
|
|
9
10
|
_Network = ipaddress.IPv4Network | ipaddress.IPv6Network
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
class _ObservableTTLCache(TTLCache):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
maxsize: int,
|
|
17
|
+
ttl: float,
|
|
18
|
+
on_evict: Callable[[], None],
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(maxsize=maxsize, ttl=ttl)
|
|
21
|
+
self._on_evict = on_evict
|
|
22
|
+
|
|
23
|
+
def popitem(self) -> tuple[Any, Any]:
|
|
24
|
+
item = super().popitem()
|
|
25
|
+
self._on_evict()
|
|
26
|
+
return item
|
|
27
|
+
|
|
28
|
+
|
|
12
29
|
class IPBanManager:
|
|
13
30
|
LOCAL_CACHE_TTL_CAP_SECONDS = 3600
|
|
31
|
+
_EVICTION_LOG_EVERY = 100
|
|
14
32
|
|
|
15
33
|
_instance: "IPBanManager | None" = None
|
|
16
34
|
banned_ips: TTLCache
|
|
17
35
|
banned_networks: list[tuple[_Network, float]]
|
|
36
|
+
evictions_count: int
|
|
37
|
+
logger: logging.Logger
|
|
18
38
|
config: Any = None
|
|
19
39
|
redis_handler: Any = None
|
|
20
40
|
agent_handler: Any = None
|
|
@@ -22,14 +42,26 @@ class IPBanManager:
|
|
|
22
42
|
def __new__(cls: type["IPBanManager"]) -> "IPBanManager":
|
|
23
43
|
if cls._instance is None:
|
|
24
44
|
cls._instance = super().__new__(cls)
|
|
25
|
-
cls._instance.
|
|
26
|
-
|
|
45
|
+
cls._instance.evictions_count = 0
|
|
46
|
+
cls._instance.logger = logging.getLogger("guard_core.handlers.ipban")
|
|
47
|
+
cls._instance.banned_ips = _ObservableTTLCache(
|
|
48
|
+
maxsize=10000,
|
|
49
|
+
ttl=cls.LOCAL_CACHE_TTL_CAP_SECONDS,
|
|
50
|
+
on_evict=cls._instance._on_eviction,
|
|
27
51
|
)
|
|
28
52
|
cls._instance.banned_networks = []
|
|
29
53
|
cls._instance.redis_handler = None
|
|
30
54
|
cls._instance.agent_handler = None
|
|
31
55
|
return cls._instance
|
|
32
56
|
|
|
57
|
+
def _on_eviction(self) -> None:
|
|
58
|
+
self.evictions_count += 1
|
|
59
|
+
if self.evictions_count % self._EVICTION_LOG_EVERY == 0:
|
|
60
|
+
self.logger.warning(
|
|
61
|
+
"IP ban cache full; %d entries evicted (silent overflow)",
|
|
62
|
+
self.evictions_count,
|
|
63
|
+
)
|
|
64
|
+
|
|
33
65
|
async def initialize_redis(self, redis_handler: Any) -> None:
|
|
34
66
|
self.redis_handler = redis_handler
|
|
35
67
|
|
|
@@ -115,9 +147,7 @@ class IPBanManager:
|
|
|
115
147
|
)
|
|
116
148
|
await self.agent_handler.send_event(event)
|
|
117
149
|
except Exception as e:
|
|
118
|
-
|
|
119
|
-
f"Failed to send ban event to agent: {e}"
|
|
120
|
-
)
|
|
150
|
+
self.logger.error("Failed to send ban event to agent: %s", e)
|
|
121
151
|
|
|
122
152
|
async def unban_ip(self, ip: str) -> None:
|
|
123
153
|
if ip in self.banned_ips:
|
|
@@ -145,9 +175,7 @@ class IPBanManager:
|
|
|
145
175
|
)
|
|
146
176
|
await self.agent_handler.send_event(event)
|
|
147
177
|
except Exception as e:
|
|
148
|
-
|
|
149
|
-
f"Failed to send unban event to agent: {e}"
|
|
150
|
-
)
|
|
178
|
+
self.logger.error("Failed to send unban event to agent: %s", e)
|
|
151
179
|
|
|
152
180
|
def _check_network_cache(
|
|
153
181
|
self, addr: ipaddress.IPv4Address | ipaddress.IPv6Address, now: float
|
|
@@ -158,7 +158,10 @@ class IPInfoManager:
|
|
|
158
158
|
|
|
159
159
|
def get_country(self, ip: str) -> str | None:
|
|
160
160
|
if not self.reader:
|
|
161
|
-
|
|
161
|
+
self.logger.warning(
|
|
162
|
+
"Geo-IP reader uninitialized; returning None for %s", ip
|
|
163
|
+
)
|
|
164
|
+
return None
|
|
162
165
|
|
|
163
166
|
try:
|
|
164
167
|
result = self.reader.get(ip)
|
{guard_core-2.2.1 → guard_core-2.2.2}/guard_core/sync/core/initialization/handler_initializer.py
RENAMED
|
@@ -190,7 +190,14 @@ class HandlerInitializer:
|
|
|
190
190
|
self.geo_ip_handler.initialize_agent(telemetry)
|
|
191
191
|
|
|
192
192
|
def initialize_dynamic_rule_manager(self) -> None:
|
|
193
|
-
if not
|
|
193
|
+
if not self.config.enable_dynamic_rules:
|
|
194
|
+
return
|
|
195
|
+
if not self.agent_handler:
|
|
196
|
+
self.logger.warning(
|
|
197
|
+
"Dynamic rules enabled but agent unavailable; falling back to "
|
|
198
|
+
"static config. Dashboard rule updates will not propagate until "
|
|
199
|
+
"agent connectivity is restored."
|
|
200
|
+
)
|
|
194
201
|
return
|
|
195
202
|
|
|
196
203
|
from guard_core.sync.handlers.dynamic_rule_handler import DynamicRuleManager
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from collections.abc import Callable
|
|
2
3
|
|
|
3
4
|
from guard_core.protocols.response_protocol import GuardResponse
|
|
@@ -12,6 +13,7 @@ from guard_core.sync.utils import extract_client_ip
|
|
|
12
13
|
class ErrorResponseFactory:
|
|
13
14
|
def __init__(self, context: ResponseContext):
|
|
14
15
|
self.context = context
|
|
16
|
+
self.logger = logging.getLogger("guard_core.sync.core.responses.factory")
|
|
15
17
|
|
|
16
18
|
def create_error_response(
|
|
17
19
|
self, status_code: int, default_message: str
|
|
@@ -59,10 +61,16 @@ class ErrorResponseFactory:
|
|
|
59
61
|
|
|
60
62
|
def apply_modifier(self, response: GuardResponse) -> GuardResponse:
|
|
61
63
|
if self.context.config.custom_response_modifier:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
try:
|
|
65
|
+
result: GuardResponse = self.context.config.custom_response_modifier(
|
|
66
|
+
response
|
|
67
|
+
)
|
|
68
|
+
return result
|
|
69
|
+
except Exception as exc:
|
|
70
|
+
self.logger.exception(
|
|
71
|
+
"custom_response_modifier raised %s; returning unmodified response",
|
|
72
|
+
exc,
|
|
73
|
+
)
|
|
66
74
|
return response
|
|
67
75
|
|
|
68
76
|
def process_response(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ipaddress
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from datetime import datetime, timezone
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
@@ -9,12 +10,31 @@ from cachetools import TTLCache
|
|
|
9
10
|
_Network = ipaddress.IPv4Network | ipaddress.IPv6Network
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
class _ObservableTTLCache(TTLCache):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
maxsize: int,
|
|
17
|
+
ttl: float,
|
|
18
|
+
on_evict: Callable[[], None],
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(maxsize=maxsize, ttl=ttl)
|
|
21
|
+
self._on_evict = on_evict
|
|
22
|
+
|
|
23
|
+
def popitem(self) -> tuple[Any, Any]:
|
|
24
|
+
item = super().popitem()
|
|
25
|
+
self._on_evict()
|
|
26
|
+
return item
|
|
27
|
+
|
|
28
|
+
|
|
12
29
|
class IPBanManager:
|
|
13
30
|
LOCAL_CACHE_TTL_CAP_SECONDS = 3600
|
|
31
|
+
_EVICTION_LOG_EVERY = 100
|
|
14
32
|
|
|
15
33
|
_instance: "IPBanManager | None" = None
|
|
16
34
|
banned_ips: TTLCache
|
|
17
35
|
banned_networks: list[tuple[_Network, float]]
|
|
36
|
+
evictions_count: int
|
|
37
|
+
logger: logging.Logger
|
|
18
38
|
config: Any = None
|
|
19
39
|
redis_handler: Any = None
|
|
20
40
|
agent_handler: Any = None
|
|
@@ -22,14 +42,26 @@ class IPBanManager:
|
|
|
22
42
|
def __new__(cls: type["IPBanManager"]) -> "IPBanManager":
|
|
23
43
|
if cls._instance is None:
|
|
24
44
|
cls._instance = super().__new__(cls)
|
|
25
|
-
cls._instance.
|
|
26
|
-
|
|
45
|
+
cls._instance.evictions_count = 0
|
|
46
|
+
cls._instance.logger = logging.getLogger("guard_core.sync.handlers.ipban")
|
|
47
|
+
cls._instance.banned_ips = _ObservableTTLCache(
|
|
48
|
+
maxsize=10000,
|
|
49
|
+
ttl=cls.LOCAL_CACHE_TTL_CAP_SECONDS,
|
|
50
|
+
on_evict=cls._instance._on_eviction,
|
|
27
51
|
)
|
|
28
52
|
cls._instance.banned_networks = []
|
|
29
53
|
cls._instance.redis_handler = None
|
|
30
54
|
cls._instance.agent_handler = None
|
|
31
55
|
return cls._instance
|
|
32
56
|
|
|
57
|
+
def _on_eviction(self) -> None:
|
|
58
|
+
self.evictions_count += 1
|
|
59
|
+
if self.evictions_count % self._EVICTION_LOG_EVERY == 0:
|
|
60
|
+
self.logger.warning(
|
|
61
|
+
"IP ban cache full; %d entries evicted (silent overflow)",
|
|
62
|
+
self.evictions_count,
|
|
63
|
+
)
|
|
64
|
+
|
|
33
65
|
def initialize_redis(self, redis_handler: Any) -> None:
|
|
34
66
|
self.redis_handler = redis_handler
|
|
35
67
|
|
|
@@ -113,9 +145,7 @@ class IPBanManager:
|
|
|
113
145
|
)
|
|
114
146
|
self.agent_handler.send_event(event)
|
|
115
147
|
except Exception as e:
|
|
116
|
-
|
|
117
|
-
f"Failed to send ban event to agent: {e}"
|
|
118
|
-
)
|
|
148
|
+
self.logger.error("Failed to send ban event to agent: %s", e)
|
|
119
149
|
|
|
120
150
|
def unban_ip(self, ip: str) -> None:
|
|
121
151
|
if ip in self.banned_ips:
|
|
@@ -143,9 +173,7 @@ class IPBanManager:
|
|
|
143
173
|
)
|
|
144
174
|
self.agent_handler.send_event(event)
|
|
145
175
|
except Exception as e:
|
|
146
|
-
|
|
147
|
-
f"Failed to send unban event to agent: {e}"
|
|
148
|
-
)
|
|
176
|
+
self.logger.error("Failed to send unban event to agent: %s", e)
|
|
149
177
|
|
|
150
178
|
def _check_network_cache(
|
|
151
179
|
self, addr: ipaddress.IPv4Address | ipaddress.IPv6Address, now: float
|
|
@@ -157,7 +157,10 @@ class IPInfoManager:
|
|
|
157
157
|
|
|
158
158
|
def get_country(self, ip: str) -> str | None:
|
|
159
159
|
if not self.reader:
|
|
160
|
-
|
|
160
|
+
self.logger.warning(
|
|
161
|
+
"Geo-IP reader uninitialized; returning None for %s", ip
|
|
162
|
+
)
|
|
163
|
+
return None
|
|
161
164
|
|
|
162
165
|
try:
|
|
163
166
|
result = self.reader.get(ip)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: guard-core
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
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
|
|
@@ -65,7 +65,7 @@ Dynamic: license-file
|
|
|
65
65
|
___
|
|
66
66
|
|
|
67
67
|
<p align="center">
|
|
68
|
-
<strong>guard-core is the framework-agnostic security engine that powers the Guard ecosystem. It provides IP control, rate limiting,
|
|
68
|
+
<strong>guard-core is the framework-agnostic security engine that powers the Guard ecosystem. It provides IP control, rate limiting, signature-based attack-pattern detection, security headers, and threshold-based behavior tracking through a protocol-based architecture. Framework-specific adapters (fastapi-guard, flaskapi-guard, djapi-guard) consume this library.</strong>
|
|
69
69
|
</p>
|
|
70
70
|
|
|
71
71
|
<p align="center">
|
|
@@ -178,7 +178,7 @@ Features
|
|
|
178
178
|
- **HTTP Security Headers**: CSP, HSTS, X-Frame-Options, and OWASP best practices.
|
|
179
179
|
- **Cloud Provider IP Blocking**: Block requests from AWS, GCP, Azure IP ranges.
|
|
180
180
|
- **IP Geolocation**: Country-based access control via GeoIP databases.
|
|
181
|
-
- **
|
|
181
|
+
- **Threshold-Based Behavior Tracking**: Per-IP request counting, response-pattern matching, suspicious-frequency triggers (deterministic threshold matching, not learning-based).
|
|
182
182
|
- **Security Decorators**: Route-level security with composable decorator mixins.
|
|
183
183
|
- **Detection Engine**: Multi-layered threat detection with regex, semantic analysis, and performance monitoring.
|
|
184
184
|
- **Distributed State Management**: Redis integration for shared state across instances.
|
|
@@ -186,6 +186,18 @@ Features
|
|
|
186
186
|
|
|
187
187
|
___
|
|
188
188
|
|
|
189
|
+
How Detection Works
|
|
190
|
+
-------------------
|
|
191
|
+
|
|
192
|
+
1. Request inputs (query, headers, body) are decoded through up to 7 layers covering URL, HTML entities, base64, hex, Unicode escapes, and SQL comments.
|
|
193
|
+
2. Decoded content is matched against ~64 regex patterns across 16 attack categories, with patterns context-filtered to relevant input zones.
|
|
194
|
+
3. Matched payloads receive a multi-metric semantic score combining keyword overlap, Shannon entropy, encoding-layer count, and obfuscation indicators.
|
|
195
|
+
4. ReDoS protection enforces a 0.1s pattern-validation timeout and a 2-5s match timeout per pattern.
|
|
196
|
+
|
|
197
|
+
The engine is signature-based with multi-metric semantic scoring on top. It is not machine-learning-based and does not learn from traffic.
|
|
198
|
+
|
|
199
|
+
___
|
|
200
|
+
|
|
189
201
|
Installation
|
|
190
202
|
------------
|
|
191
203
|
|
|
@@ -304,7 +316,7 @@ Multi-layered threat detection:
|
|
|
304
316
|
- **PatternCompiler**: ReDoS-safe regex compilation with LRU caching and timeout protection.
|
|
305
317
|
- **ContentPreprocessor**: Unicode normalization, encoding detection, attack-region-aware truncation.
|
|
306
318
|
- **SemanticAnalyzer**: Attack probability scoring, entropy analysis, obfuscation detection.
|
|
307
|
-
- **PerformanceMonitor**:
|
|
319
|
+
- **PerformanceMonitor**: Slow-pattern detection via execution-time statistics (mean/stddev thresholds), not anomaly learning.
|
|
308
320
|
|
|
309
321
|
See the [Detection Engine Internals](https://rennf93.github.io/guard-core/latest/internals/detection-engine/) for details.
|
|
310
322
|
|
|
@@ -215,6 +215,8 @@ tests/test_handler_initializer_factories.py
|
|
|
215
215
|
tests/test_handler_initializer_lazy_init.py
|
|
216
216
|
tests/test_handlers_integration.py
|
|
217
217
|
tests/test_ipban_cidr.py
|
|
218
|
+
tests/test_ipban_eviction.py
|
|
219
|
+
tests/test_ipban_lifecycle.py
|
|
218
220
|
tests/test_ipban_ttl.py
|
|
219
221
|
tests/test_ipinfo_lifecycle.py
|
|
220
222
|
tests/test_logfire_handler.py
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from collections.abc import Generator
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from guard_core.handlers.ipban_handler import (
|
|
8
|
+
IPBanManager,
|
|
9
|
+
_ObservableTTLCache,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(autouse=True)
|
|
14
|
+
def reset_singleton() -> Generator[None, None, None]:
|
|
15
|
+
IPBanManager._instance = None
|
|
16
|
+
yield
|
|
17
|
+
IPBanManager._instance = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _swap_in_small_cache(manager: IPBanManager, maxsize: int) -> None:
|
|
21
|
+
manager.banned_ips = _ObservableTTLCache(
|
|
22
|
+
maxsize=maxsize,
|
|
23
|
+
ttl=manager.LOCAL_CACHE_TTL_CAP_SECONDS,
|
|
24
|
+
on_evict=manager._on_eviction,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.asyncio
|
|
29
|
+
async def test_banned_ips_overflow_increments_eviction_counter_and_logs_at_threshold(
|
|
30
|
+
caplog: pytest.LogCaptureFixture,
|
|
31
|
+
) -> None:
|
|
32
|
+
manager = IPBanManager()
|
|
33
|
+
manager.redis_handler = None
|
|
34
|
+
_swap_in_small_cache(manager, maxsize=2)
|
|
35
|
+
|
|
36
|
+
caplog.set_level(logging.WARNING, logger="guard_core.handlers.ipban")
|
|
37
|
+
|
|
38
|
+
await manager.ban_ip("10.0.0.1", duration=300, reason="t")
|
|
39
|
+
await manager.ban_ip("10.0.0.2", duration=300, reason="t")
|
|
40
|
+
await manager.ban_ip("10.0.0.3", duration=300, reason="t")
|
|
41
|
+
|
|
42
|
+
assert manager.evictions_count == 1
|
|
43
|
+
assert not [r for r in caplog.records if r.levelno == logging.WARNING]
|
|
44
|
+
|
|
45
|
+
for i in range(4, 103):
|
|
46
|
+
await manager.ban_ip(f"10.0.{i // 256}.{i % 256}", duration=300, reason="t")
|
|
47
|
+
|
|
48
|
+
assert manager.evictions_count == 100
|
|
49
|
+
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
|
|
50
|
+
assert len(warnings) == 1
|
|
51
|
+
assert "100" in warnings[0].getMessage()
|
|
52
|
+
assert "silent overflow" in warnings[0].getMessage()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.asyncio
|
|
56
|
+
async def test_ttl_expiry_does_not_increment_eviction_counter() -> None:
|
|
57
|
+
manager = IPBanManager()
|
|
58
|
+
manager.redis_handler = None
|
|
59
|
+
manager.banned_ips = _ObservableTTLCache(
|
|
60
|
+
maxsize=10, ttl=1, on_evict=manager._on_eviction
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
await manager.ban_ip("10.0.0.1", duration=1, reason="t")
|
|
64
|
+
await manager.ban_ip("10.0.0.2", duration=1, reason="t")
|
|
65
|
+
|
|
66
|
+
time.sleep(1.1)
|
|
67
|
+
|
|
68
|
+
await manager.ban_ip("10.0.0.3", duration=1, reason="t")
|
|
69
|
+
|
|
70
|
+
assert manager.evictions_count == 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_eviction_counter_persists_across_singleton_calls() -> None:
|
|
75
|
+
manager = IPBanManager()
|
|
76
|
+
manager.redis_handler = None
|
|
77
|
+
_swap_in_small_cache(manager, maxsize=1)
|
|
78
|
+
|
|
79
|
+
await manager.ban_ip("10.0.0.1", duration=300, reason="t")
|
|
80
|
+
await manager.ban_ip("10.0.0.2", duration=300, reason="t")
|
|
81
|
+
assert manager.evictions_count == 1
|
|
82
|
+
|
|
83
|
+
second = IPBanManager()
|
|
84
|
+
assert second is manager
|
|
85
|
+
assert second.evictions_count == 1
|