Files
hackerspace.sh/integration/permission-escalation.test.ts
T
Moheeb Zara 0b6ef20891 add spine-validation owner runbook; lock privilege-escalation RLS test
docs/SPINE_VALIDATION.md: a precise owner runbook + checklist that provisions
the shipped-but-inert spine (Resend, CRON_SECRET + both crontabs, per-space
Stripe test mode + Billing Portal, the Supabase change-email template) and
proves each path end to end (dues cycle, notifications, prefs/inbox, /me,
door). Grounded in the actual config and route fail-closed codes; linked from
the README docs table.

integration/permission-escalation.test.ts: lock the additive permission
model's core guard, previously untested -- only admin/board may write
space_role_permissions, so a plain member cannot grant themselves a
capability, and the WITH CHECK pins the space.

No app code or migration; suite 626 unit / 54 integration green.
2026-05-20 23:22:48 -07:00

65 lines
2.6 KiB
TypeScript

// The additive permission model's integrity rests on one RLS gate: only an
// admin/board may WRITE space_role_permissions. If a plain member could insert
// a grant, they could hand themselves any capability (settings.manage, etc.).
// This locks that gate against real Postgres -- previously untested. Same
// role-simulation harness as the other integration tests.
import { describe, it, expect, afterAll } from 'vitest'
import { dbReady, execAsUser, rowsAsUser, seedSpace, seedMember, dropSpace } from './db'
const d = describe.skipIf(!dbReady)
d('space_role_permissions write RLS (privilege-escalation guard)', () => {
const spaces: string[] = []
function space(): string {
const { spaceId } = seedSpace()
spaces.push(spaceId)
return spaceId
}
afterAll(() => spaces.forEach(dropSpace))
it('a plain member cannot grant themselves a permission', () => {
const sid = space()
const member = seedMember(sid, { role: 'member' })
const r = execAsUser(
member.userId,
`insert into space_role_permissions (space_id, subject, permission) values ('${sid}','member','settings.manage');`,
)
expect(r.ok).toBe(false)
})
it('an admin can grant a permission (positive control)', () => {
const sid = space()
const admin = seedMember(sid, { role: 'admin' })
const r = execAsUser(
admin.userId,
`insert into space_role_permissions (space_id, subject, permission) values ('${sid}','member','ops.kb.write');`,
)
expect(r.ok).toBe(true)
})
it("a member's delete of an existing grant is filtered out (the grant survives)", () => {
const sid = space()
const admin = seedMember(sid, { role: 'admin' })
const member = seedMember(sid, { role: 'member' })
// The space seed already grants 'member' -> 'ops.kb.read'. A member's DELETE
// is USING-gated to admin/board, so it matches 0 rows (no error, no effect).
execAsUser(member.userId, `delete from space_role_permissions where space_id='${sid}' and subject='member' and permission='ops.kb.read';`)
const still = rowsAsUser(
admin.userId,
`select 1 from space_role_permissions where space_id='${sid}' and subject='member' and permission='ops.kb.read'`,
)
expect(still.length).toBe(1)
})
it('an admin cannot write a grant into another space (WITH CHECK pins space)', () => {
const sidA = space()
const adminA = seedMember(sidA, { role: 'admin' })
const sidB = space()
const r = execAsUser(
adminA.userId,
`insert into space_role_permissions (space_id, subject, permission) values ('${sidB}','member','settings.manage');`,
)
expect(r.ok).toBe(false)
})
})