# Spree: Broken Access Control in `PATCH /api/v3/store/carts/:id/associate` (IDOR)

Spree Store API v3 associate endpoint skips cart-token check, letting any authenticated user take over guest carts via reversible Sqids IDs and read checkout PII.

- Payout: Research
- Severity: high
- Program: Spree
- Found by: laijunyue
- Disclosed: 2026-09-22
- CVE: CVE-2026-94462
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-4825-p4xm-pcf2
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-4825-p4xm-pcf2
- Tags: area:web domain:api domain:web impact:data-leak impact:pii-leak kind:research platform:ghsa sev:high tech:ruby technique:source-review vuln:access-control vuln:auth-bypass vuln:idor

**Target:** PATCH /api/v3/store/carts/:id/associate endpoint (spree_api)
**Root cause:** associate resolves the cart by prefixed ID only via find_cart_for_association, omitting the authorize!(:update, @cart, cart_token) check that CartResolvable#find_cart! enforces on sibling actions. Prefixed IDs are reversible Sqids encodings of sequential primary keys, not secrets.
**Steps:**
1. Authenticate: `POST /api/v3/store/auth/login` to obtain attacker JWT.
2. Derive candidate IDs offline: `"cart_" + Sqids.encode([n])` for a range of n (min_length 10, default alphabet, no salt).
3. For each candidate: `PATCH /api/v3/store/carts/<id>/associate` with attacker JWT.
4. A hit returns 200 with victim's `billing_address`/`shipping_address`; non-guest or missing carts return 404/422.
**How found:** Code review of the controller: noticed associate was guarded only by require_authentication! and used find_cart_for_association instead of the CartResolvable#find_cart! path that binds cart_token; then observed prefixed_id is a reversible Sqids encoding of the auto-increment id.
**Exploitation:** Attacker registers a normal store account, enumerates sequential primary keys encoded as Sqids prefixed IDs, and PATCHes associate for each. The service reassigns the guest cart to the attacker and overwrites its email, then render_cart serializes billing/shipping address (name, street, postal code, phone, company) back to the attacker.
**Insight:** When one action in a controller uses a custom finder instead of the shared resolvable/authorize helper, diff it against siblings — and check whether the ID format is a reversible encoding (Sqids/Hashids) of a sequential key rather than a secret token.

