Skip to content

Commit f6310b7

Browse files
committed
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.
1 parent 5fa8d2c commit f6310b7

2 files changed

Lines changed: 16 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
1919
### Added
2020

2121
- `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.
2225

2326
### Changed
2427

2528
- 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).
2629
- `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.
2733

2834
### Fixed
2935

@@ -40,6 +46,8 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
4046
- Blocking a banned IP now emits an `ip_blocked` event (`filter_type="banned"`); repeat requests from banned IPs were previously invisible to telemetry.
4147
- `geo_ip_db_max_age` is now passed to the auto-constructed IPInfo handler; the setting previously had no runtime effect.
4248

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+
4351
### Removed
4452

4553
- Dead `RouteConfig.session_limits` attribute (never set by any decorator, never read by any check).

docs/release-notes.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
2424
### Added
2525

2626
- **`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.
2730

2831
### Changed
2932

3033
- **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.
3134
- **`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.
3238

3339
### Fixed
3440

@@ -45,6 +51,8 @@ Pipeline factory, decorated-route IP/country enforcement, and detection ReDoS ha
4551
- **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.
4652
- **`geo_ip_db_max_age` now takes effect.** It is passed to the auto-constructed IPInfo handler; the setting was previously silently inert.
4753

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+
4856
### Removed
4957

5058
- **Dead `RouteConfig.session_limits`** attribute — never set by any decorator, never read by any check.

0 commit comments

Comments
 (0)