Skip to content

Hermetic whole-app authorization gate#162

Open
pratikbodkhe wants to merge 1 commit into
mainfrom
feat/hermetic-authz-gate
Open

Hermetic whole-app authorization gate#162
pratikbodkhe wants to merge 1 commit into
mainfrom
feat/hermetic-authz-gate

Conversation

@pratikbodkhe

Copy link
Copy Markdown
Contributor

What

A deterministic, hermetic authorization gate: it executes minutia's real middleware, the admin server-layout role guard, and API route handlers headlessly in plain Node against a fixture Supabase backend — no dev server, no database, no network. The whole-app authorization matrix (every route × six identities) runs in ~0.4s.

The seam is a single globalThis.fetch swap; zero test branches are added to src/ (respects the repo's no-stubs-in-prod rule).

What it verifies

  • Middleware reachability for every page route, per identity.
  • The admin role guard ((app)/admin/layout.tsx) — only a global admin renders it; everyone else redirects.
  • API handler authz status across all four guard families (requireAdmin, requireCurrentOrgAdmin, getUser→401, public) via executed representatives, over six identities: logged-out / member / org-admin / global-admin / cross-tenant / pro.
  • Cross-tenant isolation — the two service-role list handlers (workspace/members, admin/invitations) are asserted to return only the caller's organization's rows.
  • The observed matrix must equal a hand-audited, default-deny policy.json (an explicit expected-outcome per node × identity).

Why it's trustworthy (it can't silently lie)

  • Hermeticity is enforced by asserting the unmatched-request log is empty on every probe. (The Supabase client swallows a thrown fetch into a null-data result, so a rejection can't be relied on.) Undeclared-table reads and any write/RPC throw loudly.
  • Coverage gate: every route.ts / page.tsx on disk must be either executed in the matrix or explicitly deferred with a reason — a new unguarded route fails the gate instead of slipping through.
  • Real-code negative controls (negcontrol.mjs): mutating a real guard (neuter the admin role check) and a real tenant filter (drop .eq("organization_id", …)) both turn the gate RED with named violations (reachability-leak, tenant-leak), then revert. A gate that never goes red is a lie; this proves it does.
  • Three synthetic checker-controls prove checkPolicy isn't vacuous.

Scope (honest boundary)

Covered: server-side authorization (middleware, server-component guards, handler authz status, cross-tenant isolation). Not covered here (stays with the Playwright + real-DB E2E suite): client-component rendering, real RLS SQL enforcement (the fixture applies URL filters literally, no RLS emulation), and write/PUT/PATCH/DELETE + RPC business logic.

Run

  • pnpm test:authz → 23/23, deterministic (flake-stable 3×). Wired into the CI lint-and-typecheck job.
  • node scripts/authz/negcontrol.mjs → both real-code controls go red then revert.

Emits an AKG-shaped artifact (shared graph schema with the RN adapter) under scripts/authz/artifacts/ (gitignored, regenerated per run).

Adds a deterministic, hermetic authorization gate that executes the REAL
middleware, the admin server-layout role guard, and API route handlers headlessly
in Node against a fixture Supabase backend (a single global fetch seam, no dev
server, no database, no network). The whole-app authz matrix runs in ~0.4s.

- Probes every page route (middleware reachability), the admin role guard, and a
  representative handler per guard family (requireAdmin, requireCurrentOrgAdmin,
  getUser, public) across six identities (logged-out / member / org-admin /
  global-admin / cross-tenant / pro), asserting the observed matrix equals a
  hand-audited default-deny policy.json.
- Cross-tenant isolation: the two service-role list handlers are asserted to
  return only the caller's organization rows; a real-code mutation dropping the
  org filter is proven to turn the gate red.
- Coverage gate: every route.ts / page.tsx on disk must be either executed in the
  matrix or explicitly deferred with a reason, so a new unguarded route fails the
  gate instead of slipping through.
- Hermeticity is enforced by asserting the unmatched-request log is empty on every
  probe (the Supabase client swallows thrown fetch into null-data results, so a
  rejection cannot be relied on); undeclared table reads and any write/RPC throw.
- negcontrol.mjs mutates a real guard and a real tenant filter, proves the gate
  goes red with named violations, then reverts. Wired into CI (pnpm test:authz).

The seam is purely the external fetch swap; no test branches are added to src/.
Covers server-side authorization only; client rendering, real RLS SQL, and
write/RPC business logic remain with the Playwright + real-DB suite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant