Skip to content

fix(auth): bind committee mutations to credentials - #7475

Draft
bokelley wants to merge 7 commits into
mainfrom
conductor/committee-leader-exact-credential-boundary
Draft

bokelley wants to merge 7 commits into
mainfrom
conductor/committee-leader-exact-credential-boundary

Conversation

@bokelley

@bokelley bokelley commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • bind working-group/committee leader mutations to the immutable authenticated WorkOS credential (authWorkosUserId ?? id) and an explicit selected organization
  • authorize, lock, revalidate, mutate, bump the target epoch, and audit in one fail-closed transaction; canonical identity is retained only for attribution and linked-identity self-removal protection
  • require the immutable web principal at the shared handler seam; Tavus exposes no committee mutation definitions or handlers until an integrated fix(addie): authorize admin tools by authenticated credential #7450 continuation can carry an exact voice credential
  • validate Slack actor and Slack target mappings as mapped, not deleted, not bot, and bound to a live WorkOS credential under transaction locks

Security properties

  • typed forbidden versus unavailable results and no implicit organization selection
  • exact signed Slack actor, leadership aliases, and target credential bindings locked and revalidated
  • WorkOS membership consulted again immediately before mutation
  • deterministic committee-local actor/target locks plus concrete binding, grant, leadership, mapping, and epoch locks
  • arbitrary, unmapped, revoked, deleted, bot, and inconsistent targets fail closed
  • target epoch bump and audit insertion must each affect/return exactly one row; suppressed audit insertion rolls back the mutation and all side effects
  • audit records both exact actor credential and canonical identity attribution

Restack and independent review

The prior head cb02042ba6d66826d3a2d09ba8b30be0e2f01e57 was blocked by independent review. Corrected source-tree head f4ce87dc69a1840619340d9abfda876b25770b04 addresses the Tavus principal gap, Slack mapping lifecycle validation, live target validation/exact epoch bump, audit exact-one readback, and committed self-removal regression.

The corrected source commit was replayed directly from old parent b1789a6e9a0024b011586c4b73daf935d1d8e9f5 onto final #7462 head 7b9876d4670e1518dfe15445e1746e4f081e65cb. The old empty CI trigger c93261a5984e87da0c9edd188c7bf809f8a0e9c5 was intentionally dropped. All ten non-derived source/test blobs remain byte-identical to f4ce87dc; the filtered semantic patch ID remains 3f5a23e9e2b3d4b0196c49eb379939e642b7fed1.

The range-diff is one source commit (f4ce87dc → 77608c1f); differences are limited to the Addie tool-surface budget/inventory regenerated against the final dependency and CODE_VERSION 2026.09.55 → 2026.09.56.

The earlier clear verdict for head 77608c1fea82e2f971f5e87839b296cade9b61b2 is superseded. A subsequent independent audit found that the generated Tavus inventory still modeled COMMITTEE_LEADER_TOOLS even though production Tavus refuses those tools. That head must not be treated as ready.

A narrow corrective commit 42b31cade0e65154f7bdd87ed19c74b0997131ad removes the stale Tavus generator input and regenerates only the inventory/contract hash. All 13 generated Tavus profiles now contain zero committee mutation tools; every non-Tavus profile is semantically unchanged. The Tavus admin wire-schema maximum is corrected from 27836 to 26275 bytes.

Independent corrective-delta review verdict: clear, with no findings. Reviewed head 42b31cade0e65154f7bdd87ed19c74b0997131ad, tree f648c26faf43eae49f9c7c7520479f55687999af, parent 77608c1fea82e2f971f5e87839b296cade9b61b2.

Exact-tree workflow evidence

Current remote head 42b31cade0e65154f7bdd87ed19c74b0997131ad: 29 successful checks + 2 expected skips, with zero non-green real checks. GitHub reports merge state CLEAN. Every workflow below has verified headSha 42b31cade0e65154f7bdd87ed19c74b0997131ad:

Exact ancestry/tree evidence:

  • 42b31cade0e65154f7bdd87ed19c74b0997131ad^ = 77608c1fea82e2f971f5e87839b296cade9b61b2
  • 42b31cade0e65154f7bdd87ed19c74b0997131ad^{tree} = f648c26faf43eae49f9c7c7520479f55687999af
  • dependency ancestor = 7b9876d4670e1518dfe15445e1746e4f081e65cb
  • corrective delta = exactly the Tavus inventory generator, generated inventory, and reviewed budget contract hash

