diff --git a/CHANGELOG.md b/CHANGELOG.md index 311e0b4..f0d95d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project are documented here. The format follows Keep a Changelog; versions follow SemVer. +v1.1.0 (2026-10-16) +------------------- + +The full engine config, engine-side exclusions and client identity, and the guard-core-go v4.4.0 floor +------------------------------------------------------------------------------------------------------ + +### Added + +- **The plugin accepts the full engine config** (#17): `PREST_GUARD_ENGINE_CONFIG` (inline JSON) or `PREST_GUARD_ENGINE_CONFIG_FILE` decode a strict JSON object of `guardcore.SecurityConfig` field names over the existing env subset (unknown keys, bad JSON, func fields and values the engine rejects fail closed with the generic 500). The new `GuardConfig.EngineConfigJSON` field carries it, and `GuardConfig.Configure` gives a plugin built from a fork the fully built config last, for the func and interface fields JSON cannot carry (`CustomResponseModifier`, `AgentHandler`, `GeoIPHandler`, `CustomRequestCheck`). `PREST_GUARD_TRUSTED_PROXY_DEPTH` and `GuardConfig.TrustedProxyDepth` map the engine's `trusted_proxy_depth` (0 keeps the engine default of 1). The README documents the surface. +- **The containerized example app** (#8): a compose stack running prestd built from a pinned upstream commit with the prest-guard plugin compiled and mounted, in front of Postgres, published to GHCR. + +### Changed + +- **Raised the engine floor to `github.com/rennf93/guard-core-go/v4 v4.4.0`, the reference-alignment release.** The v4.4.0 engine adds the per-request scan time budget (`Detection.MaxScanSeconds`, `pattern_timeout`), honors `Detection.ThreatScoreThreshold`, canonicalizes client identity (IPv4-mapped addresses, brackets, zones), follows the reference IP list precedence, answers an unresolved client address with 403 under `FailSecure`, applies dynamic rule versions over the base config, fixes the filtered-value scan bypass (GHSA-w9j2-2m3v-5hcc) and scans the Cookie header one pair at a time. The `nethttp-guard` floor stays at v1.4.0 in this change and moves to the v1.5.0 release (which applies the `CustomResponseModifier`) in a follow-up once that tag is published. +- **Exclusions and client identity run inside the engine** (#17): `proxy.go` and the path short-circuit in the handler are gone. Excluded paths still skip detection but, as in the reference, `ip_security` and `rate_limit` stay enforced on them, so a probe source that shares an identity with a rate limit should go in `PREST_GUARD_EXEMPT_IPS`. Client resolution follows the engine's `trusted_proxy_depth` (default 1) instead of the old rightmost-untrusted walk. The pre-engine matcher stays only for the 503 window before an engine exists, and it now cleans the path so `/_health/../articles` cannot ride an excluded prefix. +- **Behavior to know before upgrading.** A request that reaches the engine with an empty remote address is now answered 403 `Client address could not be determined` under `FailSecure`. IPv4 blacklist, whitelist, exempt and emergency entries now also match clients that connect as `::ffff:a.b.c.d`, and an IPv4 CIDR no longer trusts a mapped peer as a proxy. A configured whitelist now decides alone (a route whitelist overrides the route blacklist and skips the global lists, a global whitelist overrides the global blacklist), so an entry that relied on the blacklist beating the whitelist must move. A dynamic rule version no longer stacks on the previous one. A request that spends `Detection.MaxScanSeconds` (default 10.0) of regex CPU time is blocked with a `pattern_timeout` threat, and a set `Detection.ThreatScoreThreshold` now changes verdicts. A passive-mode log match on the old penetration wording needs updating. + +### Fixed + +- **`PREST_GUARD_AUTO_BAN` maps to `EnableRateLimitAutoBan`** (#19): `buildEngineConfig` sets it together with `EnableIPBanning` (on when the variable is set, off in passive mode). The engine feeds rate-limit violations into the autoban counter from v4.4.0 (guard-core-go#100), so the knob now takes effect on this release's floor. +- **A timing flake in `TestDeferredHandlerSwapsLiveGuardIn`**: the background engine retry could win the race against the first request (about 2 runs in 12 on master and on v4.4.0), so the test now holds the retry until the 503 is observed. +- **Docs and CI**: the README is refreshed against the current engine (large-value truncation, benchmark table), the docs site deploys to GitHub Pages with `site_url` set, and the weekly lint runs on Go 1.26.x. + +___ + v1.0.1 (2026-10-09) ------------------- diff --git a/go.mod b/go.mod index fd1e213..5a9cd13 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/rennf93/prest-guard go 1.26.0 require ( - github.com/rennf93/guard-core-go/v4 v4.3.2 - github.com/rennf93/nethttp-guard v1.4.0 + github.com/rennf93/guard-core-go/v4 v4.4.0 + github.com/rennf93/nethttp-guard v1.5.0 github.com/urfave/negroni/v3 v3.1.1 ) diff --git a/go.sum b/go.sum index 9096694..7ccb16a 100644 --- a/go.sum +++ b/go.sum @@ -12,10 +12,10 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/redis/go-redis/v9 v9.23.0 h1:/TTVdJa8BRNsybwwGGnmiYktHNlUXtbOQxlWpHBPL3s= github.com/redis/go-redis/v9 v9.23.0/go.mod h1:EWP2UUk+XDaI1s5nQB5mablZN6vccXlmBQTyfnF87D0= -github.com/rennf93/guard-core-go/v4 v4.3.2 h1:UpEUaSw1MNIoPcMbikG2hPZr+rhqp8vtCDasNh0m5DQ= -github.com/rennf93/guard-core-go/v4 v4.3.2/go.mod h1:zbSvFuYHn8Oo6bMwJ7jJ3iR8CEHBIYUvf1ALd1qMAvU= -github.com/rennf93/nethttp-guard v1.4.0 h1:7CArpfVqAbfk0I5HYVr1Vvcbb2P9D8RiPbv3wrgiBDE= -github.com/rennf93/nethttp-guard v1.4.0/go.mod h1:DP766KRBdBQXFJ44j08drxWLjV9WvO1vA4gXMJqTu+k= +github.com/rennf93/guard-core-go/v4 v4.4.0 h1:+la36FG7WWr65gCc0Rq6RyIGC4Aiy+w91tQzH1SCndg= +github.com/rennf93/guard-core-go/v4 v4.4.0/go.mod h1:6KcajSAwPjYhYxY48nqdrgihH+sXf0NIt6lGMNpKyaU= +github.com/rennf93/nethttp-guard v1.5.0 h1:i+ByOlfazOraR/YGsd3TddMo6LmX20h3f8NFmp+Og1Y= +github.com/rennf93/nethttp-guard v1.5.0/go.mod h1:tobelI4/Z3CNMBjpCCcvSAoBG1FFouDq5MWvckE/pJ4= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/urfave/negroni/v3 v3.1.1 h1:6MS4nG9Jk/UuCACaUlNXCbiKa0ywF9LXz5dGu09v8hw= diff --git a/middleware_test.go b/middleware_test.go index 94ddf36..b71929c 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -344,11 +344,13 @@ func TestRedisFailClosedAnswers503AndKeepsHealthAlive(t *testing.T) { func TestDeferredHandlerSwapsLiveGuardIn(t *testing.T) { exclusions := newPathExclusions(mergeExclusions(nil)) attempts := 0 + release := make(chan struct{}) // holds the retry until the 503 is observed factory := func() (negroni.Handler, error) { attempts++ if attempts == 1 { return nil, fmt.Errorf("engine not ready") } + <-release return negroni.HandlerFunc(func(w http.ResponseWriter, _ *http.Request, next http.HandlerFunc) { w.WriteHeader(http.StatusTeapot) }), nil @@ -358,6 +360,7 @@ func TestDeferredHandlerSwapsLiveGuardIn(t *testing.T) { if rec := serve(t, h, http.MethodGet, "/articles", "203.0.113.20", ""); rec.Code != http.StatusServiceUnavailable { t.Fatalf("before the retry succeeds the request must get 503, got %d", rec.Code) } + close(release) deadline := 100 // bounded wait for the background retry for i := 0; i < deadline; i++ { rec := serve(t, h, http.MethodGet, "/articles", "203.0.113.20", "")