Cache respects TTL/expiration rules
Why it matters
A cache with no TTL enforcement retains data beyond its useful life — including sensitive content like user-specific feeds, prices, or session artifacts. CWE-922 (Insecure Storage of Sensitive Information) applies when cached data persists longer than the context that authorized it: a logged-out user's next session can read the previous user's cached content if the cache is never expired. ISO 25010:2011 reliability.recoverability is also impacted — stale data served from an indefinite cache is indistinguishable from fresh data, making correctness impossible to guarantee after server-side updates.
Severity rationale
Low because cache staleness primarily causes UX and correctness issues rather than direct exploitation, though indefinite retention of session-scoped data is a secondary security concern.
Remediation
Implement TTL checking on every cache read and a cleanup timer that evicts expired entries periodically. Cap TTLs at 86,400 seconds (24 hours) for any cached data.
interface Entry<T> { data: T; storedAt: number; ttl: number; }
export class CacheManager<T> {
private store = new Map<string, Entry<T>>();
constructor(private defaultTTL = 5 * 60_000) {
setInterval(() => this.sweep(), 60_000);
}
set(key: string, data: T, ttl = this.defaultTTL) {
this.store.set(key, { data, storedAt: Date.now(), ttl });
}
get(key: string): T | undefined {
const e = this.store.get(key);
if (!e) return undefined;
if (Date.now() - e.storedAt > e.ttl) { this.store.delete(key); return undefined; }
return e.data;
}
private sweep() {
const now = Date.now();
for (const [k, e] of this.store) {
if (now - e.storedAt > e.ttl) this.store.delete(k);
}
}
}
Always call cache.invalidate(key) immediately after a mutation rather than waiting for TTL expiry. On logout, call cache.clear() to prevent the next session from reading the previous user's data.
Detection
-
ID:
cache-expiration -
Severity:
low -
What to look for: Examine cache implementation. Look for TTL configuration, expiration checks, or cleanup logic. Verify cached data is refreshed periodically and doesn't persist indefinitely.
-
Pass criteria: Count all cache entry creation points. Cache entries have a configurable TTL of no more than 86400 seconds (24 hours). Expired entries must not be served to the user. Old cache is cleaned up periodically or on app launch.
-
Fail criteria: Cache has no expiration. Could serve outdated data indefinitely.
-
Skip (N/A) when: Never — cache expiration is important for data freshness and security.
-
Detail on fail:
"Cache has no TTL. User data cached on first load persists indefinitely even if user logs out or data changes remotely." -
Remediation: Implement TTL with periodic cleanup:
interface CacheEntry<T> { data: T; timestamp: number; ttl: number; key: string; } export class CacheManager<T> { private entries = new Map<string, CacheEntry<T>>(); private cleanupInterval: NodeJS.Timeout | null = null; constructor(private defaultTTL = 5 * 60 * 1000) { this.startCleanupTimer(); } set(key: string, data: T, ttl = this.defaultTTL) { this.entries.set(key, { data, timestamp: Date.now(), ttl, key, }); } get(key: string): T | null { const entry = this.entries.get(key); if (!entry) return null; const isExpired = Date.now() - entry.timestamp > entry.ttl; if (isExpired) { this.entries.delete(key); return null; } return entry.data; } private startCleanupTimer() { // Clean up expired entries every minute this.cleanupInterval = setInterval(() => { const now = Date.now(); for (const [key, entry] of this.entries.entries()) { if (now - entry.timestamp > entry.ttl) { this.entries.delete(key); } } }, 60 * 1000); } destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } } }
External references
- cwe · CWE-922 — Insecure Storage of Sensitive Information
- iso-25010:2011 · reliability.recoverability — Recoverability
Taxons
History
- 2026-04-18·v1.0.0·Initial import from mobile-offline-storage·automated