Skip to content

fix(cache): log a key digest, not the cache key, on backend failure - #314

Merged
allen0099 merged 1 commit into
masterfrom
fix/cache-log-key-digest
Sep 27, 2026
Merged

allen0099 merged 1 commit into
masterfrom
fix/cache-log-key-digest

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Summary

@cache's fail-open warnings ("Cache backend read failed" / "Cache backend write failed") logged the full cache key at WARNING. The key holds the raw query string (?token=..., ?code=..., e-mails), vary header values (#268) and any build_cache_key components (user IDs etc.).

  • New fastapi_cachex.types.log_ref(value): first 12 hex digits of SHA-256 (UTF-8, surrogatepass), which is exactly what _state_ref in state/manager.py did. _state_ref now delegates to it, so OAuth state refs and cache key refs use the same format.
  • cache.py: both warnings go through _log_backend_failure(), which logs method=... path=%r key_ref=... error=%r at WARNING and key_ref=... key=<full key> at DEBUG. The shared key_ref lets you match a warning to its key.
  • manager.py: CacheManager.get()'s "Failed to decode cached value" warning had the same problem, and developer keys often embed user IDs or e-mails. It now logs key_ref at WARNING and the key at DEBUG.
  • Docs: added a paragraph to "When the backend fails" in docs/HTTP_CACHING.md and its zh-TW mirror.
  • Fragment: changelog.d/299.security.md.

Path and log forging

The path is client input and is percent-decoded. It is logged with %r, so control characters come out escaped. Starlette's request.url already strips CR/LF/tab (urllib's urlsplit), so newline forging was not possible through this field. ANSI escapes (\x1b) and U+2028 did get through, and %r now escapes them. A test drives the ASGI app directly with such a path, because test clients normalise it. No other WARNING+ site logs the path; the existing DEBUG sites use path=%s and were left alone.

Audit of other log sites (INFO and above)

  • cache.py: the two sites above were the only WARNING+ calls. invalidate(), clear_path() and every other key log are DEBUG only.
  • routes.py, lock.py, proxy.py, backends (memory/redis/memcached): DEBUG only, nothing to change.
  • manager.py: fixed (above).
  • state/manager.py: already uses a digest (StateManager logs raw OAuth state values at WARNING #107).
  • session/manager.py: two WARNINGs about missing IP/UA binding, with no keys or values. Nothing to change.

DEBUG logging still writes full keys, query strings, session IDs and client IPs throughout. That is intended for local troubleshooting, as the issue proposes.

Tests

  • tests/test_cache_backend_failure.py: read and write failures (fail_open=True) with ?token=...&email=... and a vary=["X-Tenant"] value. At WARNING the token, e-mail, tenant value and ||| are absent, and method, path='/callback', error and the digest are present. At DEBUG the full key is present, and its SHA-256 matches key_ref. A second test covers the control-character path.
  • tests/test_cache_manager.py: the decode-failure warning carries key_ref, not the key, and DEBUG has the key.

ruff check, ruff format --check, mypy --strict: clean. pytest: 997 passed, 194 skipped (no live Redis/Memcached). Coverage is 93.77%; cache.py, manager.py and types.py are at 100%.

Closes #299

The read/write failure warnings wrote the full cache key, which holds
the raw query string, vary header values and custom key components.
They now log method, path (%r-escaped) and key_ref, a 12-hex SHA-256
digest shared with the OAuth state logs via types.log_ref; the full key
goes to DEBUG under the same key_ref. CacheManager.get()'s decode
warning gets the same treatment.
@allen0099
allen0099 force-pushed the fix/cache-log-key-digest branch from f02dfa5 to 5fc3ce1 Compare September 27, 2026 14:10
@allen0099
allen0099 merged commit 4ab7158 into master Sep 27, 2026
12 checks passed
@allen0099
allen0099 deleted the fix/cache-log-key-digest branch September 27, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@cache: backend failure warnings log the raw query string

1 participant