Skip to content

fix(dynamic-rules): apply rules over the base config and wire the agent, the interval and the response modifier - #84

Merged
rennf93 merged 1 commit into
masterfrom
fix/dynamic-rules-parity
Oct 15, 2026
Merged

rennf93 merged 1 commit into
masterfrom
fix/dynamic-rules-parity

Conversation

@rennf93

@rennf93 rennf93 commented Oct 15, 2026

Copy link
Copy Markdown
Member

Closes #83

I brought dynamic rules, the agent wiring and the custom response modifier to the Python reference (guard-core master at 859ac345, including guard-core #169). I kept it as one PR and one commit: the wiring and the modifier share SecurityConfig and the engine with the rule changes, and the single bin/test_dynamic_rules.php runner covers both, so a split would leave a red first half.

Dynamic rules (src/Rules):

  • ip_blacklist bans through the engine ban manager for ip_ban_duration seconds and ip_whitelist unbans, instead of merging into the static lists. blocked_countries, whitelist_countries, block_cloud_providers, blocked_user_agents and endpoint_rate_limits replace the base values (unknown cloud providers dropped, an all-invalid list empties the value), global_rate_window only applies with a global_rate_limit, country rules without a geo resolver warn and skip, and emergency mode always halves the auto-ban threshold in effect.
  • Each new rule version is applied over the base config (guard-core #169): a failed apply keeps the previously active version, expiry restores the base, hydration then a new version overlays the base, and patterns already in the pool are not added again. The 8 reference overlay tests are ported in bin/test_dynamic_rules.php with the same scenarios and values.
  • Events match the reference envelopes (handler dynamic_rules, previous_version, applied counters, and the missing emergency_mode_activated). The three conformance xfail entries this fixes are removed from the PHP baselines (evt_dynamic_rules_update_apply, evt_emergency_mode_activated, rio_dynamic_rules_snapshot_write).
  • SecurityConfig::with() no longer drops enableAgent, enableDynamicRules, agentEnableEvents, agentEnableMetrics and dynamicRulesCachePath, an injected geo resolver survives a config without country lists, and every installed candidate carries a fresh revision so the pipeline rebuilds.
  • The manager accepts the guard-agent-php DynamicRules object, auto_ban_threshold and auto_ban_duration keep the reference minimum of 1, and the snapshot renders Z timestamps and an object for an empty endpoint_rate_limits.

Wiring and modifier:

  • enableAgent is stored. enableDynamicRules and enableEnrichment now require it (the reference validate_agent_config), which is a breaking change for configs that set either without enableAgent. agent_api_key is not modelled because the PHP engine never builds the agent, the adapters inject a handler.
  • GuardEngine::initialize() builds the DynamicRuleManager when enableDynamicRules is on and an agent handler with getDynamicRules() is attached (warning and static config otherwise, as the reference), and hydrates the last-known rules. The reference runs a background loop; a PHP request has none, so execute() calls the manager's interval gate: the first request fetches, then one fetch per dynamicRuleInterval seconds (new config field, default 300, minimum 60, as the reference). guard-agent-php drives its own rules refresh from tick() for the same reason. Under FPM each worker keeps its own clock and a fresh worker hydrates from the last-known snapshot.
  • customResponseModifier is a new config field, applied once (after the security headers, before the CORS headers) to every response the engine returns, with GuardEngine::applyResponseModifier() for the pass-through responses adapters send. An exception leaves the response unmodified. Adapters need to call that seam to modify their pass-through responses.

Docs: AGENTS.md, docs/usage.md, docs/configuration.md, README.md and the examples no longer say enableAgent fails validation or that dynamic rules are unsupported, and the metrics persistence claim now matches the engine, which builds the collector without a Redis handler.

Test and gate evidence (host PHP 8.5, a private Redis on port 6391 because test_ratelimit and test_geo_rate_limits run FLUSHDB): bin/test_dynamic_rules.php fails on master and passes here, and every runner in ci.yml plus the coverage gate passes. The ecosystem gate runs the four adapter suites against this branch.

…nt, the interval and the response modifier (#83)

Dynamic rules now apply exactly as the Python reference does: ip_blacklist bans for ip_ban_duration and ip_whitelist unbans, the country, cloud provider, user agent and endpoint rate limit values replace the base values, emergency mode always halves the auto-ban threshold, each new version is applied over the base config (guard-core #169), and the events carry the reference envelopes.

SecurityConfig::with() keeps the agent and dynamic rule fields, enableAgent is stored and required by enableDynamicRules and enableEnrichment, and dynamicRuleInterval and customResponseModifier are new fields. GuardEngine builds the DynamicRuleManager at initialize(), fetches rules through a per-request interval gate, and applies the response modifier once to every response it returns.
@rennf93 rennf93 added bug Confirmed defect area: engine Touches src/Engine/, src/Config/, or src/Routing/ breaking-change PR contains a backward-incompatible change labels Oct 15, 2026
@github-actions github-actions Bot added documentation Docs site, README, governance files area: request Touches src/Request/ or src/Ip/ (request model, client IP, canonicalization) tests Test suite changes conformance Fixture-driven parity suite against the Python engine corpus examples Example application changes ci GitHub Actions workflows labels Oct 15, 2026
@rennf93
rennf93 merged commit 029798f into master Oct 15, 2026
30 checks passed
@rennf93
rennf93 deleted the fix/dynamic-rules-parity branch October 15, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: engine Touches src/Engine/, src/Config/, or src/Routing/ area: request Touches src/Request/ or src/Ip/ (request model, client IP, canonicalization) breaking-change PR contains a backward-incompatible change bug Confirmed defect ci GitHub Actions workflows conformance Fixture-driven parity suite against the Python engine corpus documentation Docs site, README, governance files examples Example application changes tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Dynamic rules, agent wiring and the custom response modifier diverge from the reference

1 participant