Skip to main content
Tuturuuu is the proposed SAML 2.0 identity provider: an invited Claude organization member signs into Claude through WorkOS using a Tuturuuu account. This is distinct from inbound enterprise sign-in to Tuturuuu and from internal cross-app sessions. No Supabase bearer token is shared with Claude.

Pilot decisions

The pilot is SP-initiated and defaults to denied access. A future enabled client selects exactly one active nonpersonal workspace. Each user needs current MEMBER status, an explicit client assignment, a confirmed email in an approved domain, fresh verified MFA no older than ten minutes, and identity-sharing approval on every login. Guests, invitations and email-domain matches alone confer no access. No workspace or test population has been selected by the protocol fixture. Share only a stable random pairwise NameID/id, confirmed email, firstName and lastName. Persist subjects per client/Auth account; do not derive them from email or recycle them. An email change requires fresh verification and operator review of Claude linking behavior before restoring SSO. Names are presentation, never authorization. Claude owns roles, seats and invite-only provisioning. The pilot excludes OIDC, SCIM, groups, IdP-initiated login and single logout. Tuturuuu logout does not imply termination of an existing Claude session. These decisions apply to future outbound SSO Web/consent/management surfaces; ordinary Tuturuuu sign-in and cross-app session policy retain their own rules.

Phase 1 protocol boundary

packages/sso provides strict Zod configuration/request/identity/assurance contracts, bounded Redirect and POST AuthnRequest decoding, pinned request signatures when required, signed response/assertion construction and public IdP metadata without SLO. Clock, signer and ID generation are injected. Portable entrypoints have no framework imports; the implementation targets Node crypto and zlib for the future Next.js Node runtime on Vercel. The selected libraries are xml-crypto 6.4.0 and @xmldom/xmldom 0.9.12. RSA-SHA256 signatures, SHA256 digests and exclusive canonicalization are supplied by the library. Request admission rejects unknown SPs, exact-URL mismatches, stale requests, unsupported NameID/authentication contexts, passive interactions, duplicate IDs/parameters, DTDs and oversized compressed/expanded XML. Unsigned requests require an explicitly unsigned profile and do not authenticate the SP. Assertions expire after two minutes and bind Audience, Recipient, Destination and InResponseTo. The only supported NameID format is persistent; the connection’s actual requirements must be confirmed before enabling it. For both request bindings, NameIDPolicy or AllowCreate omission means false, including signed requests. Only explicit true/1 permits the future caller to allocate a subject; otherwise it must find an existing mapping or deny. These defaults apply to the portable protocol and future outbound SSO adapters, with no unsigned-request exception. See packages/sso/src/__tests__/security-review.test.ts for omission and invalid-boolean regression coverage. REFEDS MFA context requires a future adapter to prove primary authentication and current TOTP MFA; the package cannot establish assurance from a supplied timestamp alone. Phase 1 adds no routes, UI, migrations, live clients or hosted configuration. Metadata generation is a library function, not a published metadata endpoint. Do not expose the protocol functions as an issuance API without the later gates.

Phase 2 persistence boundary

Six additive migrations (20261022100000 through 20261022100005) introduce manage_sso_clients and eight RLS-enabled private tables: sso_clients, sso_saml_clients, sso_assignments, sso_subjects, sso_disclosure_grants, sso_transactions, sso_signing_keys and sso_audit_events. All children carry ws_id and composite tenant foreign keys. Creation always starts disabled; client changes require an expected policy revision. The initial profile supports only persistent NameID and one to eight exact HTTPS ACS destinations, without credentials, fragments or custom ports. Configuration stores approved email domains; authoritative email/domain enforcement belongs to phase 3. Browser roles and service_role have no direct table privileges. Management is through service-only sso_manage_client and sso_set_assignment RPCs. Both require a current MEMBER in an active nonpersonal workspace with effective manage_sso_clients permission (including the existing creator/admin semantics). The server must resolve the authenticated actor before calling them; an actor ID is a trusted server input, never a browser authorization claim. Service-only sso_create_transaction reserves a request hash uniquely per client, stores InResponseTo and an exact configured ACS, and caps expiry at five minutes. Opaque handles, browser/session bindings and RelayState are hashes; raw RelayState and assertions are absent. sso_record_consent records one immutable per-login approval or denial for the fixed identity attribute list, binding actor, policy, browser and session. Denial burns the transaction. sso_consume_transaction takes row locks, rejects expired/used transactions, rechecks enabled client, policy, current MEMBER and active assignment, and requires the matching approval and bindings. The validated request’s allow_create is persisted with default false; allocation requires explicit true, while false can reuse an existing mapping. Consumption, random 256-bit pairwise subject allocation and audit append commit together. Concurrent consumers yield one winner and a replay error. authenticated users can call sso_read_own_disclosures(ws_id) to read up to 100 of their own receipts. Its actor comes exclusively from auth.uid(). Other users and anonymous callers cannot read those receipts. Disclosure receipts and audit rows reject updates and deletes; audit appends expose only bounded event codes and correlation IDs. sso_append_audit is service-only and requires a consumed, approved transaction for issuance events. A signed event records the issuer’s observation; it does not prove Claude accepted the response. Signing-key rows contain only a public certificate, kid, status, revision and validity/rotation timestamps. A partial unique index permits at most one active key per client. Certificate syntax constraints reject private-key PEM; the future signer must validate the actual X.509 certificate and key match. No private key, secret reference, key import/rotation RPC or signer route is added by this phase. Subjects reference auth.users, so actual Auth deletion clears their user link while reserving the random value. Auth deletion also clears client/assignment creator attribution and deletes assignments and actor-bound transactions, even though the public user row remains. Membership deletion cascades assignments; MEMBER-to-GUEST demotion deletes them too. Rejoining or later promotion requires an explicit new assignment. This rule applies to every phase 2 SSO client. Workspace hard deletion cascades clients, protocol profiles, assignments, subjects, transactions and public signing-key metadata, including disabled clients. Immutable disclosure receipts and audit events retain denormalized workspace/client/actor/ transaction UUIDs without parent foreign keys. These historical receipts cannot authorize a login without a live transaction and current membership/assignment. Cleanup never disables or bypasses their append-only triggers. All eight tables force RLS; approved definer RPCs retain their PostgreSQL BYPASSRLS owner, while a non-BYPASSRLS table owner is subject to the policies. Receipt/audit actors are UUID-only correlation values without deletion-blocking user foreign keys; retention and pseudonymization require the phase 5 operating policy. There is no automatic retention job in this phase.

