Problem
The guard decision cache (_decision_cache in guard.py) keys entries on (badge_jws, tool_name) only. However, evaluate_tool_access() decisions also depend on:
params_hash
server_origin
policy_version
capability_class / deny_on_unknown_class
- Auth mode (badge vs API key vs anonymous)
When badge_jws is empty (API-key or anonymous callers), all such requests share the same cache bucket, which can return incorrect decisions.
Additionally, cached results reuse evidence_id / evidence_json, skipping the per-call evidence guarantee.
Current mitigation
The 5-second TTL limits the blast radius. In practice, badge-based auth (the primary path) produces unique JWS strings per identity, so collisions are unlikely for the main use case.
Proposed fix
Either:
- Expand the cache key to include
(badge_jws or api_key or "anon", tool_name, params_hash, server_origin) and generate fresh evidence IDs on cache hits
- Skip caching entirely when
badge_jws is absent
Context
Identified during PR #32 code review (Copilot review comments on guard.py:360).
Problem
The guard decision cache (
_decision_cacheinguard.py) keys entries on(badge_jws, tool_name)only. However,evaluate_tool_access()decisions also depend on:params_hashserver_originpolicy_versioncapability_class/deny_on_unknown_classWhen
badge_jwsis empty (API-key or anonymous callers), all such requests share the same cache bucket, which can return incorrect decisions.Additionally, cached results reuse
evidence_id/evidence_json, skipping the per-call evidence guarantee.Current mitigation
The 5-second TTL limits the blast radius. In practice, badge-based auth (the primary path) produces unique JWS strings per identity, so collisions are unlikely for the main use case.
Proposed fix
Either:
(badge_jws or api_key or "anon", tool_name, params_hash, server_origin)and generate fresh evidence IDs on cache hitsbadge_jwsis absentContext
Identified during PR #32 code review (Copilot review comments on
guard.py:360).