- Shipped
- August 27, 2026 at 4:21 PM UTC
- Author
- Kamo
- Commit
- 45a1b63
Thirty-nine endpoints were protected only by "are you signed in". Every order handler on POSController and every pricing and tax handler on CommerceMarketController read the org off the session and then checked nothing else, so any authenticated member of any role could create, edit, refund and re-status orders, and rewrite price lists, discounts, tax classes, tax zones, tax rates and customer tiers, inside their own org. Never cross-tenant — role simply meant nothing there. QuoteController's javadoc had said so outright. POSController, 11 endpoints: GET /orders and /orders/{id} take VIEW_ORDERS; POST /orders, PATCH /orders/{id}, PATCH /orders/{id}/status, POST /orders/{id}/returns, PATCH /returns/{id}/status and PATCH /fulfillments/{id} take MANAGE_ORDERS. Two reads are deliberately ACCESS_COMMERCE rather than VIEW_ORDERS — GET /config is read by PipelineView before it knows the member's vertical, and GET /accounts is the shared commerce customer directory the subscription wizard and signups tab also use, so gating either on the order rights would refuse members who legitimately never touch an order. ACCESS_COMMERCE is the root of the POS tree: everyone with any commerce right holds it and everyone with none is now refused. PUT /config takes MANAGE_PRICING because the row it writes carries quoteConversionBilling. CommerceMarketController, 28 endpoints: reads of customer tiers, tax classes, tax zones, tax rates, discounts, price lists and price-list entries take VIEW_PRICING; every create, update and delete on those seven takes MANAGE_PRICING. Discounts were not in the brief's list but sit inside the same block and a discount is a price change, so leaving them open would have been arbitrary. No new helper was needed on CommerceMarketController: hasRight already existed there, byte-identical to POSController's, and was called by NOTHING. A helper nobody calls reads in review exactly like a controller that is guarded. The eleven POSController handlers are removed from **************** the ratchet fails on stale entries, so the count falls rather than the file rotting. The CommerceMarketController pricing handlers were never in that file — its GUARD regex matches getCachedOrganizationId, so a handler that resolves a session but checks no right has always looked guarded to the scan. That is a real limitation of the ratchet, not a gap in this change. OrderAndPricingRightsGateTest pins all 39 by name and was mutation-tested: deleting a check, swapping MANAGE for VIEW on a write, moving a check after the service call, replacing the session org with a literal, putting a line back in the baseline, and hoisting the 403 above the 401 each produce a distinct named failure.