You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When an identity provider renames a domain, the address in a user's SSO claims diverges from the address the registry has stored for that account. quiltx writes and validates SSO selectors against stored addresses, while the registry evaluates them against claims. After a rename the two describe the same person with different strings, and every selector written from the stale side silently stops matching. Nothing in quiltx can currently detect this.
The mechanism
Three properties of the registry, all observable from outside it, combine to produce the drift:
SSO identity binds to the provider's stable subject identifier, not to the email. The first SSO login for an account records a link between that account and the provider's immutable subject id. Every subsequent login resolves through that link. The email claim is consulted only when no link exists yet.
The stored email is a first-login snapshot and is never refreshed. Because the link resolves first, later logins never re-read the email claim, so the account keeps whatever address it carried when it was created. Nothing writes it back.
SSO role mappings are evaluated against the claims.sso.email and sso.hd selectors are matched against the token the provider sends, not against the stored account record.
Taken together: after an IdP domain rename, one account has two addresses — the claim address (new domain) and the stored address (old domain) — and each half of the system sees only one of them. Nothing fails loudly. Users keep signing in and landing in the correct account, because (1) resolves them by subject id. Only the selectors quietly stop matching.
The same mechanism applies to an individual user rename; a domain rename is just the case that hits every account at once.
How this breaks quiltx
a. sso.email written from stored addresses matches nobody. Given local@old.example stored and local@new.example in the claims, a selector listing the former never fires. The grant simply does not happen. quiltx cannot observe claims, so no warning is possible today.
b. sso.hd for the old domain is dead, and the new domain must be declared instead. A config predating the rename declares the old domain across every role; those selectors match nobody. This is invisible because the roles still exist and users still hold whatever was assigned at their last pre-rename login — access looks correct right up until someone new signs in, or an existing user's roles are recomputed.
c. --yaml capture round-trips the stale side. Capture reads stored addresses and emits them as config. Replaying that capture writes selectors in an address space the registry will never match. So the tool's own capture/replay cycle manufactures dead config once a rename has happened, and the output looks entirely plausible.
d. Roster and users: resolution operate in stored-address space._roster_address_is_held and _resolve_configured_user both index accounts by stored email. A roster written correctly — in claim space, new domain — therefore finds no account for a person who demonstrably has one, and pushes them toward creation.
This is the sharp edge: the same address is simultaneously the only correct value for the selector and an unknown value for resolution. An operator cannot satisfy both halves with one string.
e. The rename guard fires but names a remedy quiltx cannot perform. A domain rename preserves the local part, so _accounts_sharing_a_local_part catches it and refuses to create — the right call, and it prevents the duplicate. But its message tells the operator to set the existing account's email, and quiltx never calls set_email (#117). So the guard converts a silent duplication into a hard stop with a manual, out-of-band remedy.
Why a clean fix may not exist
quiltx cannot see claims. Nothing in the admin API exposes what the IdP sends, so the authoritative value for selector matching is not readable by the tool that writes selectors.
The stored email is not authoritative for matching, yet it is the only identity field quiltx can read. Every resolution path is built on it.
So this issue is filed for the mechanism, not for a proposed patch. Recording it matters because the failure is silent in both directions and the tool currently reads as though it is working.
Possible mitigations — detection, not repair
Domain census warning. Compare the set of domains appearing in sso.hd / sso.email against the domains present across registry accounts. A declared domain that no account uses, or an account domain no selector mentions, is a strong drift signal. This needs no new data — quiltx already reads every account.
Extend the local-part check to selector validation, not just creation. An sso.email address matching no account, whose local part matches an account at a different domain, is probably drift rather than a new person. _accounts_sharing_a_local_part already computes exactly this.
Document that selectors are claim-space while captures are stored-space, and that the two are not interchangeable after a rename. Cheapest of the three, and possibly the highest value, since it is the assumption that makes (c) dangerous.
Mark capture output as needing review of selector addresses, so a replayed capture is not mistaken for a verified config.
Mitigation 1 would have surfaced a real instance: a config whose every role declared the old domain in sso.hd, against a registry in which no account's claims carried that domain any longer.
Not addressed
Repair. Realigning stored addresses to claim addresses is #117's territory; this issue is about detecting that the two have diverged at all, and about the fact that captures propagate the divergence.
Summary
When an identity provider renames a domain, the address in a user's SSO claims diverges from the address the registry has stored for that account. quiltx writes and validates SSO selectors against stored addresses, while the registry evaluates them against claims. After a rename the two describe the same person with different strings, and every selector written from the stale side silently stops matching. Nothing in quiltx can currently detect this.
The mechanism
Three properties of the registry, all observable from outside it, combine to produce the drift:
SSO identity binds to the provider's stable subject identifier, not to the email. The first SSO login for an account records a link between that account and the provider's immutable subject id. Every subsequent login resolves through that link. The email claim is consulted only when no link exists yet.
The stored email is a first-login snapshot and is never refreshed. Because the link resolves first, later logins never re-read the email claim, so the account keeps whatever address it carried when it was created. Nothing writes it back.
SSO role mappings are evaluated against the claims.
sso.emailandsso.hdselectors are matched against the token the provider sends, not against the stored account record.Taken together: after an IdP domain rename, one account has two addresses — the claim address (new domain) and the stored address (old domain) — and each half of the system sees only one of them. Nothing fails loudly. Users keep signing in and landing in the correct account, because (1) resolves them by subject id. Only the selectors quietly stop matching.
The same mechanism applies to an individual user rename; a domain rename is just the case that hits every account at once.
How this breaks quiltx
a.
sso.emailwritten from stored addresses matches nobody. Givenlocal@old.examplestored andlocal@new.examplein the claims, a selector listing the former never fires. The grant simply does not happen. quiltx cannot observe claims, so no warning is possible today.b.
sso.hdfor the old domain is dead, and the new domain must be declared instead. A config predating the rename declares the old domain across every role; those selectors match nobody. This is invisible because the roles still exist and users still hold whatever was assigned at their last pre-rename login — access looks correct right up until someone new signs in, or an existing user's roles are recomputed.c.
--yamlcapture round-trips the stale side. Capture reads stored addresses and emits them as config. Replaying that capture writes selectors in an address space the registry will never match. So the tool's own capture/replay cycle manufactures dead config once a rename has happened, and the output looks entirely plausible.d. Roster and
users:resolution operate in stored-address space._roster_address_is_heldand_resolve_configured_userboth index accounts by stored email. A roster written correctly — in claim space, new domain — therefore finds no account for a person who demonstrably has one, and pushes them toward creation.This is the sharp edge: the same address is simultaneously the only correct value for the selector and an unknown value for resolution. An operator cannot satisfy both halves with one string.
e. The rename guard fires but names a remedy quiltx cannot perform. A domain rename preserves the local part, so
_accounts_sharing_a_local_partcatches it and refuses to create — the right call, and it prevents the duplicate. But its message tells the operator to set the existing account's email, and quiltx never callsset_email(#117). So the guard converts a silent duplication into a hard stop with a manual, out-of-band remedy.Why a clean fix may not exist
So this issue is filed for the mechanism, not for a proposed patch. Recording it matters because the failure is silent in both directions and the tool currently reads as though it is working.
Possible mitigations — detection, not repair
sso.hd/sso.emailagainst the domains present across registry accounts. A declared domain that no account uses, or an account domain no selector mentions, is a strong drift signal. This needs no new data — quiltx already reads every account.sso.emailaddress matching no account, whose local part matches an account at a different domain, is probably drift rather than a new person._accounts_sharing_a_local_partalready computes exactly this.Mitigation 1 would have surfaced a real instance: a config whose every role declared the old domain in
sso.hd, against a registry in which no account's claims carried that domain any longer.Not addressed
Repair. Realigning stored addresses to claim addresses is #117's territory; this issue is about detecting that the two have diverged at all, and about the fact that captures propagate the divergence.