Dependency and merge order

This is intentionally stacked on #7462 at its final independently reviewed, CI-green head 7b9876d4670e1518dfe15445e1746e4f081e65cb for its immutable AuthorizationSnapshot hydration helpers.

Exact merge order:

  1. fix(auth): hydrate primary authorization snapshots per request #7462
  2. this PR

Do not merge this PR before #7462.

This slice only disables the unsafe Tavus committee-mutation exposure; integrated Tavus/Slack-admin authority remains #7450. It does not touch #7452 content routes or migrations 588-594.

Local validation at corrected head

  • npm run typecheck
  • npm run test:addie-tools
  • focused unit/HTTP/handler: 6 files, 317 tests passed
  • focused PostgreSQL integration: 3 files, 52 tests passed
  • independent focused web/Slack/Tavus: 61 tests passed
  • independent Addie reference/inventory/budget/portability checks
  • corrective focused Tavus/committee: 61 tests passed
  • corrective committee PostgreSQL integration: 25 tests passed
  • independent corrective Tavus/committee: 19 tests passed
  • independent corrective typecheck and Addie reference/inventory/budget/portability checks
  • git diff --check
  • no changes to migrations 588-594

Partial implementation of #6827; does not close the full umbrella issue.


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 15:42
@bokelley
bokelley changed the base branch from conductor/cold-cache-auth-snapshot to main September 13, 2026 15:50
@bokelley bokelley closed this Sep 13, 2026
@bokelley bokelley reopened this Sep 13, 2026
@bokelley
bokelley changed the base branch from main to conductor/cold-cache-auth-snapshot September 13, 2026 15:50
@bokelley
bokelley changed the base branch from conductor/cold-cache-auth-snapshot to main September 13, 2026 15:52
@bokelley bokelley closed this Sep 13, 2026
@bokelley bokelley reopened this Sep 13, 2026
@bokelley
bokelley changed the base branch from main to conductor/cold-cache-auth-snapshot September 13, 2026 15:52
@bokelley
bokelley force-pushed the conductor/committee-leader-exact-credential-boundary branch 2 times, most recently from b73876c to f4ce87d Compare September 13, 2026 16:04
@bokelley
bokelley changed the base branch from conductor/cold-cache-auth-snapshot to main September 13, 2026 16:07
@bokelley
bokelley marked this pull request as ready for review September 13, 2026 16:07
@bokelley bokelley closed this Sep 13, 2026
@bokelley bokelley reopened this Sep 13, 2026
@bokelley
bokelley marked this pull request as draft September 13, 2026 16:10
@bokelley
bokelley changed the base branch from main to conductor/cold-cache-auth-snapshot September 13, 2026 16:10
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 — clean server-only auth PR with no blocking or medium findings.

