mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-17 04:37:48 +00:00
fix(security): close confirmed findings from the 2026-10-15 hunt (#166)
Seven findings are fixed. Each fix has a regression test. - Compare X-Forwarded-Host and the serving host case-insensitively, so a case variant cannot widen the session cookie Domain. - Reject a future iat on the opaque introspection path. - Serve the cached parsed JWKS inside the signature-refresh cooldown. - Release the half-open circuit slot when a probe panics. - Cap requests that wait for provider initialisation at 512. Extra requests receive 503 with Retry-After. - Cap cookie deletion headers at 32 cookie names per response. - Cap front-channel sid markers at 1000, so unauthenticated requests cannot evict backchannel or jti replay markers.
This commit is contained in:
@@ -51,8 +51,43 @@ const (
|
||||
// sessionInvalidationTTL is how long to remember invalidated sessions
|
||||
// Should be at least as long as your session max age
|
||||
sessionInvalidationTTL = 25 * time.Hour
|
||||
|
||||
// maxFrontchannelMarkers caps the sid markers the unauthenticated
|
||||
// front-channel endpoint may keep in the shared invalidation cache, so
|
||||
// a flood cannot LRU-evict signed backchannel and jti replay markers.
|
||||
maxFrontchannelMarkers = 1000
|
||||
)
|
||||
|
||||
// markerQuota is a FIFO of cache keys written on behalf of one untrusted
|
||||
// source. The zero value is ready to use.
|
||||
type markerQuota struct {
|
||||
set map[string]struct{}
|
||||
keys []string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// admit records key and returns the oldest key to drop once more than limit
|
||||
// keys are held, or "" when none must go. Re-admitting a held key is a no-op.
|
||||
func (q *markerQuota) admit(key string, limit int) string {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if q.set == nil {
|
||||
q.set = make(map[string]struct{})
|
||||
}
|
||||
if _, ok := q.set[key]; ok {
|
||||
return ""
|
||||
}
|
||||
q.set[key] = struct{}{}
|
||||
q.keys = append(q.keys, key)
|
||||
if len(q.keys) <= limit {
|
||||
return ""
|
||||
}
|
||||
oldest := q.keys[0]
|
||||
q.keys = q.keys[1:]
|
||||
delete(q.set, oldest)
|
||||
return oldest
|
||||
}
|
||||
|
||||
// LogoutTokenClaims represents the claims in an OIDC logout token
|
||||
// as defined in OpenID Connect Back-Channel Logout 1.0
|
||||
type LogoutTokenClaims struct {
|
||||
@@ -191,6 +226,10 @@ func (t *TraefikOidc) handleFrontchannelLogout(rw http.ResponseWriter, req *http
|
||||
return
|
||||
}
|
||||
|
||||
if evicted := t.frontchannelMarkers.admit(t.buildSessionInvalidationKey("sid", sid), maxFrontchannelMarkers); evicted != "" {
|
||||
t.sessionInvalidationCache.Delete(evicted)
|
||||
}
|
||||
|
||||
t.logger.Infof("Front-channel logout: successfully invalidated session (sid=%s)", sid)
|
||||
|
||||
// Return a minimal HTML response that's suitable for iframe loading
|
||||
|
||||
Reference in New Issue
Block a user