Database verification

From the repository root, run:
The runner stages the complete migration history into an owned disposable Supabase project, applies it with supabase start and supabase db reset, runs sso-persistence.sql with strict pgTAP parsing, and drives a real two-connection consume race. It generates packages/types/src/supabase.ts from that same applied public,private,storage schema, then stops only the owned database without backup. Synthetic race rows are committed only inside the disposable database and vanish with its teardown; immutable audit rows are never bypassed for fixture cleanup. Each race uses fresh UUIDs so repeat runs preserve retained user/workspace history. The pgTAP packet covers membership removal/rejoin, demotion/promotion, real Auth deletion (including retained public users), creator and workspace deletion, byte-identical retained history, and a non-BYPASSRLS table owner. The separate race requires one winner, one replay rejection, one subject and one consume audit. It also races membership deletion and assignment deactivation against consume in both orders. The holder observes its own PID blocking the competitor before it commits: revocation first must deny consume without a subject or consume audit; consume first must commit before revocation can commit. Both orders deny a later consume. Assignment deactivation uses direct SQL so the management RPC’s client lock cannot mask missing authorization-row locks. Keep lock-removal negative controls confined to a disposable database; never alter production migrations to make the harness pass. When the committed base type snapshot already lags its applied schema, generate both the clean base and candidate using the same CLI and formatter. Apply their generated delta to the committed base with a three-way merge, failing on conflicts; exclude unrelated baseline drift rather than manually editing generated objects. The SSO-only committed type delta intentionally excludes existing employee-profile objects absent from the base snapshot. The workflow artifact still contains the complete applied schema. Check packages/users-core as well as the types package after generation because new FK relationships affect nested select inference. The runner prints a schema snapshot digest and recovery directory for a failed cleanup. Use the existing isolated lifecycle cleanup procedure for that directory. The SSO persistence database contract CI workflow repeats this on the exact PR head. No application stack or build is needed for this check. These primitives do not establish Auth account status, confirmed email, MFA, request XML validation, key selection or an issuance permit. Phase 3 must provide those checks, recheck current key/policy state after signing, and burn the original transaction on an unknown signing outcome. Never interpret a consumed database row as permission to retry or emit an assertion without those service gates.

Verification and remaining work

packages/sso/src/__tests__ drives generated signatures through Node-SAML’s SP consumer with disposable in-memory keys. Negative coverage includes wrapping, tampered claims, unsigned assertions, wrong audience/ACS, expiration, replayed InResponseTo, unknown SPs and deflate bombs. Node-SAML uses the same underlying signature library; independent SAML parsing is not independent crypto validation. Parser and XML-signature library failures expose fixed protocol error messages without request-derived diagnostics. Wrapping regressions retain an intact signed assertion beside a distinct unsigned attacker assertion, verify both original assertion and renewed outer signatures, and assert semantic rejection. The harness adds exact Destination/Recipient checks because Node-SAML 5.1.0 does not enforce them for login responses. See packages/sso/README.md for dependency advisories, limits, reproduction commands and the samlify evaluation. Phase 2 supplies the private database primitives described above. Phase 3 supplies authoritative Auth/MFA/eligibility reads, disclosure enforcement, key/policy revision fences, durable audit and disabled Web routes. Phase 4 supplies bilingual management and per-login consent UI. Phase 5 supplies rotation, compromise/disable procedures, an explicitly selected test workspace and connection, exact-commit CI/runtime acceptance and real Claude SSO tests. Runtime profile verification and actual Claude login remain unrun. Run focused tests through ttr resources run -- bun run --cwd packages/sso test and the package’s type-check script. Require applicable exact-commit CI before integration; no local release builds or production configuration changes are part of this phase.