guard-core 2.2.0__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.
Files changed (233) hide show
  1. {guard_core-2.2.0/guard_core.egg-info → guard_core-2.2.2}/PKG-INFO +16 -4
  2. {guard_core-2.2.0 → guard_core-2.2.2}/README.md +15 -3
  3. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/initialization/handler_initializer.py +8 -1
  4. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/responses/factory.py +12 -4
  5. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/detection_engine/preprocessor.py +1 -1
  6. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/ipban_handler.py +36 -8
  7. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/ipinfo_handler.py +4 -1
  8. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/redis_handler.py +7 -4
  9. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/initialization/handler_initializer.py +8 -1
  10. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/responses/factory.py +12 -4
  11. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/detection_engine/preprocessor.py +1 -1
  12. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/ipban_handler.py +36 -8
  13. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/ipinfo_handler.py +4 -1
  14. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/redis_handler.py +7 -4
  15. {guard_core-2.2.0 → guard_core-2.2.2/guard_core.egg-info}/PKG-INFO +16 -4
  16. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core.egg-info/SOURCES.txt +2 -0
  17. {guard_core-2.2.0 → guard_core-2.2.2}/pyproject.toml +1 -1
  18. guard_core-2.2.2/tests/test_ipban_eviction.py +85 -0
  19. guard_core-2.2.2/tests/test_ipban_lifecycle.py +246 -0
  20. {guard_core-2.2.0 → guard_core-2.2.2}/LICENSE +0 -0
  21. {guard_core-2.2.0 → guard_core-2.2.2}/MANIFEST.in +0 -0
  22. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/__init__.py +0 -0
  23. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/__init__.py +0 -0
  24. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/behavioral/__init__.py +0 -0
  25. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/behavioral/context.py +0 -0
  26. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/behavioral/processor.py +0 -0
  27. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/bypass/__init__.py +0 -0
  28. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/bypass/context.py +0 -0
  29. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/bypass/handler.py +0 -0
  30. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/__init__.py +0 -0
  31. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/base.py +0 -0
  32. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/helpers.py +0 -0
  33. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/__init__.py +0 -0
  34. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/authentication.py +0 -0
  35. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/cloud_ip_refresh.py +0 -0
  36. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/cloud_provider.py +0 -0
  37. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/custom_request.py +0 -0
  38. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/custom_validators.py +0 -0
  39. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/emergency_mode.py +0 -0
  40. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/https_enforcement.py +0 -0
  41. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/ip_security.py +0 -0
  42. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/rate_limit.py +0 -0
  43. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/referrer.py +0 -0
  44. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/request_logging.py +0 -0
  45. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/request_size_content.py +0 -0
  46. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/required_headers.py +0 -0
  47. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/route_config.py +0 -0
  48. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/suspicious_activity.py +0 -0
  49. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/time_window.py +0 -0
  50. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/implementations/user_agent.py +0 -0
  51. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/checks/pipeline.py +0 -0
  52. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/__init__.py +0 -0
  53. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/composite_handler.py +0 -0
  54. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/enricher.py +0 -0
  55. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/event_types.py +0 -0
  56. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/logfire_handler.py +0 -0
  57. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/metrics.py +0 -0
  58. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/middleware_events.py +0 -0
  59. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/events/otel_handler.py +0 -0
  60. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/initialization/__init__.py +0 -0
  61. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/responses/__init__.py +0 -0
  62. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/responses/context.py +0 -0
  63. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/routing/__init__.py +0 -0
  64. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/routing/context.py +0 -0
  65. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/routing/resolver.py +0 -0
  66. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/validation/__init__.py +0 -0
  67. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/validation/context.py +0 -0
  68. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/core/validation/validator.py +0 -0
  69. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/__init__.py +0 -0
  70. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/access_control.py +0 -0
  71. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/advanced.py +0 -0
  72. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/authentication.py +0 -0
  73. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/base.py +0 -0
  74. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/behavioral.py +0 -0
  75. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/content_filtering.py +0 -0
  76. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/decorators/rate_limiting.py +0 -0
  77. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/detection_engine/__init__.py +0 -0
  78. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/detection_engine/compiler.py +0 -0
  79. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/detection_engine/monitor.py +0 -0
  80. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/detection_engine/semantic.py +0 -0
  81. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/detection_result.py +0 -0
  82. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/exceptions.py +0 -0
  83. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/__init__.py +0 -0
  84. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/behavior_handler.py +0 -0
  85. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/cloud_handler.py +0 -0
  86. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/cloud_ip_stores.py +0 -0
  87. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/cors_handler.py +0 -0
  88. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/dynamic_rule_handler.py +0 -0
  89. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/ratelimit_handler.py +0 -0
  90. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/security_headers_handler.py +0 -0
  91. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/handlers/suspatterns_handler.py +0 -0
  92. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/models.py +0 -0
  93. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/__init__.py +0 -0
  94. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/agent_protocol.py +0 -0
  95. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/cloud_ip_store_protocol.py +0 -0
  96. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/geo_ip_protocol.py +0 -0
  97. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/middleware_protocol.py +0 -0
  98. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/redis_protocol.py +0 -0
  99. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/request_protocol.py +0 -0
  100. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/protocols/response_protocol.py +0 -0
  101. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/py.typed +0 -0
  102. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/scripts/__init__.py +0 -0
  103. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/scripts/rate_lua.py +0 -0
  104. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/__init__.py +0 -0
  105. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/__init__.py +0 -0
  106. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/behavioral/__init__.py +0 -0
  107. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/behavioral/context.py +0 -0
  108. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/behavioral/processor.py +0 -0
  109. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/bypass/__init__.py +0 -0
  110. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/bypass/context.py +0 -0
  111. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/bypass/handler.py +0 -0
  112. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/__init__.py +0 -0
  113. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/base.py +0 -0
  114. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/helpers.py +0 -0
  115. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/__init__.py +0 -0
  116. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/authentication.py +0 -0
  117. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/cloud_ip_refresh.py +0 -0
  118. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/cloud_provider.py +0 -0
  119. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/custom_request.py +0 -0
  120. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/custom_validators.py +0 -0
  121. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/emergency_mode.py +0 -0
  122. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/https_enforcement.py +0 -0
  123. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/ip_security.py +0 -0
  124. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/rate_limit.py +0 -0
  125. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/referrer.py +0 -0
  126. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/request_logging.py +0 -0
  127. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/request_size_content.py +0 -0
  128. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/required_headers.py +0 -0
  129. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/route_config.py +0 -0
  130. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/suspicious_activity.py +0 -0
  131. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/time_window.py +0 -0
  132. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/implementations/user_agent.py +0 -0
  133. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/checks/pipeline.py +0 -0
  134. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/__init__.py +0 -0
  135. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/composite_handler.py +0 -0
  136. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/enricher.py +0 -0
  137. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/event_types.py +0 -0
  138. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/logfire_handler.py +0 -0
  139. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/metrics.py +0 -0
  140. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/middleware_events.py +0 -0
  141. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/events/otel_handler.py +0 -0
  142. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/initialization/__init__.py +0 -0
  143. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/responses/__init__.py +0 -0
  144. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/responses/context.py +0 -0
  145. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/routing/__init__.py +0 -0
  146. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/routing/context.py +0 -0
  147. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/routing/resolver.py +0 -0
  148. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/validation/__init__.py +0 -0
  149. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/validation/context.py +0 -0
  150. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/core/validation/validator.py +0 -0
  151. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/__init__.py +0 -0
  152. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/access_control.py +0 -0
  153. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/advanced.py +0 -0
  154. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/authentication.py +0 -0
  155. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/base.py +0 -0
  156. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/behavioral.py +0 -0
  157. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/content_filtering.py +0 -0
  158. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/decorators/rate_limiting.py +0 -0
  159. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/detection_engine/__init__.py +0 -0
  160. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/detection_engine/compiler.py +0 -0
  161. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/detection_engine/monitor.py +0 -0
  162. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/detection_engine/semantic.py +0 -0
  163. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/detection_result.py +0 -0
  164. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/__init__.py +0 -0
  165. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/behavior_handler.py +0 -0
  166. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/cloud_handler.py +0 -0
  167. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/cloud_ip_stores.py +0 -0
  168. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/cors_handler.py +0 -0
  169. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/dynamic_rule_handler.py +0 -0
  170. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/ratelimit_handler.py +0 -0
  171. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/security_headers_handler.py +0 -0
  172. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/handlers/suspatterns_handler.py +0 -0
  173. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/__init__.py +0 -0
  174. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/agent_protocol.py +0 -0
  175. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/cloud_ip_store_protocol.py +0 -0
  176. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/geo_ip_protocol.py +0 -0
  177. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/middleware_protocol.py +0 -0
  178. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/redis_protocol.py +0 -0
  179. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/request_protocol.py +0 -0
  180. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/protocols/response_protocol.py +0 -0
  181. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/py.typed +0 -0
  182. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/scripts/__init__.py +0 -0
  183. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/scripts/rate_lua.py +0 -0
  184. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/sync/utils.py +0 -0
  185. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core/utils.py +0 -0
  186. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core.egg-info/dependency_links.txt +0 -0
  187. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core.egg-info/requires.txt +0 -0
  188. {guard_core-2.2.0 → guard_core-2.2.2}/guard_core.egg-info/top_level.txt +0 -0
  189. {guard_core-2.2.0 → guard_core-2.2.2}/setup.cfg +0 -0
  190. {guard_core-2.2.0 → guard_core-2.2.2}/setup.py +0 -0
  191. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_behavior_rule_ban_duration.py +0 -0
  192. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_check_log_muting.py +0 -0
  193. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_cloud_ip_refresh_on_demand.py +0 -0
  194. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_cloud_ip_stores.py +0 -0
  195. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_compiler_cache.py +0 -0
  196. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_composite_enricher.py +0 -0
  197. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_composite_handler.py +0 -0
  198. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_composite_handler_extra.py +0 -0
  199. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_cors_handler.py +0 -0
  200. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_detection_categories.py +0 -0
  201. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_detection_exclusion_integration.py +0 -0
  202. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_detection_result.py +0 -0
  203. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_detection_result_propagation.py +0 -0
  204. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_dynamic_rule_atomicity.py +0 -0
  205. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher.py +0 -0
  206. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher_behavior_correlation.py +0 -0
  207. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher_end_to_end.py +0 -0
  208. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher_identity.py +0 -0
  209. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher_logfire_integration.py +0 -0
  210. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher_otel_integration.py +0 -0
  211. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher_rule_correlation.py +0 -0
  212. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_enricher_threat_score.py +0 -0
  213. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_event_bus_filtering.py +0 -0
  214. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_event_types.py +0 -0
  215. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_handler_edge_cases.py +0 -0
  216. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_handler_initializer.py +0 -0
  217. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_handler_initializer_enricher.py +0 -0
  218. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_handler_initializer_factories.py +0 -0
  219. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_handler_initializer_lazy_init.py +0 -0
  220. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_handlers_integration.py +0 -0
  221. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_ipban_cidr.py +0 -0
  222. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_ipban_ttl.py +0 -0
  223. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_ipinfo_lifecycle.py +0 -0
  224. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_logfire_handler.py +0 -0
  225. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_logfire_handler_metric.py +0 -0
  226. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_otel_handler.py +0 -0
  227. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_otel_handler_resource_attrs.py +0 -0
  228. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_pipeline_fail_secure.py +0 -0
  229. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_preprocessor_attack_regions.py +0 -0
  230. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_preprocessor_encodings.py +0 -0
  231. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_suspicious_counts_per_type.py +0 -0
  232. {guard_core-2.2.0 → guard_core-2.2.2}/tests/test_telemetry_integration.py +0 -0
  233. {guard_core-2.2.0 → 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.0
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, penetration detection, security headers, and behavioral analysis through a protocol-based architecture. Framework-specific adapters (fastapi-guard, flaskapi-guard, djapi-guard) consume this library.</strong>
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
- - **Behavioral Analysis**: Usage monitoring, return pattern detection, frequency analysis.
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**: Anomaly detection, slow pattern tracking, statistical analysis.
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, penetration detection, security headers, and behavioral analysis through a protocol-based architecture. Framework-specific adapters (fastapi-guard, flaskapi-guard, djapi-guard) consume this library.</strong>
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
- - **Behavioral Analysis**: Usage monitoring, return pattern detection, frequency analysis.
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**: Anomaly detection, slow pattern tracking, statistical analysis.
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 (self.agent_handler and self.config.enable_dynamic_rules):
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
- result: GuardResponse = await self.context.config.custom_response_modifier(
65
- response
66
- )
67
- return result
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(
@@ -284,7 +284,7 @@ class ContentPreprocessor:
284
284
  return content
285
285
 
286
286
  async def decode_common_encodings(self, content: str) -> str:
287
- max_decode_iterations = 3
287
+ max_decode_iterations = 7
288
288
  iterations = 0
289
289
 
290
290
  while iterations < max_decode_iterations:
@@ -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.banned_ips = TTLCache(
26
- maxsize=10000, ttl=cls.LOCAL_CACHE_TTL_CAP_SECONDS
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
- logging.getLogger("guard_core.handlers.ipban").error(
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
- logging.getLogger("guard_core.handlers.ipban").error(
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
- raise RuntimeError("Database not initialized")
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)
@@ -22,11 +22,12 @@ class RedisManager:
22
22
  agent_handler: Any = None
23
23
 
24
24
  def __new__(cls: type["RedisManager"], config: SecurityConfig) -> "RedisManager":
25
- cls._instance = super().__new__(cls)
25
+ if cls._instance is None:
26
+ cls._instance = super().__new__(cls)
27
+ cls._instance.logger = logging.getLogger("guard_core.handlers.redis")
28
+ cls._instance.agent_handler = None
26
29
  cls._instance.config = config
27
- cls._instance.logger = logging.getLogger("guard_core.handlers.redis")
28
30
  cls._instance._closed = False
29
- cls._instance.agent_handler = None
30
31
  return cls._instance
31
32
 
32
33
  async def initialize_agent(self, agent_handler: Any) -> None:
@@ -54,10 +55,12 @@ class RedisManager:
54
55
  self.logger.error(f"Failed to send Redis event to agent: {e}")
55
56
 
56
57
  async def initialize(self) -> None:
57
- if self._closed or not self.config.enable_redis:
58
+ if not self.config.enable_redis:
58
59
  self._redis = None
59
60
  return
60
61
 
62
+ self._closed = False
63
+
61
64
  async with self._connection_lock:
62
65
  try:
63
66
  if self.config.redis_url is not None:
@@ -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 (self.agent_handler and self.config.enable_dynamic_rules):
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
- result: GuardResponse = self.context.config.custom_response_modifier(
63
- response
64
- )
65
- return result
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(
@@ -284,7 +284,7 @@ class ContentPreprocessor:
284
284
  return content
285
285
 
286
286
  def decode_common_encodings(self, content: str) -> str:
287
- max_decode_iterations = 3
287
+ max_decode_iterations = 7
288
288
  iterations = 0
289
289
 
290
290
  while iterations < max_decode_iterations:
@@ -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.banned_ips = TTLCache(
26
- maxsize=10000, ttl=cls.LOCAL_CACHE_TTL_CAP_SECONDS
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
- logging.getLogger("guard_core.sync.handlers.ipban").error(
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
- logging.getLogger("guard_core.sync.handlers.ipban").error(
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
- raise RuntimeError("Database not initialized")
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)
@@ -22,11 +22,12 @@ class RedisManager:
22
22
  agent_handler: Any = None
23
23
 
24
24
  def __new__(cls: type["RedisManager"], config: SecurityConfig) -> "RedisManager":
25
- cls._instance = super().__new__(cls)
25
+ if cls._instance is None:
26
+ cls._instance = super().__new__(cls)
27
+ cls._instance.logger = logging.getLogger("guard_core.sync.handlers.redis")
28
+ cls._instance.agent_handler = None
26
29
  cls._instance.config = config
27
- cls._instance.logger = logging.getLogger("guard_core.sync.handlers.redis")
28
30
  cls._instance._closed = False
29
- cls._instance.agent_handler = None
30
31
  return cls._instance
31
32
 
32
33
  def initialize_agent(self, agent_handler: Any) -> None:
@@ -54,10 +55,12 @@ class RedisManager:
54
55
  self.logger.error(f"Failed to send Redis event to agent: {e}")
55
56
 
56
57
  def initialize(self) -> None:
57
- if self._closed or not self.config.enable_redis:
58
+ if not self.config.enable_redis:
58
59
  self._redis = None
59
60
  return
60
61
 
62
+ self._closed = False
63
+
61
64
  with self._connection_lock:
62
65
  try:
63
66
  if self.config.redis_url is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: guard-core
3
- Version: 2.2.0
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, penetration detection, security headers, and behavioral analysis through a protocol-based architecture. Framework-specific adapters (fastapi-guard, flaskapi-guard, djapi-guard) consume this library.</strong>
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
- - **Behavioral Analysis**: Usage monitoring, return pattern detection, frequency analysis.
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**: Anomaly detection, slow pattern tracking, statistical analysis.
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "guard-core"
3
- version = "2.2.0"
3
+ version = "2.2.2"
4
4
  description = "Framework-agnostic security engine for the Guard ecosystem."
5
5
  authors = [
6
6
  {name = "Renzo Franceschini", email = "rennf93@users.noreply.github.com"}
@@ -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