Security Policy
Supported Versions
Only the latest release receives security fixes. Older versions are not maintained.
Reporting a Vulnerability
Do not open a public GitHub issue for security vulnerabilities.
Report security issues to flaviocfo@gmail.com with:
- A description of the vulnerability and its potential impact
- Steps to reproduce or a minimal proof-of-concept
- Any suggested mitigations you are aware of
You will receive an acknowledgement within 72 hours. We aim to release a fix within 14 days for critical issues and 30 days for others. We will credit you in the release notes unless you prefer to remain anonymous.
Resolved Findings (v1.0.0)
The router has been audited across nine specialist sprints (S1..S9) plus
two pre-release mini-sprints (S10-PreCSA, S10-PreMSR) covering 95+
findings and 51 explicit threat-model hypotheses (TM-2026-001..051).
The full evidence is preserved under
/reports/ — every harness is reproducible with
go test -race.
The four findings that materially gated the v1.0.0 release are listed here for operator awareness. All are fixed in code at the v1.0.0 tag; this section exists so a security-conscious adopter can verify by ID that the issue is closed.
| ID | Sev | Class | Summary | Fix location | Status |
|---|---|---|---|---|---|
| CSA-2026-0060 | 8 | CWE-440 / CWE-863 | ParamsFromContext() silently returned empty params when any Use()-registered middleware wrapped the request context (Timeout, WithValue, RealIP). An auth handler comparing :userID to a JWT subject would see "" and could grant or deny access incorrectly. |
params.go:357-378 — slow-path ctx.Value fallback gated by reflection-discovered hasReqCtxField |
FIXED |
| HPS-2026-0005 | 7 | CWE-601 | When the request line used absolute-form URI (RFC 7230 §5.3.2, e.g. GET http://evil.com/x HTTP/1.1), RedirectTrailingSlash and RedirectFixedPath echoed the attacker-controlled scheme + host into the Location header — open redirect. |
mux.go:945, 960 — Location is now built from a path-only url.URL so the scheme + host can never originate from request input |
FIXED |
| FPE-2026-010 | 6 | CWE-693 / CWE-863 | Calling Mux.Use(authMW) followed by Mux.HandleFast(...) silently registered a fast route with NO middleware applied. Use()'s GoDoc explicitly claimed this combination panics — but the panic guard from CSA-2026-0054 was only wired to Group.HandleFast, not root Mux.HandleFast. |
mux.go:435-445 — root HandleFast panics when Use()-registered middleware is present, mirroring Group.HandleFast |
FIXED |
| TM-2026-005 | 4 | CWE-532 | The construction-time slog.Warn issued when OAuth2Introspect is configured with AllowInsecureEndpoint: true logged the full endpoint URL — including any credentials embedded in the query string. |
middleware/oauth2.go:229-233 — log host + scheme only, never the full URL |
FIXED |
Audit-trail breakdown
- S1..S6: initial security battery covering SAST, supply-chain, HTTP protocol, path-routing, DoS, concurrency, middleware, timing.
- S7: focused fuzzing + property-based test infrastructure.
- S8: delta-driven re-validation of S1..S7 closures with new harnesses.
- S9: pre-release Onda 2 consolidation (95+ findings, 51 hypotheses, three composite-attack hypotheses TM-COMPOSITE-2026-001..003).
- S10-PreCSA / S10-PreMSR: S9 follow-up to close the
budget-exhaustion gaps. 9 of 9 UNTESTED concurrency hypotheses
(TM-007/008/019/025/027/028/036/037/045) REFUTED under
-race -count=3. 6 of 6 middleware hypotheses (TM-001/002/004/005/022/044) closed (4 REFUTED, 2 documented as defaults requiring operator opt-in).
The maturity verdict (production-readiness for high-load, stress, and
high-concurrency environments) is recorded in
/reports/overview/2026-05-08-final-maturity-verdict.md.
Operator-facing defaults requiring opt-in
Three middleware components ship with backwards-compatible defaults
that are unsafe in production. Each emits a slog.Warn at construction
time when the unsafe default is in effect; search startup logs for these
warnings.
| Middleware | Unsafe default | Safe production setting | Finding ID |
|---|---|---|---|
JWTAuth |
RequireExpiry: false (no exp ⇒ replayable) |
RequireExpiry: true (RFC 8725 §4.4) |
TM-2026-001 |
RealIP() |
called with no CIDRs | RealIP(&proxyCIDR) with explicit trusted CIDR list |
TM-2026-044 |
OAuth2Introspect |
AllowInsecureEndpoint: true |
leave false — HTTPS endpoint only |
MSR-2026-0067 |
The README "Security defaults" section reproduces this matrix and the hardened-stack snippet.
Thread-Safety Contract (MM-2026-0017 / CSA-2026-0052)
All public Mux fields (PanicHandler, NotFound, MethodNotAllowed,
GlobalOPTIONS, ErrorHandler, RedirectTrailingSlash, RedirectFixedPath,
CaseInsensitive, UseRawPath, UnescapePathValues, RedirectCode,
HandleMethodNotAllowed, HandleOPTIONS) must be set before the first
call to ServeHTTP. On the first request these values are atomically
captured into a frozen muxConfig snapshot and every subsequent dispatch
reads from that snapshot — direct field mutation after serving begins is
ignored by the dispatch path and races with the snapshot's first read.
To reconfigure handlers after serving starts, mutate the field and then call
Mux.Rebuild(). Rebuild() atomically resets the snapshot and the lazy
NotFound/405/OPTIONS handler caches so the next request re-reads every
field. Rebuild() is safe to call concurrently with ServeHTTP.
Use() and Pre() are safe to call concurrently with Handle() during
route registration (before serving), but must not be called concurrently with
active requests.
Timeout Middleware (MM-2026-0019)
middleware.Timeout cancels the request context after the configured duration.
Handlers must actively check ctx.Done() (or use context-aware I/O) to be
preempted. Handlers that ignore the context will continue running until they
return, regardless of the timeout — goroutine exhaustion is possible if handlers
block indefinitely.
Example of a cooperative handler:
func myHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case result := <-doWork(ctx):
w.Write(result)
case <-ctx.Done():
http.Error(w, "request timeout", http.StatusGatewayTimeout)
}
}
Slowloris / Server Timeouts (MM-2026-0024)
MuxMaster is an http.Handler and does not configure the underlying
http.Server. To mitigate Slowloris and similar attacks, always set timeouts
on your server:
srv := &http.Server{
Handler: mux,
ReadHeaderTimeout: 30 * time.Second,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 120 * time.Second,
MaxHeaderBytes: 1 << 20, // 1 MiB
}
Accepted Known Limitations
Route-Existence Timing Oracle (MM-2026-0026)
The timing difference between a 404 response (path not in tree) and a 405 response (path exists, wrong method) is ~440 ns. This is intrinsic to radix tree lookup and is present in httprouter, chi, and bunrouter as well. If this is a concern, use a WAF or add uniform response delays via middleware.
Path normalisation accepted behaviour (PRF-2026-0001..0005)
The following routing behaviours are intentional and documented as operator responsibilities — they are not router defects:
%61dminmatches/adminwhenUseRawPath=false(the default).net/httpdecodes%61toaduring URL parsing per RFC 3986 §6.2.2.2, so the router sees/admin. To enforce byte-exact path matching setr.UseRawPath = true— patterns then match againstr.URL.RawPath, which preserves the percent-encoded form (PRF-2026-0002).
UseRawPath traversal (PRF-2026-0002 / CDX-S8-002)
When BOTH UseRawPath = true AND UnescapePathValues = true, %2f
inside a single path segment is matched as one segment by the radix tree
(because / is preserved as the path separator only via a literal
slash) and is then DECODED in the captured param value. A request such
as /files/..%2fetc%2fpasswd against the route /files/:filepath
binds :filepath to the literal string ..\x2fetc\x2fpasswd — i.e. the
captured value contains a real slash.
Handlers that pass ParamsFromContext(...).ByName("filepath") to
os.Open, http.FileServer, or any URL/file API WITHOUT calling
path.Clean (and rejecting values that contain ..) are vulnerable to
directory traversal. The clean_path middleware does NOT normalise
post-decode values; it only canonicalises the request path before
dispatch.
Mitigation. Choose one of:
- Leave
UnescapePathValues = false(default) and let the handler callurl.PathUnescapeonly afterpath.Cleanand a..check. - Use the safe pattern in
examples/static-site/which callspath.Cleanon the param BEFORE filesystem dispatch and rejects paths whose cleaned form starts with...
When UseRawPath and UnescapePathValues are both set, MuxMaster
emits a one-time slog.Warn at the first Handle/HandleFast call
to make the misconfiguration visible in startup logs. Additionally,
ServeFiles PANICS at registration when both flags are set, since
http.FileServer would treat decoded slashes as path separators inside
the static-file root (CDX-S8-002).
-
Catch-all
*filepathparameters carry raw bytes, including any..traversal sequences. The router does NOT sanitise catch-all values — that is the boundary between router and storage backend. Handlers serving files MUST callpath.Cleanand must reject paths that escape their root (e.g. viafilepath.IsLocalor by checkingfilepath.Rel(root, joined)).Mux.ServeFilesalready delegates tohttp.FileServerwhich performs path cleaning (PRF-2026-0005). -
Static routes must be registered before sibling wildcards. Calling
r.GET("/users/:id", h)and thenr.GET("/users/active", h)panics because:idalready claims the wildcard slot at that depth. Register the more-specific static route first; this matches httprouter's behaviour (PRF-2026-0003). -
RedirectFixedPath=truediscloses route existence via the redirect status. The default isfalseprecisely because path cleaning before dispatch can convert non-existent paths into observable hits. Leave the default unless you understand the disclosure trade-off (PRF-2026-0004). -
middleware.CleanPath(registered viaPre) normalises dot segments before dispatch. It does NOT bypass authentication — middleware registered viaUsestill wraps the dispatched handler — but it can cause requests to land on a different handler than the raw path suggests. CleanPath is intended for clients that emit/foo/../barand similar; do not use it on endpoints where the literal path is semantically meaningful (PRF-2026-0001).
OAuth2 introspection cache poisoning (MSR-2026-0063)
The OAuth2Introspect middleware caches the introspection response
(keyed by sha256 of the bearer token) for CacheTTL seconds. If the IDP
revokes a token mid-cache-window, MuxMaster continues to honour the
cached active=true response until the cache entry expires — a blast
radius of up to CacheTTL. For high-security endpoints, set
OAuth2Options.CacheTTL = -1 to disable caching entirely; every request
will hit the introspection endpoint, but the singleflight group
(DOS-OAUTH2-001 fix) coalesces concurrent calls for the same token, so
the IDP load growth is bounded by distinct-token concurrency rather
than total request rate.
Timeout middleware preemption (DOS-2026-0003)
middleware.Timeout cancels the request context after the configured
duration but does NOT preempt the handler goroutine. Go has no
preemption primitive for blocked syscalls — a handler that ignores
ctx.Done() will continue running to completion, regardless of the
timeout. Under load this accumulates goroutines and exhausts memory or
upstream connections.
Handlers MUST observe ctx.Done() on every blocking call (DB, network,
file I/O). Use the *Context variants of stdlib APIs (sql.DB.QueryContext,
HTTP request bodies via r.Context(), etc.).
Compress sniff-buffer per-connection memory (DOS-2026-0007)
middleware.Compress buffers up to 8 KiB per stalled connection while
sniffing whether the response body is large enough to be compressed.
Total memory under attack is N × 8 KiB for N concurrent stalled
connections. The middleware does not enforce a per-connection timeout;
operators MUST set http.Server.ReadHeaderTimeout,
http.Server.WriteTimeout and a connection cap on the listener to
bound this exposure.
Reflect-based ctx field offset (MM-2026-0035)
The tiered reqBundle optimisation (1 alloc per request with parameters)
relies on unsafe.Add over the offset of the private ctx field of
*http.Request, derived once via reflection at init(). If a future Go
version removes or renames the field, the offset cannot be resolved and
MuxMaster automatically falls back to r.WithContext (2 allocs per
request) without crashing. The fallback is exercised in
reports/concurrency-security-auditor/harness/h018_reqctx_offset_test.go
as part of the CSA harness. CI should cross-build against gotip
periodically to surface drift before a stable Go release.
RealIP misconfiguration (MSR-2026-0055)
middleware.RealIP() called with no trusted-proxy CIDR list trusts every
peer — any client can spoof X-Forwarded-For / X-Real-IP and the router
will accept it as the real client IP. This is only safe behind a single
trusted proxy that strips inbound XFF; in any other deployment it is a
trivial spoofing primitive that defeats ThrottlePerIP and IP-based
access controls. Always pass the proxy CIDR list explicitly:
proxyCIDR := netip.MustParsePrefix("10.0.0.0/8")
mux.Use(middleware.RealIP(&proxyCIDR))
A slog.Warn is emitted at construction time when RealIP() is called
without CIDRs.
RealIP + ThrottlePerIP ordering (DOS-2026-0002)
middleware.ThrottlePerIP with a nil keyFn keys on r.RemoteAddr. If
RealIP is not registered (or is registered AFTER ThrottlePerIP), every
request behind a reverse proxy carries the LB's own address as
RemoteAddr and the per-IP limit collapses to a global rate limit. Always
register RealIP first so r.RemoteAddr reflects the true client IP
before throttling decisions are made:
mux.Use(middleware.RealIP(&proxyCIDR)) // first
mux.Use(middleware.ThrottlePerIP(50, ts, nil)) // then
A slog.Warn is emitted at construction time when ThrottlePerIP is
called with a nil keyFn.
Accepted Timing Oracles (TSC-2026-0001..0007)
The timing-and-side-channel analyst sprint catalogued seven sub-microsecond to low-microsecond timing differences. The following are accepted as architectural or stdlib-derived; mitigation requires either Go runtime changes (out of MuxMaster's scope) or invasive padding that would degrade valid-request latency. Operators concerned about LAN-adjacent statistical attacks should rate-limit aggressively and monitor for prefix-scan probes.
-
TSC-2026-0001 (BasicAuth valid vs invalid password, 890 ns). The
map[string][32]bytecredential lookup usesruntime.mapaccess2_faststrwhich is not constant time. The post-auth code path (next.ServeHTTPvshttp.Error+WWW-Authenticate) also dominates the visible delta. Constant-time comparison of the hashed credentials is already used; the remaining oracle is the map shape. -
TSC-2026-0002 (BasicAuth user-exists vs not-exists, 61 ns). Same root cause as TSC-0001 — the hashedCreds map lookup leaks existence. Effect size is sub-microsecond and impractical over WAN.
-
TSC-2026-0004 (APIKey hit vs miss, 1141 ns). The
map[[32]byte]stringlookup leaks key existence with a similar magnitude. A constant-time alternative requires iterating every registered key withsubtle.ConstantTimeCompare, which is O(n) per request — only worthwhile for very small key sets. -
TSC-2026-0005 (Route existence, 923 ns) — MM-2026-0026 magnitude update. Registered vs unregistered paths take measurably different time inside the radix tree. Already documented as the "Route-Existence Timing Oracle" earlier in this file.
-
TSC-2026-0006 (ECDSA zero-sig vs random-sig, 1234 ns). Stdlib
ecdsa.Verifyreturns at slightly different times depending on signature shape. The signature is rejected either way; the oracle only reveals that the signature is zero, which is publicly observable in the request anyway. -
TSC-2026-0007 (OAuth2 cache hit active vs inactive, 143 µs). The 401 vs 200 response paths differ in execution length by design. The timing difference reveals nothing beyond what the HTTP status code already exposes.
JWT Mixed-Family Algorithms (TSC-2026-0003)
JWTAuth configured with HS* and RS*/ES* algorithms in the same
Algorithms list leaks the algorithm code-path via response latency
(HMAC verifies in ~1 µs, RSA-2048 in ~300 µs). An attacker submitting
tokens labelled with different alg values can determine which path the
server runs from the response time alone, narrowing the attack surface for
algorithm-confusion attacks (RFC 8725 §3.1). Configure each endpoint with
a single algorithm family. Mixed-family configuration emits a slog.Warn
at construction time.
BasicAuth Brute-Force (MM-2026-0027)
middleware.BasicAuth does not rate-limit authentication attempts. Compose it
with middleware.ThrottlePerIP to mitigate online brute-force:
mux.Use(
middleware.ThrottlePerIP(10, time.Second, nil),
middleware.BasicAuth("realm", creds),
)
BREACH Compression Oracle (MM-2026-0030 / DOS-2026-0006)
middleware.Compress does not mitigate the BREACH attack. Confirmed in
reports/dos-resilience-tester/harness/breach_oracle_test.go with a Cohen's
d effect size of ~10.3 — an attacker controlling one URL/query parameter
that is echoed alongside a secret in a gzip-compressed response can recover
each character of the secret with ~2 requests on average.
Mitigations, in decreasing order of safety:
-
Do not compress endpoints that echo user-controlled input near secrets. The simplest fix: register
middleware.Compressonly on routes that do not echo attacker-controlled data into the body, or build a separate middleware chain for sensitive endpoints. -
Move secrets out of the response body. Put OAuth2 scopes, CSRF tokens, session IDs and JWTs in headers, cookies, or dedicated endpoints that are never reachable via attacker-controlled input.
-
Variable-length random padding. If 1 and 2 are not feasible, append a random-length (>= 256 bytes, length randomised per request) random payload to the response body. Validated by
TestBREACHOracleWithRandomPadding: with this scheme the oracle's Cohen's d drops below 0.03 (negligible). Fixed-length padding is not sufficient — the random content compresses to similar sizes per request.
Example mitigation pattern (variable-length random padding):
func tokenInfo(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
padN := 256 + rand.Intn(256) // length itself is randomised
pad := make([]byte, padN)
_, _ = rand.Read(pad)
body := fmt.Sprintf(`{"scope":"%s","query":"%s","pad":"%x"}`,
oauthScope, q, pad)
_, _ = io.WriteString(w, body)
}
MuxMaster cannot apply these mitigations on the operator's behalf because they require domain knowledge of which response fields are secret vs user-controlled.
Registration Panics Leave Tree Inconsistent (MM-2026-0033)
If Handle (or any route registration method) panics — for example due to a
route conflict — the radix tree may be in an inconsistent state. Do not catch
and ignore registration panics. Let them crash main() so the inconsistency
is discovered during development, not in production.
Recoverer Must Be Outermost (MM-2026-0034)
middleware.Recoverer (or RecovererWithLogger) only catches panics in
middleware and handlers registered inside it. Register it as the outermost
middleware — or use r.Pre(middleware.RecovererWithLogger(logger)) — to
ensure it wraps the full dispatch chain.
Composite token-handling stack (CDX-S8-001)
A bearer-token endpoint that combines OAuth2 introspection (or JWT) with
per-IP throttling and reverse-proxy IP forwarding has FOUR distinct
attack surfaces. Defaulting any one to "off" is exploitable end-to-end:
a passive observer captures a bearer token over plaintext HTTP, replays
it indefinitely (no exp), and forges X-Forwarded-For to bypass per-IP
throttle. The hardened stack flips ALL of the following invariants ON:
| # | Invariant | Mechanism | Default |
|---|---|---|---|
| 1 | Introspection endpoint MUST be HTTPS | OAuth2Options.Endpoint panics on non-HTTPS |
enforced |
| 2 | JWT tokens MUST carry exp |
JWTOptions.RequireExpiry = true |
OFF (opt-in) |
| 3 | XFF must be parsed RIGHTMOST past trusted CIDRs | RealIP(trustedCIDRs...) walks rightmost-leftward |
enforced when len(trustedCIDRs)>0 |
| 4 | Per-IP table size must be CAPPED | ThrottlePerIPCapped (default 100_000) |
enforced via ThrottlePerIP wrapper |
A configuration that inadvertently leaves any of these OFF (e.g. opens
AllowInsecureEndpoint "for testing", omits RequireExpiry, calls
RealIP() with no CIDRs, or builds a custom throttle without a cap)
is exploitable. The recommended pattern is:
// Hardened token-handling stack — see CDX-S8-001 / SECURITY.md
// "Composite token-handling stack".
trusted, _ := netip.ParsePrefix("10.0.0.0/8")
mux.Pre(
middleware.RealIP(&trusted), // (3) rightmost XFF
)
mux.Use(
middleware.ThrottlePerIP(100, 5*time.Second, nil), // (4) capped per-IP
middleware.JWTAuth(middleware.JWTOptions{
Secret: secret,
Algorithms: []string{"HS256"},
RequireExpiry: true, // (2) reject no-exp
}),
// OR use OAuth2Introspect with HTTPS endpoint:
// middleware.OAuth2Introspect(middleware.OAuth2Options{
// Endpoint: "https://idp.example/introspect", // (1) HTTPS only
// ...
// }),
)
examples/jwt/ and examples/oauth2/ demonstrate the full pattern
with comments cross-referencing CDX-S8-001.
Layered panic recovery (CSA-2026-0058 / CSA-2026-0059)
MuxMaster has THREE layers that may catch panics. Understanding the order is essential when configuring PanicHandler and middleware.Recoverer.
| Layer | What it catches | What it does on a second panic |
|---|---|---|
Mux.PanicHandler |
Panics in handler / Use / UseFast chain. |
NOT recovered by MuxMaster. |
middleware.Recoverer (Pre) |
Panics anywhere downstream including the | Catches if PanicHandler panicked. |
| first PanicHandler invocation. | ||
net/http per-conn recover |
Anything that escapes both above. | Logs "http: panic serving ..." and closes TCP. |
Boundary rules.
Mux.PanicHandlerMUST NOT panic. If it does, the secondary panic propagates to the next layer (Recoverer inPre(), or net/http) and the connection is terminated mid-response. There is no goroutine leak and no process crash, but the client sees a reset stream — confusing for HTTP/2 multiplexing and reverse-proxy retries.- To make recovery FULLY symmetric for both stdlib and FastHandler
routes, register
RecovererWithLoggerviar.Pre(...). Pre wraps ALL dispatch including HandleFast, so even a panic in fast routes (whichUse-registered Recoverer cannot catch — see CSA-2026-0054) is contained. Mux.PanicHandlerruns INSIDE the dispatch frame and covers both stdlib and FastHandler routes; it is the single, route-type-agnostic recovery point if you do not want to register a Pre middleware.
Pre vs Use security boundary (CSA-2026-0059 / H8-01)
Mux.Pre() and Mux.Use() register middleware in different positions
of the dispatch pipeline. Mistaking one for the other has direct
authentication/authorisation consequences when HandleFast routes are
also in play.
| Middleware family | Wraps Handle (stdlib)? |
Wraps HandleFast? |
Mode |
|---|---|---|---|
r.Pre(...) |
YES | YES | Outside dispatch — http.Handler. |
r.Use(...) |
YES | NO — panics at register. | Inside dispatch — http.Handler. |
r.UseFast(...) |
NO | YES | Inside dispatch — FastMiddleware. |
Implications.
- An auth gate (e.g.
JWTAuth) registered viar.Use(...)does NOT coverHandleFastroutes — MuxMaster panics atHandleFastregistration to expose this immediately (CSA-2026-0054). Either (a) register the auth viar.Pre(...)so it covers BOTH route types, or (b) duplicate the auth as aFastMiddlewareand register it viar.UseFast(...). - Because
Preruns OUTSIDE the dispatch (inMux.ServeHTTPbefore the radix-tree lookup), it sees the path BEFORE any route-specific handler decision — useful forCleanPath,RealIP,RecovererWithLogger, request ID assignment, or any policy that must be uniform across the router. Useruns INSIDE the dispatch (after the route is matched) and is baked into the wrapped handler at registration time. Its only path to a fast route is viaUseFast.
Operators auditing an auth/CORS/rate-limit policy by reading Use(...)
calls SHOULD also inspect HandleFast(...) registrations and
UseFast(...) calls; otherwise a fast-route bypass is invisible.
HTTP/1.1 Smuggling (MM-2026-0045)
Request smuggling (CL.TE / TE.CL / TE.TE) is defended by Go's net/http
package at the framing layer. No action is required at the MuxMaster level.
Verified clean against standard smuggling test suites.
Error Oracle (MM-2026-0046)
Differential error responses (404 vs 405 vs 301) are intentional HTTP
semantics and are present in all HTTP routers. If normalising error responses
is required for your threat model, use a WAF or a custom NotFound /
MethodNotAllowed handler.
ThrottlePerIPCapped saturation (TM-2026-013, DOS-2026-0057) — ACCEPTED
When the per-IP throttle table reaches maxTableSize and every slot is
in active use (refs > 0), new client IPs receive HTTP 503 immediately.
An attacker controlling at least maxTableSize distinct IPs (default
100 000) and keeping their requests open can sustain this lockout for
as long as the connections remain.
Reproduction: reports/dos-resilience-tester/harness/s9_dos_test.go:TestThrottlePerIPCappedSaturationHoldout.
Required operator mitigations:
- Deploy upstream DDoS scrubbing (Cloudflare, AWS Shield, GCP Cloud Armor).
- Configure
http.Server{ReadHeaderTimeout, IdleTimeout, ReadTimeout}so slow-handler connections cannot hold throttle slots indefinitely. - Reduce
maxTableSizefor high-sensitivity endpoints — the cap intentionally trades fairness for memory safety.
This is accepted behaviour: the cap exists precisely to prevent
unbounded memory growth under IP-churn attacks. See
/reports/dos-resilience-tester/harness/s9_dos_test.go for the
evidence and MSR-2026-0068 for the cooperative refs-decrement fix
that ensures the table drains correctly when timed-out requests release.
Upstream source
The security-sensitive defaults discussed above (Pre/Use ordering for Recoverer and RealIP, RequireExpiry on JWTAuth, the OAuth2 endpoint contract, the conditional-GET handling) are implemented across mux.go, handler.go, and response.go in the upstream repository.