Found during the clause-② contract review of PR #11038 (skills seat, session session_01MsbKEG4LtERSLaDrbehM3e, 2026-08-22). Filed unassigned as a finding — recording only, first-touch grading is triage's.
Observation
PR #11038 correctly closes the anonymous disclosure per the maintainer's Option-A ruling: the break-glass guard now runs only after resolveActor(ctx) establishes an identity. But inside the guard, target resolution is unchanged:
lettargetId: string|undefined=ctx?.body?.userId??ctx?.body?.user_id;if(!targetId&&ctx.path==='/delete-user')targetId=breakGlassActor.userId;
On /delete-user — a self-service path whose vendor contract names no target (the subject IS the caller) — the guard still prefers body.userId when present. So any authenticated caller (no admin role required; /delete-user sits outside the /admin/ lane) can POST {"userId": "<target>"} and read the answer off the refusal: 409 LAST_LOCAL_CREDENTIAL ⇒ the named user is the environment's break-glass holder; the vendor's ordinary response ⇒ not.
The guard queries and refuses on a target the endpoint itself would never act on — the lookup answers a question the caller was never authorized to ask, merely authenticated to ask.
Severity framing
Strictly narrower than #10776 (attacker needs any account in the org, e.g. self-signup deployments or any compromised low-privilege user) — which is why this is a residual finding, not a defect in PR #11038: the ruling was "authentication before the guard", implemented faithfully, and the pre-fix code had the same body-first preference with no authentication at all.
Shape of a fix, not a prescription
On /delete-user, ignore body.userId entirely and key the guard on the resolved actor (targetId = breakGlassActor.userId unconditionally for that path) — the endpoint's subject is the caller, so the guard should ask about no one else. The /admin/* paths are unaffected (target-naming is their contract, and adminMiddleware authorizes the caller before anything is served... though note the guard's 409 may still precede the admin-authorization refusal for an authenticated non-admin — worth measuring in the same pass).
Refs: #10776 (the anonymous half, fixed) · PR #11038 (the fix under review when this was found).
Found during the clause-② contract review of PR #11038 (skills seat, session
session_01MsbKEG4LtERSLaDrbehM3e, 2026-08-22). Filed unassigned as afinding— recording only, first-touch grading is triage's.Observation
PR #11038 correctly closes the anonymous disclosure per the maintainer's Option-A ruling: the break-glass guard now runs only after
resolveActor(ctx)establishes an identity. But inside the guard, target resolution is unchanged:On
/delete-user— a self-service path whose vendor contract names no target (the subject IS the caller) — the guard still prefersbody.userIdwhen present. So any authenticated caller (no admin role required;/delete-usersits outside the/admin/lane) can POST{"userId": "<target>"}and read the answer off the refusal: 409LAST_LOCAL_CREDENTIAL⇒ the named user is the environment's break-glass holder; the vendor's ordinary response ⇒ not.The guard queries and refuses on a target the endpoint itself would never act on — the lookup answers a question the caller was never authorized to ask, merely authenticated to ask.
Severity framing
Strictly narrower than #10776 (attacker needs any account in the org, e.g. self-signup deployments or any compromised low-privilege user) — which is why this is a residual finding, not a defect in PR #11038: the ruling was "authentication before the guard", implemented faithfully, and the pre-fix code had the same body-first preference with no authentication at all.
Shape of a fix, not a prescription
On
/delete-user, ignorebody.userIdentirely and key the guard on the resolved actor (targetId = breakGlassActor.userIdunconditionally for that path) — the endpoint's subject is the caller, so the guard should ask about no one else. The/admin/*paths are unaffected (target-naming is their contract, andadminMiddlewareauthorizes the caller before anything is served... though note the guard's 409 may still precede the admin-authorization refusal for an authenticated non-admin — worth measuring in the same pass).Refs: #10776 (the anonymous half, fixed) · PR #11038 (the fix under review when this was found).