Skip to content

docs: authorization change evidence design - #96

Open
lakhansamani wants to merge 6 commits into
mainfrom
feat/authz-change-evidence-spec
Open

lakhansamani wants to merge 6 commits into
mainfrom
feat/authz-change-evidence-spec

Conversation

@lakhansamani

Copy link
Copy Markdown
Contributor

What

Design for recording before/after state on authorization changes, so an auditor can answer what changed, from what, to what, and on whose authority — not just that something changed.

Why

A verification pass against server main (891f58fd) found:

Requirement Today
The change happened yes
State before no — never captured
The change itself partial — FGA tuple write/delete records count=N only
State after no — never captured
Audit evidence partial — best-effort, no actor identity, resource_id unset

Scope

Scoped by the claim it makes true, not by a file. An earlier revision said "FGA only", meaning internal/service/admin_fga.go — that turned out to cover 4 of 8 FGA tuple mutation sites. SCIM group membership is FGA tuples and writes them directly; purgeFgaTuplesForUser deletes them on user delete. Neither is audited, and scim.Dependencies has no AuditProvider at all.

Shipping that narrower version would have replaced a known gap with false confidence, which is worse than the gap.

  • Claim 1 — every FGA tuple change is evidenced. All 8 sites, plus a static guard test so a ninth cannot appear silently.
  • Claim 2 — every change to a user's roles or org membership is evidenced.
  • Deferred — clients, trusted issuers, org connections, SCIM endpoints, domains, SAML IdP (~22 sites). They change rarely and hold nearly all the credential material that makes redaction risky.

Locked decisions

  1. Scope by claim, not by file
  2. Explicit before/after snapshots, not replay-derivable deltas
  3. Actor identity: record what exists (auth_mode), no new auth model
  4. Synchronous audit for authorization changes only
  5. On audit-write failure: return an error, do not compensate — the change stays applied

Status

Phase 0 is implemented and verified in authorizerdev/authorizer#802. Phases 1–3 await review of this spec.

Scoping to internal/service/admin_fga.go covered 4 of 8 FGA tuple
mutation sites. SCIM group membership is stored as FGA tuples and
writes them directly; purgeFgaTuplesForUser deletes them on user
delete. Neither is audited, and scim.Dependencies has no
AuditProvider at all.

Rescope by claim rather than by file, add the static guard test that
prevents a ninth site appearing unaudited, and bring roles/membership
in. Infrastructure-config surfaces stay deferred.
Verified empirically: the three new storage subtests fail on couchbase
before the fix. Earlier table read its SELECT column list as filter
support.
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Oct 5, 2026 7:33am UTC

@netlify

netlify Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for authorizerdev-docs ready!

Name Link
🔨 Latest commit d6f4d69
🔍 Latest deploy log https://app.netlify.com/projects/authorizerdev-docs/deploys/6ac352c450a5d80008cd614f
😎 Deploy Preview https://deploy-preview-96--authorizerdev-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Principal is written only by the gRPC interceptor; GraphQL and REST
build none, which is why requireSuperAdmin falls back to IsSuperAdmin.
Reading Principal.AuthMode would record an empty auth_mode for the
dashboard path. Found by the Phase 1 whole-branch review.
lakhansamani added a commit to authorizerdev/authorizer that referenced this pull request Oct 6, 2026
Phase 1 plumbing for authorization-change evidence. Adds no new audit records and
changes no observable behaviour; consumers arrive in Phase 2.

- audit.Provider.LogEventSync(ctx, Event) error for operations where the audit
  record is part of the contract rather than a side effect. LogEvent keeps its
  signature and fire-and-forget semantics, so logins and token issuance never
  gain a synchronous dependency on the audit table. Both share one
  buildAuditLog so the paths cannot drift on record shape.
- token.Provider.AdminAuthMode records HOW a super-admin authenticated
  (admin_session vs shared_secret). Super-admin is one shared AdminSecret with
  no per-admin identity, so an audit record can never name a person; the
  credential mode is the honest substitute. IsSuperAdmin is now a predicate
  over AdminAuthMode, so the admit decision and the recorded mode cannot
  disagree. The session handle is never recorded.
- scim.Dependencies.AuditProvider plus a nil-safe wrapper. The SCIM package had
  no audit provider at all, which blocks half of Phase 2's call sites.

Reviewed for admit-equivalence branch by branch: neither widened nor narrowed.
Interface widening is safe by construction - both interfaces are under
internal/, which the compiler enforces as unimportable.

Design: authorizerdev/docs#96

This branch was successfully deployed

1 active deployment
Preview — d6f4d698 Deployed Oct 5, 2026 by vercel[bot]
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