You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+14Lines changed: 14 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -125,6 +125,20 @@ Cloud-IP region scoping, IP allow-list correctness, bounded body inspection, asy
125
125
126
126
___
127
127
128
+
v3.1.2 (2026-06-08)
129
+
-------------------
130
+
131
+
Configurable country-check logging (v3.1.2)
132
+
-------------------------------------------
133
+
134
+
### Changed
135
+
136
+
-**Per-request country verdicts are now configurable and routed through the `guard_core` logger.**`_log_country_check_result` previously logged whitelisted / not-affected results at `INFO` straight to the root logger (`logging.info`), so it was unreachable by named-logger levels and by `log_suspicious_level` / `log_request_level`. It now logs via `logging.getLogger("guard_core")` and honours the new `SecurityConfig.log_country_check_level` (default `"INFO"`, preserving prior behaviour). Set it to `None` to silence the routine whitelisted / not-affected chatter while keeping the genuine signal. Continuing the noise reduction from v3.1.0: blocked-country hits still log at `WARNING`, and no-rules / no-geolocation cases still log at `DEBUG`. Sync mirror updated identically.
137
+
-**Penetration-detection hits now honour `log_suspicious_level`.** The per-component "Potential attack detected from …" line in `_check_request_component` was a second suspicious-log path that logged at a hardcoded `WARNING` on the root logger, bypassing config and duplicating the authoritative `SuspiciousActivityCheck` log. It now routes through the `guard_core` logger at `log_suspicious_level` (threaded down from `detect_penetration_attempt`); setting `log_suspicious_level=None` silences it.
138
+
-**All remaining bare root-logger calls in `utils.py` and `cloud_handler.py` moved onto named `guard_core` loggers** (`Error processing client IP`, `Error checking IP`, `Enhanced detection failed`, and the cloud-provider `Failed to fetch … IP ranges` errors). No behavioural change at default levels — `logging.getLogger("guard_core").setLevel(...)` now governs the whole library. Sync mirror updated identically.
|**Purpose**| Periodically refreshes cloud provider IP ranges based on `config.cloud_ip_refresh_interval`|
261
261
|**Blocks?**| Never. Always returns `None`|
262
-
|**Side Effects**|Triggers `middleware.refresh_cloud_ip_ranges()` when the interval has elapsed |
262
+
|**Side Effects**|Schedules a single-flight background refresh (running `middleware.refresh_cloud_ip_ranges()` off the request path) when the interval has elapsed |
|`redis_prefix`|`str`|`"guard_core:"`| Key prefix for namespace isolation. |
291
+
|`redis_socket_connect_timeout`|`float \| None`|`2.0`| Seconds to wait establishing a TCP connection. Must be positive; `None` disables (blocks indefinitely on a partitioned Redis). |
292
+
|`redis_socket_timeout`|`float \| None`|`2.0`| Seconds to wait on a read/write before raising. Must be positive; `None` means no timeout. |
293
+
|`redis_health_check_interval`|`int`|`30`| Seconds between pooled-connection health checks. `0` disables. |
294
+
|`redis_max_connections`|`int \| None`|`None`| Cap on the connection pool size. `None` uses redis-py's default. |
|`redis_fail_open`|`bool`|`False`| On Redis outage, `fail_secure` governs by default. Set `True` to skip the failing check and let the request through, treating Redis outages as an availability concern distinct from other check failures. |
Copy file name to clipboardExpand all lines: docs/internals/check-implementations.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -199,9 +199,9 @@ ___
199
199
200
200
**Purpose**: Periodically refreshes cloud provider IP ranges.
201
201
202
-
**Blocks**: Never.
202
+
**Blocks**: Never. The refresh runs as a single-flight background task (via `cloud_handler.schedule_refresh`, which invokes the middleware's `refresh_cloud_ip_ranges()`), so the request that crosses the interval is never delayed by provider fetches.
203
203
204
-
**Triggers when**: `config.block_cloud_providers` is set and `cloud_ip_refresh_interval` seconds have elapsed since the last refresh.
204
+
**Triggers when**: `config.block_cloud_providers` is set and `cloud_ip_refresh_interval` seconds have elapsed since the last refresh. If scheduling fails, the debounce timestamp is restored so the next request retries.
Creates a `redis.asyncio.Redis` connection from `config.redis_url` with `decode_responses=True`. Pings to verify connectivity. Raises `GuardRedisError(503)` on failure.
39
+
Creates a `redis.asyncio.Redis` connection from `config.redis_url` with `decode_responses=True`, applying the configured socket timeouts (`redis_socket_timeout`, `redis_socket_connect_timeout`), pool cap (`redis_max_connections`), health-check interval (`redis_health_check_interval`), and — when `redis_retries > 0` — a client-level `Retry` with exponential backoff on connection/timeout errors. Pings to verify connectivity. Raises `GuardRedisError(503)` on failure.
40
+
41
+
Note that the client-level retry re-sends non-idempotent commands: a lost reply after the server already committed an `INCR` over-counts by one. For guard-core's rate-limit counters that fails closed (mildly over-restrictive, self-heals next window); callers needing exactly-once semantics should not build on `incr()`.
40
42
41
43
**Close**:
42
44
@@ -134,16 +136,24 @@ class GuardRedisError(GuardCoreError):
134
136
135
137
Adapters should catch `GuardRedisError` during initialization and handle it according to their framework's error model.
136
138
139
+
When a `GuardRedisError` escapes a security check at request time (a Redis outage mid-request), the pipeline honors `fail_secure` by default: the request is blocked with a 500. Setting `redis_fail_open=True` opts into skipping the failing check and letting the request through, treating Redis outages as an availability concern distinct from other check failures.
0 commit comments