The adapter has exactly two options (WithMaxBodyBytes, WithLogger); all
security tuning is engine configuration. See the
guard-core-go configuration reference
for the full SecurityConfig surface.
cfg := guardcore.DefaultSecurityConfig()
cfg.EnableRateLimiting = true
cfg.RateLimit = 30
cfg.RateLimitWindow = 60
cfg.EndpointRateLimits = map[string]guardcore.RateLimitEntry{
"/rate/strict": {Requests: 1, Window: 10},
}
cfg.EnableIPBanning = true
cfg.AutoBanThreshold = 5
cfg.AutoBanDuration = 300
cfg.CustomErrorResponses = map[int]string{
403: "Blocked by echo-guard",
}The engine skips ssrf scanning for address headers (host, x-forwarded-for, x-real-ip, ...) through its built-in default set (guardcore.DefaultExcludedHeaders, the reference's _DEFAULT_EXCLUDED_HEADERS), so no manual exclusion is needed. ExcludedDetectionHeaders only adds names on top of that default.
cfg.CustomResponseModifier runs on every pass-through response after the security and CORS headers, as the last step of the reference response pass, and CORS headers are applied again after it. Blocked responses already carry it from the engine. With a modifier configured the adapter holds the whole handler response in memory until the handler returns, so a streamed response is delivered at the end instead of incrementally; without one the response is untouched and streams as usual.
Distributed bans and rate limits require Redis:
cfg.EnableRedis = true
cfg.RedisURL = os.Getenv("REDIS_URL") // e.g. redis://redis:6379
cfg.RedisPrefix = "echo_guard:"Without Redis the managers fall back to in-process state, which does not share across replicas.
WithMaxBodyBytes bounds what the detector sees. Bodies larger than the
limit are still forwarded to your handler; only the inspected prefix is
truncated. The default matches the engine's MaxBodyInspectBytes (262144).
When behind a reverse proxy, trust only the proxy hop so the engine resolves the real client IP from forwarded headers:
cfg.TrustedProxies = []string{"172.16.0.0/12", "10.0.0.0/8"}
cfg.TrustedProxyDepth = 1The engine's behavioral surface is configurable through the global fields:
cfg.GlobalBehaviorRules = []guardcore.BehaviorRuleConfig{
{RuleType: "usage", Threshold: 100, Window: 3600, Action: "ban"},
}
cfg.BehaviorScanResponseBody = true
cfg.BehaviorMaxResponseBodyInspectBytes = 262144BehaviorScanResponseBody gates return-pattern rules that read the response
body (regex:, json:, or bare substring patterns; status: patterns work
without it). With the flag on, the adapter captures the leading
BehaviorMaxResponseBodyInspectBytes of every pass-through response body and
reports status code plus captured prefix to Engine.ProcessResponse after
your handler runs, mirroring the reference response factory's behavioral
phase. Return rules never modify the response. Note: a handler that returns
an Echo HTTPError is written by Echo's error handler after the middleware
returns, so the engine observes that response too late for return rules; have
the handler write the response itself when return-pattern rules matter.
Country rules resolve through the engine's geo lifecycle. A token enables the
full IPInfo download/refresh lifecycle; a database path keeps local-file
mode. OnGeoEvent receives country_blocked, geo_lookup_failed, and
decorator_violation events:
cfg.IPInfoToken = os.Getenv("IPINFO_TOKEN")
cfg.IPInfoMaxAge = 86400 // 0 falls back to the reference default
cfg.BlockedCountries = []string{"CN"}
cfg.OnGeoEvent = func(ev guardcore.GeoEvent) { ... }Route-level country rules (RouteConfig.BlockedCountries /
WhitelistCountries) emit the same events through the same hook.
Route configuration lives in the engine registry and reaches the middleware
through guardecho.WithRouteID (see Usage). Every
guardcore.RouteConfig field is reachable, including the per-route detection
exclusion surface:
engine.Routes.Register("search", func(rc *guardcore.RouteConfig) {
rc.ExcludedDetectionParams = map[string]bool{"q": true}
rc.ExcludedDetectionHeaders = map[string]bool{"referer": true}
rc.ExcludedDetectionBodyFields = map[string]bool{"note": true}
rc.EnabledDetectionCategories = []string{"xss", "sqli"}
scanOff := false
rc.DetectionScanBody = &scanOff
rc.BehaviorRules = []guardcore.BehaviorRuleConfig{
{RuleType: "usage", Threshold: 10, Window: 60, Action: "ban"},
}
})Set cfg.EnableCORS = true (plus any CORSAllowOrigins, CORSAllowMethods,
CORSAllowHeaders, CORSAllowCredentials, CORSExposeHeaders,
CORSMaxAge tuning). Preflights are short-circuited by the engine through
guardecho.New, blocked responses carry the CORS headers over the
security-header set, and the adapter merges
Engine.CORSResponseHeaders(req) into every pass-through response.
cfg.CustomErrorResponses replaces the body of any engine verdict status
code; the status code and security headers stay engine-owned.