fix(authz): eliminate cache-key collision in cachedPermissionStore
The scope-prefix concatenation cacheKey used a bare U+FFFD separator with no escaping, so an adversarial subject/tenant id containing that character could collide with a different subject/tenant pair and leak cached roles across tenants. Switch to JSON.stringify([scopeKey, subjectId]) for an unambiguous key. Also replace the untested key.endsWith() substring sweep used to invalidate a subject across all tenants on a global write with an explicit bySubject index, and add test coverage for both the collision and the cross-tenant invalidation sweep.
This commit is contained in:
@@ -63,4 +63,20 @@ describe("cachedPermissionStore", () => {
|
||||
expect((await store.assignmentsFor("u1")).roles).toEqual([]);
|
||||
expect((await store.assignmentsFor("u1", { tenantId: "t1" })).roles).toEqual(["editor"]);
|
||||
});
|
||||
|
||||
test("a global write invalidates the subject in every tenant", async () => {
|
||||
const inner = memoryPermissionStore();
|
||||
const store = cachedPermissionStore(inner, { ttlMs: 60_000 });
|
||||
await store.assignmentsFor("u1", { tenantId: "t1" }); // warm the tenant entry
|
||||
await store.assignRole("u1", "editor"); // global write
|
||||
expect((await store.assignmentsFor("u1", { tenantId: "t1" })).roles).toEqual(["editor"]);
|
||||
});
|
||||
|
||||
test("cache keys cannot collide across subject/tenant boundaries", async () => {
|
||||
const inner = memoryPermissionStore();
|
||||
const store = cachedPermissionStore(inner, { ttlMs: 60_000 });
|
||||
await inner.assignRole("b\uFFFDc", "editor", { tenantId: "a" });
|
||||
expect((await store.assignmentsFor("b\uFFFDc", { tenantId: "a" })).roles).toEqual(["editor"]);
|
||||
expect((await store.assignmentsFor("c", { tenantId: "a\uFFFDb" })).roles).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user