async function getDashboard(user: User) {
const key = `dashboard:${user.role}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const data = await db.dashboard.findFirst({ where: { tenantId: user.tenantId } });
await redis.set(key, JSON.stringify(data), "EX", 60);
return data;
}
The cache key has no tenant id, so an admin of one tenant can get another tenant's cached dashboard
The database query is scoped by tenantId, but the cache key is not. The first admin to load the page fills dashboard:admin. For the next 60 seconds, every admin of every tenant gets that tenant's data. Fix: put the tenant (and the user, for personal data) in the key, such as dashboard:<tenantId>:<role>.
"JSON.parse allows prototype pollution" is the tempting wrong answer. JSON.parse only creates plain data. Pollution needs an unsafe merge afterwards. Colons in Redis keys are normal.
Read more