What I checked:

  • This is a server-only authorization PR: new fail-closed committee-leader mutation service, auth-middleware snapshot rewrite, and Addie tool rebinding. No files under static/schemas/source/**, docs/reference/**, or dist/** are touched, so no schema↔docs coherence, changeset-scope, oneOf-discriminator, artifact-immutability, or governance-precedent gate applies.
  • Reviewer confirmed deliberate fail-closed design: bounded WorkOS enforcement client (5s / 0-retry) plus lock_timeout cap in-transaction provider calls; exactly-one-row epoch/audit readbacks roll back on mismatch; request authority is re-hydrated from the primary DB on every path.
  • No Critical, High, or Medium findings reported.

High-risk flag: true, but all reasons are (modified) files under server/src/addie/** and server/src/routes/**. Per decision rules, a (modified) high-risk file with no medium-or-higher finding is presumed safe — the flag alone is not an escalation trigger (rows 3 & 5 require a deletion or a medium finding, respectively; neither is present).

Gate checks: gated_paths is false, so row 2 does not fire despite review_decision: REVIEW_REQUIRED. No no-auto-approve team match (row 7 N/A). No prior decision. Fewer than three medium findings (row 8 N/A).

Rows 1–8 all fail to match → row 9 → approve.

@bokelley
bokelley changed the base branch from conductor/cold-cache-auth-snapshot to main September 13, 2026 16:16
@bokelley
bokelley dismissed aao-secretariat[bot]’s stale review September 13, 2026 16:16

The base branch was changed.

@bokelley
bokelley changed the base branch from main to conductor/cold-cache-auth-snapshot September 13, 2026 16:16
@bokelley
bokelley force-pushed the conductor/committee-leader-exact-credential-boundary branch from c93261a to 77608c1 Compare September 14, 2026 01:56
@bokelley
bokelley changed the base branch from conductor/cold-cache-auth-snapshot to main September 14, 2026 01:57
@bokelley
bokelley marked this pull request as ready for review September 14, 2026 01:57
@bokelley bokelley closed this Sep 14, 2026
@bokelley bokelley reopened this Sep 14, 2026
@bokelley
bokelley marked this pull request as draft September 14, 2026 01:57
@bokelley
bokelley changed the base branch from main to conductor/cold-cache-auth-snapshot September 14, 2026 01:57
authenticatedWorkosUserId: string,
selectedOrganizationId: string,
): Promise<boolean> {
const memberships = await workos.userManagement.listOrganizationMemberships({

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.

Medium: listOrganizationMemberships is an unbounded external HTTP call, and it runs twice inside the open transaction — after working_groups and working_group_leaders are locked FOR UPDATE (and, when a row exists, authorization_epochs FOR UPDATE). SET LOCAL statement_timeout/lock_timeout do not bound a JS-side network await; neither does the WorkOS SDK set a default request timeout. Production symptom: a slow or hung WorkOS API leaves this connection idle-in-transaction holding those row locks, so concurrent committee-leadership mutations on the same committee block until the SDK's own TCP timeout releases it — not until lock_timeout. Wrap the provider call in an explicit timeout/abort, or move the authorization decision outside the lock window.

aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Sep 14, 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 — security-hardening rewrite of Addie's committee-leader tools, no blocking findings.

This subsequent pass on the #7450 continuation swaps MemberContext identity for an exact authenticated principal enforced by a new transactional service, with fail-closed and concurrency test coverage. Addie/server-only scope; no protocol-surface, schema↔docs, oneOf, released-artifact, or changeset concerns. CODE_VERSION bumped correctly.

Medium findings

  • server/src/services/committee-leader-mutation.ts — WorkOS listOrganizationMemberships runs (twice) inside the open transaction while working_groups/working_group_leaders rows are locked FOR UPDATE with no timeout; a hung provider holds those locks idle-in-transaction. Operational-readiness concern, non-blocking.

Decision path: high_risk is true but all high-risk entries are (modified), not (deleted). The single medium finding is not in category data-loss/schema/infra, so row 4 does not fire. Row 5 requires a (modified) high-risk file AND a medium finding — but the medium concern is on server/src/services/committee-leader-mutation.ts, which is NOT in the high_risk_reasons list (it does not match the high-risk globs), so row 5 does not fire. gated_paths is false; no team gate; prior decision was approve. Fewer than three mediums. Falls through to row 9 → approve.

@bokelley
bokelley changed the base branch from conductor/cold-cache-auth-snapshot to main September 14, 2026 02:23
@bokelley
bokelley dismissed aao-secretariat[bot]’s stale review September 14, 2026 02:23

The base branch was changed.

@bokelley bokelley closed this Sep 14, 2026
@bokelley bokelley reopened this Sep 14, 2026
@bokelley
bokelley changed the base branch from main to conductor/cold-cache-auth-snapshot September 14, 2026 02:23
@bokelley
bokelley changed the base branch from conductor/cold-cache-auth-snapshot to main September 14, 2026 02:37
@bokelley
bokelley changed the base branch from main to conductor/cold-cache-auth-snapshot September 14, 2026 02:38
@bokelley
bokelley force-pushed the conductor/committee-leader-exact-credential-boundary branch from 47b2218 to 42b31ca Compare September 14, 2026 02:38
@bokelley
bokelley requested review from a team and removed request for a team September 14, 2026 02:39
@bokelley
bokelley force-pushed the conductor/cold-cache-auth-snapshot branch 3 times, most recently from 3216b0d to 0c14d9f Compare September 17, 2026 13:56
Base automatically changed from conductor/cold-cache-auth-snapshot to main September 17, 2026 14:17

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