Skip to content

sec(auth): enforce per-permission Constraints on ladder/config/RI-exchange-config/marketplace/revoke (SEC-01 follow-up to #60) #404

Description

@cristim

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions