Problem
Issue #60 fixed the SEC-01 fail-open class (a bare HasPermissionAPI verb
check with no per-permission Constraints evaluation) on execute-any/
execute-own (direct-execute), approve-any/approve-own (session approve
AND the approveViaToken email deep-link flow), and runPlannedPurchase.
Issue #60's own review comments widened the census beyond those three named
verbs to a full sweep of requirePermissionConstraints call sites in
internal/api. Two more sites were fixed alongside #60 (approveViaToken's
own defense-in-depth check). Five sites from that wider census remain
unfixed:
| Handler |
Where |
upsertLadderConfig |
internal/api/handler_ladder.go (~line 77 as of the review) |
updateConfig |
internal/api/handler_config.go (~line 59) |
updateRIExchangeConfig |
internal/api/handler_ri_exchange.go (~line 963) |
marketplaceList |
internal/api/handler_marketplace.go (~line 139) |
revokePurchase |
internal/api/handler_purchases_revoke.go (~line 139) |
Line numbers are from the 2026-07-28 review pass and may be stale; re-locate
each handler on current origin/main before fixing.
marketplaceList and revokePurchase move money in the seller/refund
direction, where MaxPurchaseAmount may not be the relevant constraint but
Providers/AccountIDs/Services are -- the fix for each site should use
whichever Constraints dimensions actually apply to that verb, not
mechanically copy purchaseConstraintSets.
Note on the two independent gates
getAllowedAccounts (session/group scope) and requirePermissionConstraints
(per-permission Constraints) are independent gates. A permission with no
AccountIDs constraint satisfies the second unconditionally, so passing
requirePermissionConstraints is not evidence that account scoping also
happened, and vice versa. Verify both gates independently for each of the
five sites above.
Fix direction
For each handler: build the constraint set(s) from whichever data the verb
actually mutates (mirroring purchaseConstraintSets's approach of deriving
from store/persisted data, never client-supplied numbers), and call
requirePermissionConstraints(ctx, session, action, resource, sets) (the
action parameter was added in #60; thread the real verb for each site
through it). Add a regression test per site mirroring
TestHandler_executePurchase_PermissionConstraintsDenied: a session that
passes the bare verb gate but whose Constraints reject the request must
get 403 before any state mutation.
Triage
type/security, priority/p1, severity/high, impact/all-users,
effort/m, triaged -- same fail-open class as #60 on the same
Constraints struct.
Problem
Issue #60 fixed the SEC-01 fail-open class (a bare
HasPermissionAPIverbcheck with no per-permission
Constraintsevaluation) onexecute-any/execute-own(direct-execute),approve-any/approve-own(session approveAND the
approveViaTokenemail deep-link flow), andrunPlannedPurchase.Issue #60's own review comments widened the census beyond those three named
verbs to a full sweep of
requirePermissionConstraintscall sites ininternal/api. Two more sites were fixed alongside #60 (approveViaToken'sown defense-in-depth check). Five sites from that wider census remain
unfixed:
upsertLadderConfiginternal/api/handler_ladder.go(~line 77 as of the review)updateConfiginternal/api/handler_config.go(~line 59)updateRIExchangeConfiginternal/api/handler_ri_exchange.go(~line 963)marketplaceListinternal/api/handler_marketplace.go(~line 139)revokePurchaseinternal/api/handler_purchases_revoke.go(~line 139)Line numbers are from the 2026-07-28 review pass and may be stale; re-locate
each handler on current
origin/mainbefore fixing.marketplaceListandrevokePurchasemove money in the seller/refunddirection, where
MaxPurchaseAmountmay not be the relevant constraint butProviders/AccountIDs/Servicesare -- the fix for each site should usewhichever
Constraintsdimensions actually apply to that verb, notmechanically copy
purchaseConstraintSets.Note on the two independent gates
getAllowedAccounts(session/group scope) andrequirePermissionConstraints(per-permission
Constraints) are independent gates. A permission with noAccountIDsconstraint satisfies the second unconditionally, so passingrequirePermissionConstraintsis not evidence that account scoping alsohappened, and vice versa. Verify both gates independently for each of the
five sites above.
Fix direction
For each handler: build the constraint set(s) from whichever data the verb
actually mutates (mirroring
purchaseConstraintSets's approach of derivingfrom store/persisted data, never client-supplied numbers), and call
requirePermissionConstraints(ctx, session, action, resource, sets)(theactionparameter was added in #60; thread the real verb for each sitethrough it). Add a regression test per site mirroring
TestHandler_executePurchase_PermissionConstraintsDenied: a session thatpasses the bare verb gate but whose
Constraintsreject the request mustget 403 before any state mutation.
Triage
type/security,priority/p1,severity/high,impact/all-users,effort/m,triaged-- same fail-open class as #60 on the sameConstraintsstruct.