Skip to content

fix(api): exclude disabled and invited users from PAM elevation approvers (#3174) - #3183

Open
bdunncompany wants to merge 1 commit into
LanternOps:mainfrom
bdunncompany:fix/3174-pam-approver-status
Open

fix(api): exclude disabled and invited users from PAM elevation approvers (#3174)#3183
bdunncompany wants to merge 1 commit into
LanternOps:mainfrom
bdunncompany:fix/3174-pam-approver-status

Conversation

@bdunncompany

Copy link
Copy Markdown
Collaborator

Fixes #3174.

The defect

resolveElevationApprovers (apps/api/src/services/pamApprovers.ts) resolves 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 invited, 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 require status = 'active', which is the same shape resolveIntentApprovers uses on the AI side. Nothing else changed — the wildcard-role handling, the partner org_access logic and the final mobile_devices narrowing 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:

Result
Status predicate removed 2 failed / 4 passed — exactly the two new cases
Fix in place 6 passed

Full runs on the final tree, Node 22.23.2:

  • tsc --noEmit -p apps/api/tsconfig.jsonexit 0, no output
  • apps/api full unit suite (the test-api job) → 1271 files passed / 5 skipped, 20144 tests passed / 61 skipped
  • Targeted: pamApprovers, routes/agents/elevationRequests, services/actionIntents → 13 files / 174 tests passed

Scope note

This is the PAM half only. apps/api/src/services/actionIntents/intentApprovers.ts is the AI-side twin and is being fixed on the tier3-supervised-four-eyes branch (#3175); this branch deliberately does not touch it, so the two do not conflict.

https://claude.ai/code/session_01RUup17Z6KMH9jSkhBBhRJ1

…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PAM: resolveElevationApprovers counts disabled/invited users as eligible approvers

1 participant