Latest Results
fix(cache): enforce fail-closed Redis backend for distributed mode (closes #189)
Issue #189: RedisCache silently fell back to node-local VerificationCache
when Redis was unavailable, breaking distributed consistency and enabling
policy/rate-limit bypass by routing requests to cold nodes.
Root cause: RedisCache.__init__ unconditionally created a VerificationCache
fallback when _client is None, and get/set/invalidate/clear silently routed
to it. Infrastructure failure became an implicit security-mode downgrade.
Fix: Introduce explicit CacheBackendMode enum with three modes --
STRICT_DISTRIBUTED (new default for RedisCache and get_cache):
Redis unavailable at startup -> raises CacheBackendUnavailableError.
Redis error at runtime (get/set/invalidate/clear) -> raises
CacheBackendUnavailableError. No silent node-local fallback, ever.
Use this for rate limits, replay protection, policy gates.
EXPLICIT_DEGRADED (opt-in):
Redis unavailable -> activates VerificationCache fallback, but only
after emitting a structured JSON security event to qwed.cache.security
logger. Every result from the fallback carries _degraded_mode=True and
_cache_backend=local_degraded so callers and auditors can detect the
downgrade. Cross-node divergence becomes explicit, not silent.
LOCAL_ONLY:
Never touches Redis. Pure in-memory VerificationCache. Used by
cached_verify() (backward compatible -- its existing behavior was
already effectively local-only via silent fallback).
Additional changes:
- _emit_security_event(): structured JSON log (event, mode, tenant_id,
timestamp) emitted on BACKEND_UNAVAILABLE_AT_STARTUP and
BACKEND_UNAVAILABLE_AT_RUNTIME
- stats() now exposes mode and degraded fields for observability
- redis_config.py: add reset_redis_state() for test isolation
- VerificationCache: zero changes (backward compatible)
- cached_verify(): explicitly LOCAL_ONLY with doc comment explaining why
Tests: 25 new tests in tests/test_pr189_redis_failclosed.py covering
startup-down, runtime-loss, security event emission, JSON event validity,
_degraded_mode tagging, cross-node isolation documentation, get_cache()
default mode contract, and cached_verify() backward compatibility.
Full regression: 1198 passed, 82 skipped -- no regressions.fix/issue-189-redis-failclosed fix(cache): enforce fail-closed Redis backend for distributed mode (closes #189)
Issue #189: RedisCache silently fell back to node-local VerificationCache
when Redis was unavailable, breaking distributed consistency and enabling
policy/rate-limit bypass by routing requests to cold nodes.
Root cause: RedisCache.__init__ unconditionally created a VerificationCache
fallback when _client is None, and get/set/invalidate/clear silently routed
to it. Infrastructure failure became an implicit security-mode downgrade.
Fix: Introduce explicit CacheBackendMode enum with three modes --
STRICT_DISTRIBUTED (new default for RedisCache and get_cache):
Redis unavailable at startup -> raises CacheBackendUnavailableError.
Redis error at runtime (get/set/invalidate/clear) -> raises
CacheBackendUnavailableError. No silent node-local fallback, ever.
Use this for rate limits, replay protection, policy gates.
EXPLICIT_DEGRADED (opt-in):
Redis unavailable -> activates VerificationCache fallback, but only
after emitting a structured JSON security event to qwed.cache.security
logger. Every result from the fallback carries _degraded_mode=True and
_cache_backend=local_degraded so callers and auditors can detect the
downgrade. Cross-node divergence becomes explicit, not silent.
LOCAL_ONLY:
Never touches Redis. Pure in-memory VerificationCache. Used by
cached_verify() (backward compatible -- its existing behavior was
already effectively local-only via silent fallback).
Additional changes:
- _emit_security_event(): structured JSON log (event, mode, tenant_id,
timestamp) emitted on BACKEND_UNAVAILABLE_AT_STARTUP and
BACKEND_UNAVAILABLE_AT_RUNTIME
- stats() now exposes mode and degraded fields for observability
- redis_config.py: add reset_redis_state() for test isolation
- VerificationCache: zero changes (backward compatible)
- cached_verify(): explicitly LOCAL_ONLY with doc comment explaining why
Tests: 25 new tests in tests/test_pr189_redis_failclosed.py covering
startup-down, runtime-loss, security event emission, JSON event validity,
_degraded_mode tagging, cross-node isolation documentation, get_cache()
default mode contract, and cached_verify() backward compatibility.
Full regression: 1198 passed, 82 skipped -- no regressions.fix/issue-189-redis-failclosed Latest Branches
0%
fix/issue-189-redis-failclosed N/A
codspeed-wizard-1780238860317 © 2026 CodSpeed Technology