Loading...
Skip to main content

CVE-2026-94462 — Spree: Broken Access Control in spree_api — CVE...

HIGH

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

Published September 22, 2026Updated September 22, 2026Source: osv

Summary

## Summary The Store API v3 endpoint `PATCH /api/v3/store/carts/:id/associate` binds a guest cart to the authenticated caller without verifying possession of that cart. It locates the cart by prefixed ID only — `current_store.carts.where(user: [nil, current_user]).find_by_prefix_id!(params[:id])` — and omits the `authorize!(:update, @cart, cart_token)` check that every other action in the controller performs via `CartResolvable`. Because prefixed IDs are a reversible Sqids encoding of the auto-increment primary key (obfuscation, not a token), an authenticated customer can name arbitrary guest cart IDs, take them over, and read the checkout addresses stored on them. This is broken access control / IDOR, reachable by any low-privilege registered user. ## Severity Requires an authenticated store account and depends on target guest carts already carrying an address and not yet being associated, on a store not running in `login_required` mode. Confidentiality impact is the driver (guest checkout PII); integrity impact is limited and recoverable (cart reassignment + email overwrite on an in-progress cart). Not Critical: the action is gated behind authentication (`PR:L`, not `PR:N`) and constrained by cart state, so it is not anonymously exploitable. ## Details **Root cause:** `associate` skips the cart-possession check its sibling actions enforce and trusts a guessable identifier as the sole locator. **Entry point.** `Spree::Api::V3::Store::CartsController#associate` (`carts_controller.rb:88-96`), guarded only by `prepend_before_action :require_authentication!, only: [:index, :associate]`. That requires the *caller* be authenticated; it does not tie the request to a specific guest cart. ```ruby # spree/api/app/controllers/spree/api/v3/store/carts_controller.rb:88-96 # PATCH /api/v3/store/carts/:id/associate def associate @cart = find_cart_for_association result = Spree.cart_associate_service.call(guest_order: @cart, user: current_user, guest_only: true) if result.success? render_cart else render_service_error(result.error.to_s) end end ``` **Missing check.** `find_cart_for_association` (`carts_controller.rb:177-178`) resolves any guest cart (`user IS NULL`) in the store by ID with no `authorize!(..., cart_token)`. Contrast `CartResolvable#find_cart!`, which binds the token. ```ruby # spree/api/app/controllers/spree/api/v3/store/carts_controller.rb:177-178 def find_cart_for_association current_store.carts.where(user: [nil, current_user]).find_by_prefix_id!(params[:id]) end ``` **Identifier.** `prefixed_id` is `"cart_" + SQIDS.encode([id])` with `SQIDS = Sqids.new(min_length: 10)` (`prefixed_id.rb:17,56`) — default alphabet, no salt, no blocklist. Sqids is non-cryptographic and reversible, so candidate IDs are derivable offline from sequential primary keys. ```ruby # spree/core/app/models/concerns/spree/prefixed_id.rb:17-56 SQIDS = Sqids.new(min_length: 10) def prefixed_id return nil unless id.present? "#{self.class._prefix_id_prefix}_#{Spree::PrefixedId::SQIDS.encode([id])}" end ``` **Data flow.** `Spree.cart_associate_service.call(guest_order: @cart, user: current_user, guest_only: true)` reassigns the owner and overwrites email, preserving existing addresses via `bill_address ||= / ship_address ||=`. `render_cart` then serializes `billing_address`/`shipping_address` (first_name, last_name, address1, address2, city, postal_code, phone, company) back to the caller. ## PoC **Preconditions:** attacker holds an ordinary store account (self-service registration) and the store's publishable key (a front-end credential, present in any headless storefront bundle); one or more guest carts carry checkout addresses; store is not in `login_required` mode. 1. Authenticate: `POST /api/v3/store/auth/login` → attacker JWT. 2. Derive candidate IDs offline: `"cart_" + Sqids.encode([n])` for a range of `n`. 3. For each candidate: `PATCH /api/v3/store/carts/<id>/associate` with the attacker JWT. A hit returns `200` with the victim's `billing_address`/`shipping_address`; non-guest or missing carts return `404`/`422`. ## Impact **Confidentiality:** an authenticated attacker can enumerate guest cart IDs and read checkout PII (name, street, postal code, phone) on carts they don't own. **Integrity:** limited and recoverable — each call reassigns the guest cart and overwrites its email, disrupting the original guest's in-progress cart. Requires a registered account, so not anonymously exploitable. ## Remediation Update to Spree 5.4.4 or 5.5.4. Your storefront, based on https://github.com/spree/storefront, doesn't need any updates because it has always sent a cart token when associating carts; this is a backend issue.

Advisory Timeline

  • First published September 22, 2026 (20 days ago).
  • Last revised September 22, 2026 — 0 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

Bundler
Update spree_api to 5.5.4 or later
gem install spree_api -v 5.5.4
Bundler
Update spree_api to 5.4.4 or later
gem install spree_api -v 5.4.4

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (2)

Affects 1 ecosystem: rubygems (1).

PackageEcosystemAffectedFixed In
spree_api
rubygems
5.5.0, 5.5.1, 5.5.2, 5.5.35.5.4
spree_api
rubygems
5.4.0, 5.4.1, 5.4.2, 5.4.35.4.4

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

  • CWE-639
    Insecure Direct Object Reference (IDOR)MITRE

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 7.1 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
Low
Availability
None

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N

Frequently Asked Questions

What is CVE-2026-94462?
Spree: Broken Access Control in `PATCH /api/v3/store/carts/:id/associate` (IDOR) This vulnerability has been assigned a severity rating of HIGH (CVSS score: 7.1/10).
How do I check if my project is affected by CVE-2026-94462?
CVE-2026-94462 affects spree_api. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-94462 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
7.1

High exploitability or significant impact. Prioritize remediation within days.

Also Known As

GHSA-4825-p4xm-pcf2

Related CVEs

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies