Audits whether a cache key carries every discriminator the cached response actually depends on, so two requests that must not share a slot cannot collide. Ranks identity discriminators (tenant, user, authorization scope, plan tier) above presentation ones (locale, region, currency, feature flag), maps each missing discriminator to the data-exposure or wrong-content consequence it causes, classifies each key-and-value pair into a critical / high / medium severity band (including the Python lru_cache-on-an-instance-method trap), and writes the fix as a namespaced key builder plus the matching HTTP Vary header. Use when a cache key is being designed or changed, when a per-user or per-tenant response is about to be stored in a shared cache or CDN, or when investigating a report that one user or tenant saw another's data.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
The common folk description of this bug is that self is not part of
the cache key, so all instances share one entry. That is wrong, and the
real mechanism matters because it changes the fix.
What the Python documentation actually says:
functools.cached_property "stores results at the instance level"
and functools.lru_cache stores them "at the class level"
(Python FAQ, how do I cache method calls?).self instance argument is included in
the cache"
(functools docs).lru_cache "creates a reference to the instance unless special
efforts are made to pass in weak references", and "instances are kept
alive until they age out of the cache or until the cache is cleared"
(Python FAQ).So the entry is keyed on the whole argument tuple, self included, in
one cache that lives on the class. Whether two instances collide
therefore depends entirely on how self hashes:
id()"
(Python glossary, hashable).
Identity-based hashing means each instance gets its own entries. The
defect here is retention, not collision: a class-level cache pins every
request-scoped object it has seen until the entry ages out of maxsize.__eq__
and __hash__ opts into value-based hashing. If the fields that
participate in equality omit the tenant, then a repository object for
tenant A and one for tenant B compare equal, hash equal, and share
every cached entry. That is a genuine cross-tenant collision, and it is
invisible in the key expression because the collision happens inside
__hash__.Fixes, in order of preference:
functools.cached_property when the value is per-instance and
takes no arguments. It stores at the instance level and does not
create a reference to the instance, so the result is released with the
instance
(Python FAQ).