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
{{ message }}
Repository navigation
sec(auth): execute:ri-exchange missing from adminCarvedOuts; admin:* bypasses every exchange guardrail #1644
execute:ri-exchange is missing from adminCarvedOuts (internal/auth/types.go), which lists only {execute, purchases}, {approve_any, purchases} and {retry_any, purchases}. Because of that omission, permissionsAllow (internal/auth/service_group.go) returns true unconditionally for an admin:* holder on execute:ri-exchange, bypassing the account, provider, service and region dimensions and the spend cap.
Why this matters now
This is pre-existing, but it has just become load-bearing. The Azure RI exchange execute work (issue #596 / PR #1515) extends the verb's blast radius from AWS-only to Azure, on an operation the code itself repeatedly describes as financially irreversible. Every guardrail added on that path - including the source-region fix in PR #1515 - is skipped for an admin-wildcard holder.
The separation-of-duties rationale that put execute:purchases in the carve-out list (#923) applies at least as strongly here: an RI exchange consumes existing commitments and buys replacements, and cannot be undone.
Fix direction
Add {execute, ri-exchange} to adminCarvedOuts so an admin must hold an explicit, constrained execute:ri-exchange grant like everyone else. Check for callers/tests that rely on the wildcard shortcut before landing, and mirror whatever migration/communication #923 used for execute:purchases.
Found during adversarial review of PR #1515. Out of scope for that PR.
Note from the #1550 work (PR #1737, now open): this is a one-line change once that PR lands, and it will then be covered end to end automatically.
PR #1737 adds a grant ceiling to the group-permission write paths. Two things there make this issue smaller than it currently looks:
grantCeilingAllows (internal/auth/group_ceiling.go) re-checks adminCarvedOuts inside its admin:* branch, deliberately mirroring permissionsAllow branch for branch. So adding {ActionExecute, ResourceRIExchange} to adminCarvedOuts is picked up by both the enforcement path and the new grant ceiling from the same edit; there is no second place to remember.
checkGrantCeiling refuses to grant any pair in adminCarvedOuts at all, so the moment execute:ri-exchange joins that map, an admin also stops being able to write it onto a group to route around the carve-out. That closes the permission-administration bypass for this verb at the same time, without a separate change.
Worth confirming before making the edit, since it is the substantive question this issue turns on and #1737 does not answer it: ResourceRIExchange's doc comment in internal/auth/types.go says RI exchanges are financially irreversible because the AWS API has no rollback once submitted, which is the same argument #923 used for the three purchase verbs. If that reasoning holds, the omission looks like an oversight rather than a decision. But adding it to adminCarvedOuts means an admin holding only admin:* can no longer execute an RI exchange, and every current admin would need explicit membership in a group granting the verb — the same operational step #923 imposed for purchases. That is a policy call, not a mechanical one.
One consequence to weigh: execute:ri-exchange is constraint-gated via requirePermissionConstraints, unlike the purchase verbs' usual path. PR #1737's ceiling handles constraint containment (a holder capped at MaxPurchaseAmount: 100 cannot hand out an uncapped grant), so that dimension is already covered whichever way this goes.
Activity
cristim commentedon Aug 8, 2026
Note from the #1550 work (PR #1737, now open): this is a one-line change once that PR lands, and it will then be covered end to end automatically.
PR #1737 adds a grant ceiling to the group-permission write paths. Two things there make this issue smaller than it currently looks:
grantCeilingAllows(internal/auth/group_ceiling.go) re-checksadminCarvedOutsinside itsadmin:*branch, deliberately mirroringpermissionsAllowbranch for branch. So adding{ActionExecute, ResourceRIExchange}toadminCarvedOutsis picked up by both the enforcement path and the new grant ceiling from the same edit; there is no second place to remember.checkGrantCeilingrefuses to grant any pair inadminCarvedOutsat all, so the momentexecute:ri-exchangejoins that map, an admin also stops being able to write it onto a group to route around the carve-out. That closes the permission-administration bypass for this verb at the same time, without a separate change.Worth confirming before making the edit, since it is the substantive question this issue turns on and #1737 does not answer it:
ResourceRIExchange's doc comment ininternal/auth/types.gosays RI exchanges are financially irreversible because the AWS API has no rollback once submitted, which is the same argument #923 used for the three purchase verbs. If that reasoning holds, the omission looks like an oversight rather than a decision. But adding it toadminCarvedOutsmeans an admin holding onlyadmin:*can no longer execute an RI exchange, and every current admin would need explicit membership in a group granting the verb — the same operational step #923 imposed for purchases. That is a policy call, not a mechanical one.One consequence to weigh:
execute:ri-exchangeis constraint-gated viarequirePermissionConstraints, unlike the purchase verbs' usual path. PR #1737's ceiling handles constraint containment (a holder capped atMaxPurchaseAmount: 100cannot hand out an uncapped grant), so that dimension is already covered whichever way this goes.Adjacent: #1550, #1629, #1738.
test(api): drive the routed execute handler, not just the permission …
fix(frontend): route each carved-out verb through the group that gran…
sec(auth): carve execute:ri-exchange out of admin:* and seed the gran…
test(api): drive the routed execute handler, not just the permission …
fix(frontend): route each carved-out verb through the group that gran…
sec(exchange): gate the config write that arms unattended RI exchange
sec(exchange): gate the config write that arms unattended RI exchange
sec(exchange): gate the config write that arms unattended RI exchange
sec(exchange): gate the config write that arms unattended RI exchange (…