Skip to content

fix(auth): enforce binding-only detach lifecycle integrity - #7460

Draft
bokelley wants to merge 7 commits into
conductor/admin-bind-workos-compensationfrom
conductor/binding-detach-integrity
Draft

bokelley wants to merge 7 commits into
conductor/admin-bind-workos-compensationfrom
conductor/binding-detach-integrity

Conversation

@bokelley

@bokelley bokelley commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Credential detach now requires the identity and authorization epoch observed by the admin, rejects stale or terminal identity graphs, and moves only the exact non-primary binding to a new singleton identity. It preserves every existing authority row and commits the required epoch advances with exactly one verified durable audit.

The transaction keeps deterministic identity/binding locks, adds nonblocking credential lifecycle locks shared with bind/deletion, and locks matching bind-operation rows before checking status. It rejects deletion/quarantine/reconciliation evidence and requires the authenticated actor credential's identity to have exactly one primary matching the canonical actor. Audit attribution retains both the canonical user and actual authenticated credential.

Audit INSERT RETURNING must report exactly one non-null row with the expected ID. Deferred triggers are flushed; stored audit cardinality/payload, epochs and lifecycle evidence are checked before verified advisory unlocks and COMMIT. Any failed receipt, suppression, stale precondition or contention rolls back the whole transaction. Detach never writes the bind journal or calls WorkOS.

Organization memberships and grant provenance, platform-admin membership, leadership, organization selection and subscriptions remain unchanged. API keys are WorkOS-owned and receive no provider mutation. No historical reversal, before-image inference, authority reconstruction, protocol changeset, or migration is included. No migration 594 or reservation. Parent migration 593 is inherited unchanged.

Part of #6827; this does not close the broader authorization issue.

Stack and merge hold: #7459 → #7464 → #7460. Exact direct parent: 126d98c8a3ce29a84e948827a847afbff58afed5 (#7464), on containment 9a7cd5362787a0badbfccd0f1cb1dd002654c22f (#7459). The parent lifecycle, compensation and containment safeguards are preserved. The independently reviewable delta is 7 files, +1485/−105. Keep this PR Draft; no merge or auto-merge before dependencies, green required CI and human approval.

Validation on head 06702b8c4198474c749b24ea05c6c90816e7eca3 (Docker Node 24 / PostgreSQL 16):

  • 339 tests in 17 focused suites passed (the original 337 plus two exception-disclosure regressions), including 34 helper driver-fault cases, 71 real PostgreSQL helper cases, 11 deterministic composition cases, and parent lifecycle/containment regressions.
  • Fault coverage includes audit rowCount 0/>1/NULL, empty/NULL return values, suppressed/altered/deleted/duplicated audit, immediate/deferred faults, lock/unlock and rollback failures, epoch suppression/rewrites, stale/replayed/concurrent detach, and both personal/corporate identity directions.
  • Composition covers bind/deletion ownership in both orders, loss of a captured secondary actor's canonical primary, bind→detach→bind same/conflicting-host replay, and both journal-update ownership orders. PostgreSQL lock evidence establishes schedules; full authority snapshots, exact epoch maps, one detach audit, unchanged journals at detach boundaries, and provider call counts are asserted.
  • Full typecheck, migration loader/order/isolation 26 cases, migration validation 3 cases, direct-parent/range-diff and whitespace checks passed. Direct ancestry remains the exact released parent; the helper, race/fault suites and all parent lifecycle/containment/migration files are byte-identical to the prior reviewed head.
  • Exact route regressions inject arbitrary exception text, token/PII/provider payloads, stack and cause into conflict and generic failures. The response retains the 409 status and credential_binding_changed code with a fixed message; structured logs retain only helper-owned constant reasons and redact unknown details before telemetry hooks. The generic 500 response remains unchanged.
  • Pinned local Semgrep 1.67.0 / Python 3.12.14: five rules, 850 files, zero findings. Inherited parser diagnostics affect only unchanged parent files; both changed production files scan without diagnostics.
  • Independent security/correctness review of this exact head and its two-file disclosure fix: no findings.

Fresh GitHub CI on this exact head is green: 38 passing checks, with the AI review intentionally skipped for Draft. Semgrep reports zero findings; CodeQL, build and all test shards, and migration smoke tests passed. The workflows were triggered using the main base required by repository filters, then the Draft base was restored to conductor/admin-bind-workos-compensation. Human maintainers approval and dependency merge order remain required; no merge or auto-merge.


Open workspace in Conductor

Draft disposition — 2026-10-09

This draft is retained for the stated workstream. Continue on this existing branch; no parallel PR is needed. This disposition does not waive CI, review, migration, or release gates.

@bokelley
bokelley requested a review from a team September 13, 2026 13:59
aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Sep 13, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — credential-detach containment PR is clean.

Scope: 6 files, server/** only (admin route users.ts, new credential-detach-db.ts helper, admin-people.html, plus unit/integration tests). No protocol surface (static/schemas/source/**, docs/reference/**, dist/**, changesets, package.json version), so no schema↔docs, changeset, oneOf, immutability, or 3.1.x governance gates apply.

Reviewer found no Critical/High/Medium findings. The largest new file (credential-detach-db.ts) was reviewed end to end: advisory-lock ordering, NOWAIT row locks, epoch precondition/advance-and-verify, stored-audit read-back, unlock-before-commit, and connection-destroy-on-error are all fail-closed and covered by 49 fault-injection tests.

Gates checked:

  • Author team gate: no no-auto-approve match.
  • gated_paths: false → row 2 not triggered.
  • high_risk: true only via server/src/routes/admin/users.ts (modified), but row 5 requires a medium finding to escalate — there are none. High-risk flag alone does not escalate.
  • No deletions in sensitive paths (row 3 n/a).
  • No prior decision.

No blocking or medium findings → rows 1–8 do not fire → row 9 approve.

@bokelley
bokelley force-pushed the fix/6827-identity-containment branch from e323c3d to 9a7cd53 Compare September 14, 2026 01:07
@bokelley bokelley changed the title fix(auth): make credential detach atomic and binding-only fix(auth): enforce binding-only detach lifecycle integrity Sep 14, 2026
@bokelley
bokelley changed the base branch from fix/6827-identity-containment to main September 14, 2026 03:28
@bokelley
bokelley dismissed aao-secretariat[bot]’s stale review September 14, 2026 03:28

The base branch was changed.

@bokelley
bokelley force-pushed the conductor/binding-detach-integrity branch from 806df40 to 36538d0 Compare September 14, 2026 03:30
@bokelley
bokelley changed the base branch from main to conductor/admin-bind-workos-compensation September 14, 2026 03:31
@bokelley
bokelley requested review from a team and removed request for a team September 14, 2026 03:31
@bokelley
bokelley changed the base branch from conductor/admin-bind-workos-compensation to main September 14, 2026 03:47
@bokelley
bokelley force-pushed the conductor/binding-detach-integrity branch from 36538d0 to 06702b8 Compare September 14, 2026 03:47
@bokelley
bokelley changed the base branch from main to conductor/admin-bind-workos-compensation September 14, 2026 03:47
@bokelley
bokelley requested review from a team and removed request for a team September 14, 2026 03:56
@bokelley
bokelley force-pushed the conductor/admin-bind-workos-compensation branch from 126d98c to 91f0472 Compare September 14, 2026 17:27
@bokelley
bokelley force-pushed the conductor/admin-bind-workos-compensation branch 3 times, most recently from b9d5e2c to a157438 Compare September 14, 2026 23:25

This branch has not been deployed

No deployments
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