Repository navigation
fix(auth): enforce the SSO email-code sign-in policy on every surface (ent#849) - #3428
Merged
Merged
Conversation
…Abilityai/trinity-enterprise#849) The "keep email-code sign-in available" setting was stored and never read. New open-core seam services/login_policy_gate.py (provider registered by the private module; no provider = unchanged OSS behaviour; provider error fails open). Every function that mints or redeems an emailed code now asks it: - /api/auth/email/request and /verify: uniform 403 email_code_disabled, checked before the body is read. /api/auth/mode adds email_code_login_enabled. - MCP inline sign-in: silent suppression and refusal before redeeming. - Workspace guest sign-in, Telegram/WhatsApp /login, public-link email verification: platform users and whitelisted addresses are refused, outsiders keep codes. Refusals read like a normal send or a wrong code; while the policy refuses, every code request takes the same detached background path, so timing does not reveal membership. - /token (admin password) never consults the gate. Login page shows SSO buttons and Admin Login when email codes are off, and SSO buttons no longer disappear when email auth is off. A page opened before the toggle maps the 403 to "Sign in with SSO"; the CLI does the same. A per-function AST guard fails CI when a new caller of create_login_code, verify_login_code, create_verification or verify_code skips the gate. Mutation-checked: each new backend and frontend test goes red with its fix removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bilityai/trinity-enterprise#849) The Slack legacy require_email check mints and redeems its own emailed code; it now takes the same member rule and detached-send path as Telegram and WhatsApp, and draws the code from secrets. The public-link request step counts every request per address while codes are refused, so the 429 lands on the same request for a member and an outsider. The caller guard also covers send_verification_code, which is how the Slack flow escaped it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Private half: the provider lands in a draft enterprise PR that depends on this one. |
…gs fragment (Abilityai/trinity-enterprise#849) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le.com (Abilityai/trinity-enterprise#849) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # tests/registry.json
Contributor
|
merge-train (2026-10-09): merged |
…in: import-line union with #3412)
Contributor
|
merge-train (2026-10-10): merged |
3 tasks
webmixgamer
added a commit
that referenced
this pull request
Oct 10, 2026
…erges) into the review round The remote PR branch carried two merge-train merges of dev on top of 37829f0 (#3412, #3428, #3479 and others). One conflict, Settings.vue: - the whitelist add now goes through #3479's settingsStore.addWhitelistEmail, which returns { email, existingAccountRole } so the form can still name an existing account's role (emailWhitelist.spec updated, pass-through case added); - the add's refusals use #3455's InlineError under the form, beside the account notice, and the page-wide error is no longer set (the eyeball of d9473d3: a 409 landed at the bottom of Settings); pinned in workspaceOnlyWiring.spec; - #3479's guarded workspaceOnlyUsers computed, plus this round's filter reset. test_3455's whitelist fake gains the two reads the route now makes. Re-run on the merge: targeted backend 3983 passed (134 files incl. every unit test the merges changed), frontend 5501 passed (3 known host-only files), MCP 829/831 (2 = the host's pnpm layout), CLI suites green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes abilityai/trinity-enterprise#849 (cross-repo: the private issue closes by hand at release).
Problem
The SSO setting "Keep email-code sign-in available" was stored and never read. Unchecking it did not stop emailed 6-digit codes from signing users in.
Change
New open-core seam
services/login_policy_gate.py(same shape asmfa_gate). With no provider registered, behaviour is unchanged. A provider error fails open. Every function that mints, redeems or sends an emailed code now asks it:POST /api/auth/email/request,/verifyemail_code_disabledfor every address, before the body is readGET /api/auth/modeemail_code_login_enabled/login, Slackrequire_email, public-link email verificationPOST /api/token(admin password)On the surfaces that also serve outsiders, a refusal reads like a normal send or a wrong code. While codes are refused, every request step sends in a detached task, and the public-link per-address 429 counts every request, so neither the body, the status nor the timing reveals membership.
UI: the login page shows the SSO buttons and Admin Login when email codes are off, and SSO buttons no longer vanish when email auth is off. A page opened before the toggle maps the 403 to "Sign in with SSO"; the CLI prints the same next step. The settings hint names the surfaces and says admin password login always stays available.
Guard:
test_every_login_code_caller_asks_the_gatefails CI when a new function callscreate_login_code/verify_login_code/create_verification/verify_code/send_verification_codewithout asking the gate (exemptions listed with reasons).The private module's provider lands in a separate draft PR that depends on this one. No submodule pointer move here.
Tests
tests/unit/test_ent849_email_code_policy.py: 35 tests (gate, every surface, uniform bodies/timing/429,/tokenuntouched, AST caller guard).src/frontend/tests/unit/loginEmailCodePolicy.spec.js: 9 mounted tests (render states, loading hold, store refusal, stale-page 403).portalAgentsAtCentre.spec.js, which fails ondevwithout this change (date-dependent).Security
CSO diff report:
docs/security-reports/cso-diff-2026-10-09-ent849-email-code-policy.md. Two medium findings from the adversarial pass (Slack flow ungated; public-link 429 membership oracle) are fixed in this branch. Open: 0.Not in this PR: revoking sessions, MCP keys and channel bindings issued before the toggle (abilityai/trinity-enterprise#851).
🤖 Generated with Claude Code