Retail store connections are reachable only by their own org's commerce settings managers

FixSecurityService
Shipped
September 17, 2026 at 4:26 AM UTC
Author
Kamo
Commit
6f51fa9

The retail provider-config handlers on CommerceMarketController resolved the session's org and then used whatever market and config uid the path named. Update, delete and the sync logs accepted a session from ANY organization; test-connection and sync accepted no session at all **************** lets a sessionless request through, and both handlers ignored the org they resolved). A store connection holds the store's API key, secret or access token, and test-connection calls the stored storeUrl with them, so anyone who knew a config's uid could repoint its store URL at their own server and have the token delivered there, or trigger syncs against another tenant's store. Create also linked a config to any market id, including another organization's. Every handler (list for a market, list for the org, create, update, delete, sync logs, test-connection, sync) now runs refuseRetailProviderAccess first: - 401 without a session; - 403 without MANAGE_SUBSCRIPTION_SETTINGS, the gate of /settings/features/pos, the only screen that reads or writes these connections (CommerceProviderSetup, its config dialog and sync dashboard), and the read tier of the sibling billing-provider connection on the same page; - 404 unless the market belongs to the session's organization **************** and, when a config is named, the config belongs to that organization AND that market. Another tenant's market or config answers like a missing one. Sync logs now require configId; without it the lookup was by a NULL config. RetailService and RetailSyncOrchestrator live in kamo-shared-library and still look configs up by uid alone; the ownership check runs here, before any of them is called, and nothing else in any service calls them with a caller-supplied uid (the webhook path resolves its own config and verifies the provider HMAC, fail-closed). Callers mapped: kamo-internal commerceProviderApi.ts only (commerceApi.ts's retail provider methods have no users; no mobile, kamo-js or service callers). Every call passes the config's own marketId from its DTO and runs on the **************** page. MarketOverviewTab and the new-market page also list configs and catch errors, so a member without the right who reaches a market page sees no linked providers instead of the list. RetailProviderConfigAccessTest drives every handler: anonymous 401 and a member without the right 403, both with nothing touched; another org's market 404; another org's config under this org's market 404 and a config under the wrong own market 404, with nothing updated, deleted, tested, synced or read; the manager path 200 on all eight. Removing each of the five checks fails exactly the test written for it.

All changes

Like what you see shipping?

All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.

Start Free ForeverView Pricing