mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-17 17:18:28 +08:00
* fix(auth): bound the three unbounded session-policy caches security-policy.ts and session-policy.ts are read from Better Auth's session create and update hooks, so they run on every session validation. All three caches were plain Maps with a hand-rolled 'Date.now() - fetchedAt < TTL' read and no ceiling: entries were released only by an explicit invalidate, so they grew for the life of the process. membershipCache is the sharpest of the three because it is keyed by user, not organization — one entry per user who ever authenticated on that instance. Move all three to LRUCache, already a direct dependency and the pattern copilot/entitlements.ts and providers/client-cache.ts use. The library owns the TTL and the ceiling; every existing invalidate* keeps working unchanged. Two things to preserve, both now pinned by tests: - membership results keep their asymmetric TTL (a non-member result expires far sooner, so a user who joins through a path this codebase never sees cannot dodge the new org's policy). Expressed as membershipCacheTtlMs rather than an inline ternary, since it is a security property and not a tuning knob. - reads test '!== undefined', because a version is a number and a membership is nullable — a truthiness check would treat both as a miss. security-policy.ts had no test file; adds one covering caching, invalidation, failure fallbacks and the TTL asymmetry. * fix(auth): raise the cache ceilings to a memory backstop getSessionCookieCacheVersion feeds Better Auth's session.cookieCache.version, so these are read on every session read, not just create/refresh. At max: 1000 a busy instance could exceed the live key set inside the 60s TTL and start evicting early — never a wrong answer (a miss is one indexed lookup, exactly the pre-cache behaviour) but a hit-rate cliff on a hot path. Entries are a few dozen bytes, so headroom is nearly free: orgs 1k -> 20k, users 10k -> 100k. That is single-digit MB at worst and puts the ceiling far above any plausible per-instance working set, leaving it as the memory backstop it was meant to be. * docs(rules): write down the caching decision tree The lifecycle-map-vs-TTL-cache distinction, the ceiling-as-backstop sizing, and the fetchMethod-unless-you-need-a-hang-deadline call each took real digging to settle. Recording them so the next cache does not re-derive the same answers — or re-introduce the unbounded tenant-keyed Map this branch just removed. Notes the two non-obvious traps behind that: ttl alone does not bound memory without a ceiling, and React cache() is a no-op in Trigger workers, so a gate that looks free on a settings page is uncached and per-block on the executor.