The machine that said no to my machine
Production AI systems · Guardrails
What you will leave with
Implement a permission snapshot that fails on newly granted access and on missing policy coverage.
The owned guardrail you will build
The incident’s classifier is platform-owned. This production-backed equivalent protects a database boundary by declaring what must remain denied and failing CI when reality drifts.
- 01Denied snapshotObjects, roles, and privileges that must stay closed.
- 02Migration chainBuilds the database from zero in CI.
- 03Privilege probeObserves SELECT, INSERT, and EXECUTE grants.
- 04Violation diffTreats a grant or missing object as failure.
- 05Required gateBlocks the change before the boundary reaches production.
Build it in sequence
Declare what must remain denied
The snapshot is the reviewable policy. Keep it separate from the probe so policy changes cannot hide inside implementation changes.
{
"roles": ["anonymous", "authenticated", "public"],
"relations": {
"privilege": "SELECT",
"objects": ["private.customer_events", "analytics.internal_rollup"]
},
"relationInserts": {
"privilege": "INSERT",
"objects": ["private.audit_log"]
},
"functions": {
"privilege": "EXECUTE",
"objects": ["private.refresh_internal_rollup()"]
}
}Expand policy into observable assertions
The production script creates one assertion per object, role, and privilege. It only observes; migrations remain the sole owner of GRANT and REVOKE.
export function expandPairs(snapshot: ProtectedSnapshot): Pair[] {
const pairs: Pair[] = []
for (const object of snapshot.relations.objects) {
for (const role of snapshot.roles) {
pairs.push({ kind: 'relation', object, role, privilege: 'SELECT' })
}
}
for (const object of snapshot.functions.objects) {
for (const role of snapshot.roles) {
pairs.push({ kind: 'function', object, role, privilege: 'EXECUTE' })
}
}
return pairs
}Fail on holes and on missing coverage
A granted privilege is a security hole. A missing protected object is snapshot drift. Both fail because the gate never interprets disappearance as safety.
export function computeViolations(rows: ProbeRow[]): CheckResult {
const holes = rows.filter(row => row.exists && row.granted)
const missing = rows.filter(row => !row.exists)
return {
ok: holes.length === 0 && missing.length === 0,
total: rows.length,
holes,
missing,
}
}Prove that a grant turns the build red
The real test injects an accessible protected function and asserts that the report names the exact hole.
it('fails when a restricted role receives EXECUTE', () => {
const result = computeViolations([{
kind: 'function',
object: 'private.refresh_internal_rollup()',
role: 'public',
privilege: 'EXECUTE',
exists: true,
granted: true,
}])
expect(result.ok).toBe(false)
expect(formatReport(result)).toContain('HOLE')
})Run it against a database rebuilt from zero
CI applies the committed schema and every migration to an ephemeral Postgres instance, then probes the resulting grants. No development or production credential is required.
permission-snapshot:
services:
postgres:
image: supabase/postgres:15
steps:
- uses: actions/checkout@v4
- run: apply-schema-and-every-migration.sh
- run: >-
node --experimental-strip-types
scripts/permission-snapshot/check-permissions.ts
--db-url "$EPHEMERAL_DATABASE_URL"Definition of done
- The denied set is versioned separately from the probe.
- The probe changes no grants; it only observes and asserts.
- A newly granted SELECT, INSERT, or EXECUTE fails CI.
- A renamed or removed protected object also fails CI.
- The database is rebuilt from the committed migration chain.
- Negative tests prove both a privilege hole and snapshot drift.
What breaks and what it means
A protected object is reported missing.
Do not delete it from the snapshot just to get green. Confirm whether the migration chain creates it and whether the rename was intentional.
Development is safe but CI reports a hole.
Development may contain an out-of-band manual revoke. The from-zero chain is exposing migration drift; repair the migration, not the check.
Questions, corrections, and useful disagreement
Sign in with GitHub to join the conversation. Comments are public. Article views are anonymous and counted once per browser, per article, each day.