Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .mike.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ version_selector: true
title_switch: true
versions_file: docs/versions/versions.json
versions:
- 4.4.0
- 4.3.3
- 4.3.2
- 4.3.1
Expand Down Expand Up @@ -52,4 +53,4 @@ versions:
- 0.1.0
- latest
aliases:
latest: 4.3.3
latest: 4.4.0
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.

___

v4.4.0 (2026-10-15)
-------------------

The scan-budget release: the detection scan yields the event loop and is capped per request, the Cookie header is scanned pair by pair, and dynamic rule versions apply over the base config (v4.4.0)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

### Added

- **`detection_max_scan_seconds`: a per-request cap on regex scan CPU time** (guard-core #166, #167). A scan could hold the event loop and nothing bounded the total scan time of one request. In the async tree the scan now awaits the regex worker between patterns, so other requests run in between, and `detection_max_scan_seconds` (default `10.0`, range `0.1` to `60.0`) caps the thread CPU time spent regex scanning one request, summed across every field and every pattern. Once the budget is spent the remaining patterns and values are not scanned and the request receives a weighted `pattern_timeout` threat (pattern `scan_time_budget_exhausted`) that counts toward the anomaly score, so padding a request with filler cannot skip detection. The default is far above normal traffic, so ordinary requests never reach it. The sync tree has no event loop and keeps blocking.
- **`excluded_semantic_detection_headers`: an opt-in semantic-only header exclusion** (guard-core #166, #167). Header names listed here (case-insensitive, default empty) skip the semantic analyzer only, and regex category scanning of those headers is unchanged. JWT session cookies and bearer tokens can score close to `detection_semantic_threshold` because the analyzer counts base64 as obfuscation, so an app can now set `excluded_semantic_detection_headers={"cookie", "authorization"}` instead of raising the global threshold. A route adds names with `@security.detection_exclusion(semantic_headers={...})`.

### Fixed

- **The Cookie header is scanned one `name=value` pair at a time** (guard-core #161, #162, @HardMax71). The whole header used to be one scanned value, so the `;` between cookies counted as command separators in the semantic score and an ordinary logged-in header reached 0.71 and was rejected. Each pair now goes through the same sensitive, excluded or normal header path as before, so every regex and semantic check still runs on each cookie and the `cookie` log redaction is unchanged. Only spaces around a pair are trimmed (a CR or LF stays in the scanned value), empty pairs are skipped, and each pair counts against `detection_max_scan_values`, as each query parameter does.
- **Dynamic rule versions apply over the base config, not over the previous version** (guard-core #168, #169). `DynamicRuleManager._apply_rules` now restores the base snapshot before applying a new version or a different rule, so a version is an overlay on the base config. Before this, emergency mode halved `auto_ban_threshold` again for every emergency version (16, 8, 4, 2), and a version that stopped setting a field kept the value the previous version set. A failed apply rolls back to the state before the attempt, the retained base is deep-copied so the live config cannot mutate it, and expiry is unchanged and still restores the base. Suspicious patterns added by a rule were appended to the registry again for every version that repeated them, so patterns the registry already holds are now skipped (patterns still persist after expiry, as before). IP bans keep their current behavior.
- **Passive-mode suspicious logs keep the reason** (guard-core #164, #165). A passive line without a trigger now reads `[PASSIVE MODE] Suspicious activity detected from <ip>: ... - Reason: <reason> - Headers: ...` instead of the penetration wording with the reason dropped. Lines with a trigger are unchanged. If you match on the old passive log text, update the match.

### Changed

- **Behavior to know before upgrading.** A dynamic rule version no longer carries over the previous version's changes: a field the new version does not set returns to the base value, so rules that relied on stacking versions must set every field they want. Emergency mode no longer compounds the `auto_ban_threshold` reduction per version. A request that spends `detection_max_scan_seconds` of regex CPU time is now flagged with a `pattern_timeout` threat instead of being scanned to the end. The Cookie header is scanned per pair, so a logged-in cookie header that used to be rejected as a command-separator false positive now passes while XSS, SQLi and CRLF inside a single cookie are still detected.

### Docs

- `enable_dynamic_rules` (field description, `docs/configuration/security-config.md` and the agent skill reference) now lists everything a rule changes, checked against every config write in the rule application module, and `docs/configuration/detection-tuning.md` documents the two new options and the Cookie behavior (guard-core #165, #162, #167).

### Test

- The timing tests measure CPU time and corroborate before failing on loaded runners: the lazy-init test now blocks the background init and asserts ordering, the bounded-scan ceiling must be breached at every size, and the ReDoS and hostname-fragment tests use the child's CPU time with wall clock only guarding against a hang (guard-core #163).

### Chore

- The sponsor button points at the maintainer's Sponsors page, the mkdocs `site_url` drops `/latest/` so canonical URLs resolve, and the CodeQL action moves to 4.38.3 (guard-core #158, #159, #160).

___

v4.3.3 (2026-10-12)
-------------------

Expand Down
35 changes: 35 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,41 @@ Release Notes

___

v4.4.0 (2026-10-15)
-------------------

The scan-budget release: the detection scan yields the event loop and is capped per request, the Cookie header is scanned pair by pair, and dynamic rule versions apply over the base config (v4.4.0)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

### Added

- **`detection_max_scan_seconds`: a per-request cap on regex scan CPU time** (guard-core #166, #167). A scan could hold the event loop and nothing bounded the total scan time of one request. In the async tree the scan now awaits the regex worker between patterns, so other requests run in between, and `detection_max_scan_seconds` (default `10.0`, range `0.1` to `60.0`) caps the thread CPU time spent regex scanning one request, summed across every field and every pattern. Once the budget is spent the remaining patterns and values are not scanned and the request receives a weighted `pattern_timeout` threat (pattern `scan_time_budget_exhausted`) that counts toward the anomaly score, so padding a request with filler cannot skip detection. The default is far above normal traffic, so ordinary requests never reach it. The sync tree has no event loop and keeps blocking.
- **`excluded_semantic_detection_headers`: an opt-in semantic-only header exclusion** (guard-core #166, #167). Header names listed here (case-insensitive, default empty) skip the semantic analyzer only, and regex category scanning of those headers is unchanged. JWT session cookies and bearer tokens can score close to `detection_semantic_threshold` because the analyzer counts base64 as obfuscation, so an app can now set `excluded_semantic_detection_headers={"cookie", "authorization"}` instead of raising the global threshold. A route adds names with `@security.detection_exclusion(semantic_headers={...})`.

### Fixed

- **The Cookie header is scanned one `name=value` pair at a time** (guard-core #161, #162, @HardMax71). The whole header used to be one scanned value, so the `;` between cookies counted as command separators in the semantic score and an ordinary logged-in header reached 0.71 and was rejected. Each pair now goes through the same sensitive, excluded or normal header path as before, so every regex and semantic check still runs on each cookie and the `cookie` log redaction is unchanged. Only spaces around a pair are trimmed (a CR or LF stays in the scanned value), empty pairs are skipped, and each pair counts against `detection_max_scan_values`, as each query parameter does.
- **Dynamic rule versions apply over the base config, not over the previous version** (guard-core #168, #169). `DynamicRuleManager._apply_rules` now restores the base snapshot before applying a new version or a different rule, so a version is an overlay on the base config. Before this, emergency mode halved `auto_ban_threshold` again for every emergency version (16, 8, 4, 2), and a version that stopped setting a field kept the value the previous version set. A failed apply rolls back to the state before the attempt, the retained base is deep-copied so the live config cannot mutate it, and expiry is unchanged and still restores the base. Suspicious patterns added by a rule were appended to the registry again for every version that repeated them, so patterns the registry already holds are now skipped (patterns still persist after expiry, as before). IP bans keep their current behavior.
- **Passive-mode suspicious logs keep the reason** (guard-core #164, #165). A passive line without a trigger now reads `[PASSIVE MODE] Suspicious activity detected from <ip>: ... - Reason: <reason> - Headers: ...` instead of the penetration wording with the reason dropped. Lines with a trigger are unchanged. If you match on the old passive log text, update the match.

### Changed

- **Behavior to know before upgrading.** A dynamic rule version no longer carries over the previous version's changes: a field the new version does not set returns to the base value, so rules that relied on stacking versions must set every field they want. Emergency mode no longer compounds the `auto_ban_threshold` reduction per version. A request that spends `detection_max_scan_seconds` of regex CPU time is now flagged with a `pattern_timeout` threat instead of being scanned to the end. The Cookie header is scanned per pair, so a logged-in cookie header that used to be rejected as a command-separator false positive now passes while XSS, SQLi and CRLF inside a single cookie are still detected.

### Docs

- `enable_dynamic_rules` (field description, `docs/configuration/security-config.md` and the agent skill reference) now lists everything a rule changes, checked against every config write in the rule application module, and `docs/configuration/detection-tuning.md` documents the two new options and the Cookie behavior (guard-core #165, #162, #167).

### Test

- The timing tests measure CPU time and corroborate before failing on loaded runners: the lazy-init test now blocks the background init and asserts ordering, the bounded-scan ceiling must be breached at every size, and the ReDoS and hostname-fragment tests use the child's CPU time with wall clock only guarding against a hang (guard-core #163).

### Chore

- The sponsor button points at the maintainer's Sponsors page, the mkdocs `site_url` drops `/latest/` so canonical URLs resolve, and the CodeQL action moves to 4.38.3 (guard-core #158, #159, #160).

___

v4.3.3 (2026-10-12)
-------------------

Expand Down
3 changes: 2 additions & 1 deletion docs/versions/versions.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"4.4.0": "4.4.0",
"4.3.3": "4.3.3",
"4.3.2": "4.3.2",
"4.3.1": "4.3.1",
Expand Down Expand Up @@ -48,5 +49,5 @@
"1.0.1": "1.0.1",
"1.0.0": "1.0.0",
"0.1.0": "0.1.0",
"latest": "4.3.3"
"latest": "4.4.0"
}
2 changes: 1 addition & 1 deletion guard_core/.agents/skills/guard-core/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Guard Core best practices and conventions for the framework-agnosti

Official guard-core skill to write code with best practices, keeping up to date with the real installed surface (introspect the package, do not rely on model memory of it).

Current as of guard-core 4.3.3.
Current as of guard-core 4.4.0.

guard-core is the framework-agnostic security engine powering the Guard ecosystem. Framework adapters (fastapi-guard, flaskapi-guard, djapi-guard, tornadoapi-guard) implement three protocols and get the full pipeline, detection engine, Redis state, and telemetry for free.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "guard-core"
version = "4.3.3"
version = "4.4.0"
description = "Framework-agnostic security engine for the Guard ecosystem."
authors = [
{name = "Renzo Franceschini", email = "rennf93@guard-core.com"}
Expand Down
Loading