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): enforce per-permission Constraints on execute-any/own, approve-any/own, runPlanned (SEC-01 follow-up to #1210) #60
PR LeanerCloud/cloud-commitments-cli#1210 fixes SEC-01 (issue LeanerCloud/cloud-commitments-cli#1141) for execute:purchases and execute:ri-exchange by enforcing per-permission Constraints (MaxPurchaseAmount, Providers, Services, Regions, AccountIDs) at execution time. The same fail-open class still exists on the sibling money-spending verbs whose constraints are also silently ignored:
execute-any:purchases / execute-own:purchases — Handler.authorizeSessionExecuteDirect (internal/api/handler_purchases.go:792) gates the direct-execute path with a bare HasPermissionAPI(action=execute-any, resource=purchases) / HasPermissionAPI(action=execute-own, ...) check. If the granting permission carries Constraints (e.g. MaxPurchaseAmount: $500, Providers: [aws]), they are not evaluated. A user who satisfies the upstream execute:purchases constraint set (PR fix(auth): enforce per-permission constraints on execute paths cloud-commitments-cli#1210) but whose execute-any:purchases is capped tighter can still direct-execute past the tighter cap.
approve-any:purchases / approve-own:purchases — Handler.authorizeSessionApprove (internal/api/handler_purchases.go:592) gates session-approve via the bare verb check. approvePurchaseViaSession then calls purchase.Manager.ApproveAndExecute which synchronously fires the AWS purchase (internal/api/handler_purchases.go:572); the approve path is also an execute path, but the approve permission's Constraints aren't enforced.
runPlannedPurchase (internal/api/handler_purchases.go:306) calls requirePermission("execute", "purchases") then transitions the planned execution to running without consulting Constraints against the persisted recommendations. A user who runs a plan they didn't create (via update-any:purchases) bypasses any Constraints on their own execute:purchases permission.
All three are the same fail-open class PR LeanerCloud/cloud-commitments-cli#1210 fixed for the web execute path: a bare verb gate runs, the matcher's nil request-side constraints short-circuit checkPermissionConstraints to allow, and the configured caps are silently ignored on a money-mutation path.
For execute-any / execute-own: build the same per-rec constraint sets as purchaseConstraintSets in validateExecutePurchaseRequest and call requirePermissionConstraints(session, "execute-any" or "execute-own", "purchases", sets) inside authorizeSessionExecuteDirect once the verb branch is chosen.
For approve-any / approve-own: in authorizeSessionApprove, after the verb branch is chosen, re-hydrate the execution's recommendations and build purchaseConstraintSets against them before allowing ApproveAndExecute to fire.
For runPlannedPurchase: re-hydrate the planned execution's recommendations and call requirePermissionConstraints(session, "execute", "purchases", purchaseConstraintSets(...)) before the pending|paused → running transition. Same shape as the web execute path.
Regression tests should mirror the SEC-01 ones in TestHandler_executePurchase_PermissionConstraintsDenied: a session that passes the bare verb gate but whose constraints reject the request returns 403 before any state mutation / SDK call.
Triage
type/security, priority/p1, severity/high, urgency/soon, impact/all-users, effort/m, triaged. Same severity as the parent SEC-01: a constrained purchaser can spend past their configured cap via the unfixed verb on a money path.
Problem
PR LeanerCloud/cloud-commitments-cli#1210 fixes SEC-01 (issue LeanerCloud/cloud-commitments-cli#1141) for
execute:purchasesandexecute:ri-exchangeby enforcing per-permissionConstraints(MaxPurchaseAmount, Providers, Services, Regions, AccountIDs) at execution time. The same fail-open class still exists on the sibling money-spending verbs whose constraints are also silently ignored:execute-any:purchases/execute-own:purchases—Handler.authorizeSessionExecuteDirect(internal/api/handler_purchases.go:792) gates the direct-execute path with a bareHasPermissionAPI(action=execute-any, resource=purchases)/HasPermissionAPI(action=execute-own, ...)check. If the granting permission carriesConstraints(e.g.MaxPurchaseAmount: $500,Providers: [aws]), they are not evaluated. A user who satisfies the upstreamexecute:purchasesconstraint set (PR fix(auth): enforce per-permission constraints on execute paths cloud-commitments-cli#1210) but whoseexecute-any:purchasesis capped tighter can still direct-execute past the tighter cap.approve-any:purchases/approve-own:purchases—Handler.authorizeSessionApprove(internal/api/handler_purchases.go:592) gates session-approve via the bare verb check.approvePurchaseViaSessionthen callspurchase.Manager.ApproveAndExecutewhich synchronously fires the AWS purchase (internal/api/handler_purchases.go:572); the approve path is also an execute path, but the approve permission's Constraints aren't enforced.runPlannedPurchase(internal/api/handler_purchases.go:306) callsrequirePermission("execute", "purchases")then transitions the planned execution torunningwithout consulting Constraints against the persisted recommendations. A user who runs a plan they didn't create (viaupdate-any:purchases) bypasses any Constraints on their ownexecute:purchasespermission.All three are the same fail-open class PR LeanerCloud/cloud-commitments-cli#1210 fixed for the web execute path: a bare verb gate runs, the matcher's
nilrequest-side constraints short-circuitcheckPermissionConstraintsto allow, and the configured caps are silently ignored on a money-mutation path.Fix sketch
Apply the same
requirePermissionConstraintspattern PR LeanerCloud/cloud-commitments-cli#1210 introduced:execute-any/execute-own: build the same per-rec constraint sets aspurchaseConstraintSetsinvalidateExecutePurchaseRequestand callrequirePermissionConstraints(session, "execute-any" or "execute-own", "purchases", sets)insideauthorizeSessionExecuteDirectonce the verb branch is chosen.approve-any/approve-own: inauthorizeSessionApprove, after the verb branch is chosen, re-hydrate the execution's recommendations and buildpurchaseConstraintSetsagainst them before allowingApproveAndExecuteto fire.runPlannedPurchase: re-hydrate the planned execution's recommendations and callrequirePermissionConstraints(session, "execute", "purchases", purchaseConstraintSets(...))before thepending|paused → runningtransition. Same shape as the web execute path.Regression tests should mirror the SEC-01 ones in
TestHandler_executePurchase_PermissionConstraintsDenied: a session that passes the bare verb gate but whose constraints reject the request returns 403 before any state mutation / SDK call.Triage
type/security,priority/p1,severity/high,urgency/soon,impact/all-users,effort/m,triaged. Same severity as the parent SEC-01: a constrained purchaser can spend past their configured cap via the unfixed verb on a money path.