fix(api): exclude disabled and invited users from PAM elevation approvers (#3174) - #3183
Open
bdunncompany wants to merge 1 commit into
Open
fix(api): exclude disabled and invited users from PAM elevation approvers (#3174)#3183bdunncompany wants to merge 1 commit into
bdunncompany wants to merge 1 commit into
Conversation
…vers (LanternOps#3174) resolveElevationApprovers resolved both candidate sets — direct organization_users members and partner_users members whose org_access covers the org — without ever consulting users.status. Memberships are retained when an account is disabled or is still in 'invited', so those accounts kept counting as eligible approvers. Two consequences, both matching the AI-side twin (resolveIntentApprovers): the approver set is inflated with push targets that can never respond, and any logic keyed on the approver count is skewed by ghosts that look like real approvers. Both queries now innerJoin users and require status = 'active', which is the same shape the action-intent resolver uses. Tests assert the join target and the predicate that actually reach drizzle, not the ids the mock was primed to return — the mock resolves its rows regardless of the WHERE, so a test checking only returned ids could not fail if the gate were removed. Confirmed against the previous behaviour: deleting only the status predicate fails exactly the two new cases and leaves the other four passing. Claude-Session: https://claude.ai/code/session_01RUup17Z6KMH9jSkhBBhRJ1
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 #3174.
The defect
resolveElevationApprovers(apps/api/src/services/pamApprovers.ts) resolves both candidate sets — directorganization_usersmembers, andpartner_usersmembers whoseorg_accesscovers the org — without ever consultingusers.status. Memberships are retained when an account is disabled or is stillinvited, so those accounts keep counting as eligible approvers.Both consequences named in the issue follow: the approver set is inflated with push targets that can never respond, and any logic keyed on the approver count is skewed by ghosts that look like real approvers.
The fix
Both candidate queries now
innerJoin(users)and requirestatus = 'active', which is the same shaperesolveIntentApproversuses on the AI side. Nothing else changed — the wildcard-role handling, the partnerorg_accesslogic and the finalmobile_devicesnarrowing are untouched.No new table and no new column, so there is no cascade or export-policy registration to add.
Test evidence
The two new cases assert the join target and the predicate that actually reach drizzle, not the ids the mock was primed to return. That distinction matters here: the mock resolves its rows regardless of the
WHERE, so a test that only checked returned ids would pass just as happily with the gate deleted.Confirmed against the previous behaviour. Removing only the
eq(users.status, 'active')predicate from both queries — keeping the join, so the failure is about the gate and not a broken mock chain:Full runs on the final tree, Node 22.23.2:
tsc --noEmit -p apps/api/tsconfig.json→ exit 0, no outputapps/apifull unit suite (thetest-apijob) → 1271 files passed / 5 skipped, 20144 tests passed / 61 skippedpamApprovers,routes/agents/elevationRequests,services/actionIntents→ 13 files / 174 tests passedScope note
This is the PAM half only.
apps/api/src/services/actionIntents/intentApprovers.tsis the AI-side twin and is being fixed on thetier3-supervised-four-eyesbranch (#3175); this branch deliberately does not touch it, so the two do not conflict.https://claude.ai/code/session_01RUup17Z6KMH9jSkhBBhRJ1