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
docs(changelog): fold the #39 Redis, logging, cloud-refresh, and instrumentation work into the 3.5.0 section
The six Redis resilience fields, redis_fail_open semantics,
log_country_check_level and the log-level routing, the non-blocking
single-flight cloud refresh (with the TTL store and protocol routing),
and the pydantic-instrumentation opt-out now live in the single 3.5.0
release section, mirrored in docs/release-notes.md, with contributor
credit. Supersedes the back-dated v3.1.2 block on the PR branch, removed
at rebase.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,11 +19,17 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
19
19
### Added
20
20
21
21
-`guard_core.core.checks.build_default_pipeline(middleware)`: assembles the canonical 17-check pipeline. Framework adapters should use this instead of hand-listing check classes, so new checks ship to every adapter without adapter changes.
22
+
-**Redis resilience settings**: `redis_socket_connect_timeout` (default `2.0`s) and `redis_socket_timeout` (default `2.0`s) bound how long any Redis call can hold a request (both must be positive — `0` would mean a non-blocking socket, not "no timeout"; `None` disables); `redis_health_check_interval` (default `30`s, `0` disables) recycles stale pooled connections; `redis_max_connections` (default `None` = redis-py default) caps the pool; `redis_retries` (default `1`, `0` disables) adds client-level retries with exponential backoff on connection/timeout errors. Note the client-level retry can re-send a non-idempotent `INCR` whose reply was lost after the server committed it, over-counting by one — fail-closed for guard-core's rate-limit counters and self-healing next window.
23
+
-**`redis_fail_open`** (`bool`, default `False`): when a Redis outage surfaces as a `GuardRedisError` inside a security check, `fail_secure` governs by default (the request is blocked). Set `True` to skip the failing check and let the request through, treating Redis outages as an availability concern distinct from other check failures.
24
+
-**`log_country_check_level`**: per-request country verdicts that are not blocks (whitelisted / not-affected) now log at a configurable level (default `"INFO"`, `None` silences them) via the named `guard_core` logger instead of the root logger. Blocked-country hits still log at `WARNING`; no-rules / no-geolocation cases at `DEBUG`. Penetration-detection hits likewise honour `log_suspicious_level` (previously a second hardcoded-`WARNING` root-logger path), and the remaining bare root-logger calls in `utils.py` / `cloud_handler.py` moved onto named `guard_core` loggers. Async and sync mirrors updated identically.
22
25
23
26
### Changed
24
27
25
28
- Detection regex matching now uses one shared worker pool instead of constructing a thread pool per pattern match. Built-in patterns are verified linear — a test gate runs every built-in through the ReDoS safety validator — and matched directly, without the per-match thread-pool timeout wrapper (only custom and legacy-mode patterns pay that cost); scan input is capped to `detection_max_content_length` before matching in every mode — including legacy/no-preprocessor mode, which previously scanned unbounded content — bounding worst-case match time (a thread-pool timeout cannot interrupt a regex already running on the interpreter). Detection results are unchanged (attack-simulation baseline holds).
26
29
-`add_pattern` now returns `bool` (`True` registered, `False` rejected) instead of `None`, so callers can distinguish a rejected pattern from a registered one.
30
+
-**Cloud-provider IP refresh no longer runs on the request path.** The request that crosses `cloud_ip_refresh_interval` schedules a single-flight background refresh (`cloud_handler.schedule_refresh`) instead of awaiting multi-second provider fetches inline; while one refresh is in flight, further requests are no-ops. The background task runs the middleware's `refresh_cloud_ip_ranges()`, so adapter overrides stay on the periodic path; the debounce timestamp is restored when scheduling fails so the next request retries instead of waiting a full interval; and the in-memory cloud-IP store now honors the refresh TTL, so non-Redis deployments refetch provider ranges each interval instead of caching them for the process lifetime. Async and sync mirrors updated identically.
31
+
-**guard-agent's telemetry models are opted out of pydantic plugin instrumentation** at `guard_core` import: `SecurityEvent`/`SecurityMetric`/`EventBatch` set `plugin_settings={"logfire": {"record": "off"}}`, so a host app running `logfire.instrument_pydantic()` no longer emits a span per security event. A model that cannot be force-rebuilt degrades with a logged warning instead of crashing the import.
32
+
-**The pipeline handles Redis outages per `redis_fail_open`**: a `GuardRedisError` escaping a check is either skipped with a warning (`redis_fail_open=True`) or handed to the standard `fail_secure` path (default). Async and sync mirrors updated identically.
27
33
28
34
### Fixed
29
35
@@ -40,6 +46,8 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
40
46
- Blocking a banned IP now emits an `ip_blocked` event (`filter_type="banned"`); repeat requests from banned IPs were previously invisible to telemetry.
41
47
-`geo_ip_db_max_age` is now passed to the auto-constructed IPInfo handler; the setting previously had no runtime effect.
42
48
49
+
The Redis resilience settings, `redis_fail_open`, `log_country_check_level`, the non-blocking cloud-IP refresh, and the pydantic-instrumentation opt-out were contributed by [@davidsmfreire](https://github.com/davidsmfreire) in [#39](https://github.com/rennf93/guard-core/pull/39).
50
+
43
51
### Removed
44
52
45
53
- Dead `RouteConfig.session_limits` attribute (never set by any decorator, never read by any check).
Copy file name to clipboardExpand all lines: docs/release-notes.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,11 +24,17 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
24
24
### Added
25
25
26
26
-**`build_default_pipeline()` — one source of truth for the check pipeline.** New `guard_core.core.checks.build_default_pipeline(middleware)` assembles the canonical 17-check pipeline in its defined order. Framework adapters call it instead of hand-listing check classes, so a new engine check reaches every adapter (FastAPI, Flask, Django) without an adapter-side change.
27
+
-**Redis resilience settings**: `redis_socket_connect_timeout` (default `2.0`s) and `redis_socket_timeout` (default `2.0`s) bound how long any Redis call can hold a request (both must be positive — `0` would mean a non-blocking socket, not "no timeout"; `None` disables); `redis_health_check_interval` (default `30`s, `0` disables) recycles stale pooled connections; `redis_max_connections` (default `None` = redis-py default) caps the pool; `redis_retries` (default `1`, `0` disables) adds client-level retries with exponential backoff on connection/timeout errors. Note the client-level retry can re-send a non-idempotent `INCR` whose reply was lost after the server committed it, over-counting by one — fail-closed for guard-core's rate-limit counters and self-healing next window.
28
+
-**`redis_fail_open`** (`bool`, default `False`): when a Redis outage surfaces as a `GuardRedisError` inside a security check, `fail_secure` governs by default (the request is blocked). Set `True` to skip the failing check and let the request through, treating Redis outages as an availability concern distinct from other check failures.
29
+
-**`log_country_check_level`**: per-request country verdicts that are not blocks (whitelisted / not-affected) now log at a configurable level (default `"INFO"`, `None` silences them) via the named `guard_core` logger instead of the root logger. Blocked-country hits still log at `WARNING`; no-rules / no-geolocation cases at `DEBUG`. Penetration-detection hits likewise honour `log_suspicious_level` (previously a second hardcoded-`WARNING` root-logger path), and the remaining bare root-logger calls in `utils.py` / `cloud_handler.py` moved onto named `guard_core` loggers. Async and sync mirrors updated identically.
27
30
28
31
### Changed
29
32
30
33
-**Detection regex matching now uses one shared worker pool.** Instead of constructing a thread pool per pattern match, matching uses a single shared executor, and built-in (compile-time-vetted) patterns match directly without the per-match timeout wrapper — only custom and legacy-mode patterns pay that cost. Scan input is now capped to `detection_max_content_length` before matching in every mode, including legacy/no-preprocessor mode, which previously scanned unbounded content (a thread-pool timeout cannot interrupt a regex already running on the interpreter, so the cap bounds the worst case directly). Detection results are unchanged — the attack-simulation baseline (recall 0.857, false-positive rate 0.0) holds bit-for-bit.
31
34
-**`add_pattern` now returns `bool`** (`True` registered, `False` rejected) instead of `None`, so callers can distinguish a rejected pattern from a registered one.
35
+
-**Cloud-provider IP refresh no longer runs on the request path.** The request that crosses `cloud_ip_refresh_interval` schedules a single-flight background refresh (`cloud_handler.schedule_refresh`) instead of awaiting multi-second provider fetches inline; while one refresh is in flight, further requests are no-ops. The background task runs the middleware's `refresh_cloud_ip_ranges()`, so adapter overrides stay on the periodic path; the debounce timestamp is restored when scheduling fails so the next request retries instead of waiting a full interval; and the in-memory cloud-IP store now honors the refresh TTL, so non-Redis deployments refetch provider ranges each interval instead of caching them for the process lifetime. Async and sync mirrors updated identically.
36
+
-**guard-agent's telemetry models are opted out of pydantic plugin instrumentation** at `guard_core` import: `SecurityEvent`/`SecurityMetric`/`EventBatch` set `plugin_settings={"logfire": {"record": "off"}}`, so a host app running `logfire.instrument_pydantic()` no longer emits a span per security event. A model that cannot be force-rebuilt degrades with a logged warning instead of crashing the import.
37
+
-**The pipeline handles Redis outages per `redis_fail_open`**: a `GuardRedisError` escaping a check is either skipped with a warning (`redis_fail_open=True`) or handed to the standard `fail_secure` path (default). Async and sync mirrors updated identically.
32
38
33
39
### Fixed
34
40
@@ -45,6 +51,8 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
45
51
-**Banned-IP blocks are now visible to telemetry.** Blocking a banned IP emits an `ip_blocked` event with `filter_type="banned"`; repeat requests from already-banned IPs were previously invisible.
46
52
-**`geo_ip_db_max_age` now takes effect.** It is passed to the auto-constructed IPInfo handler; the setting was previously silently inert.
47
53
54
+
The Redis resilience settings, `redis_fail_open`, `log_country_check_level`, the non-blocking cloud-IP refresh, and the pydantic-instrumentation opt-out were contributed by [@davidsmfreire](https://github.com/davidsmfreire) in [#39](https://github.com/rennf93/guard-core/pull/39).
55
+
48
56
### Removed
49
57
50
58
-**Dead `RouteConfig.session_limits`** attribute — never set by any decorator, never read by any check.
0 commit comments