Relationships
#1819 IdP group memberships go stale indefinitely — user.idpGroups is only written by the SSO login hook
Opened by keeb · 8/25/2026· Shipped 9/3/2026
Problem
user.idpGroups is the source of truth for idp-group: grant subjects in swamp serve, but it is written in exactly one place — the swamp-club SSO provisioning hook (lib/auth.ts:1038-1041):
await db.collection("user").updateOne(
authIdFilter(user.id),
{ $set: { idpGroups, emailVerified: true } },
);provisionUserOnEveryLogin: true (lib/auth.ts:1020) means this fires on every SSO login — and only on login. There is no other writer of that field.
Impact
The refresh path documented in #1171 does not actually refresh group membership:
- A group changes in the IdP.
user.idpGroupsin Mongo stays stale until that user next completes an SSO login. This is unbounded — days or weeks for a long-lived session.- swamp serve
CollectiveRefreshServiceticks every--group-refresh-interval(default 4h), calls userinfo, which reachesgetAdditionalUserInfoClaim(lib/auth.ts:980-990), which reads that same staleuser.idpGroups.
The serve refresh loop is faithfully re-fetching a value that only changes at login. It cannot fix staleness it inherits. The real bound on an IdP group change taking effect is "until the user next authenticates via SSO", not the 4h interval the serve manual describes.
This is security-relevant because idp-group: subjects gate grant enforcement (#1154). A user removed from an IdP group keeps the corresponding swamp permissions for as long as their session survives.
Related inconsistency
Inside getAdditionalUserInfoClaim, collectives are resolved live through _collectiveLookup, while groups are read from the frozen user document field. Two claims in the same userinfo response with materially different freshness guarantees, which is easy to misread when reasoning about grant behavior.
Suggested direction
Options, roughly in increasing order of effort:
- Re-resolve groups at userinfo time rather than reading the stored field, so the existing 4h serve refresh becomes meaningful.
- Push-invalidate on IdP group change (requires SCIM or equivalent inbound provisioning — filed separately).
- At minimum, correct the serve manual so the documented deprovisioning window matches actual behavior.
Option 1 alone would bring the effective bound from unbounded down to the documented 4h.
Shipped
Click a lifecycle step above to view its details.