import { describe, it, expect, beforeAll } from "vitest"; import PocketBase from "pocketbase"; import { DEMO, PB_URL, adminPb, requireServices, userPb } from "./helpers"; /** * Regression guard for a live privilege escalation. * * The built-in `users` collection ships with createRule "" (anyone may sign * up) and updateRule "id = @request.auth.id" (anyone may edit themselves). * Because `role` is an ordinary field on that collection, both were * exploitable: an anonymous request could create an owner account, and a * client manager could promote themselves to owner and then read every other * client's board. Both were verified working against the live instance before * the rules were locked. * * If either test here fails, someone has reverted the users rules and every * client's data is readable by anyone who can reach the PocketBase URL. */ let admin: PocketBase; beforeAll(async () => { await requireServices(); admin = await adminPb(); }); describe("the users collection cannot be used to escalate privileges", () => { it("refuses anonymous account creation", async () => { const anon = new PocketBase(PB_URL); anon.autoCancellation(false); await expect( anon.collection("users").create({ email: `anon-${Date.now()}@example.com`, password: "probe-password-123", passwordConfirm: "probe-password-123", name: "Should not exist", role: "owner", }) ).rejects.toThrow(); }); it("refuses a client manager promoting themselves to owner", async () => { const pb = await userPb(DEMO.managerEmail, DEMO.managerPassword); const selfId = pb.authStore.record!.id; await expect( pb.collection("users").update(selfId, { role: "owner" }) ).rejects.toThrow(); const after = await admin.collection("users").getOne(selfId); expect(after.role).toBe("client_manager"); }); it("refuses a client manager reassigning themselves to another client", async () => { // The other half of the same hole: switching `client` would hand them a // different customer's entire board through the normal read rules. const pb = await userPb(DEMO.managerEmail, DEMO.managerPassword); const selfId = pb.authStore.record!.id; const before = await admin.collection("users").getOne(selfId); await expect( pb.collection("users").update(selfId, { client: "" }) ).rejects.toThrow(); const after = await admin.collection("users").getOne(selfId); expect(after.client).toBe(before.client); }); it("does not let a client manager list other accounts", async () => { const pb = await userPb(DEMO.managerEmail, DEMO.managerPassword); const visible = await pb.collection("users").getFullList(); expect(visible).toHaveLength(1); expect(visible[0].id).toBe(pb.authStore.record!.id); }); });