VYPR
High severityNVD Advisory· Published Feb 6, 2026· Updated Feb 9, 2026

Spree allows unauthenticated users can access all guest addresses

CVE-2026-25758

Description

Spree is an open source e-commerce solution built with Ruby on Rails. A critical IDOR vulnerability exists in Spree Commerce's guest checkout flow that allows any guest user to bind arbitrary guest addresses to their order by manipulating address ID parameters. This enables unauthorized access to other guests' personally identifiable information (PII) including names, addresses and phone numbers. The vulnerability bypasses existing ownership validation checks and affects all guest checkout transactions. This vulnerability is fixed in 4.10.3, 5.0.8, 5.1.10, 5.2.7, and 5.3.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
spree_apiRubyGems
< 4.10.34.10.3
spree_apiRubyGems
>= 5.0.0, < 5.0.85.0.8
spree_apiRubyGems
>= 5.1.0, < 5.1.105.1.10
spree_apiRubyGems
>= 5.2.0, < 5.2.75.2.7
spree_apiRubyGems
>= 5.3.0, < 5.3.25.3.2

Affected products

1

Patches

5
15619618e43b

Merge commit from fork

https://github.com/spree/spreeSzymon ChorobskiFeb 5, 2026via ghsa
3 files changed · +150 6
  • api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 0 added
    @@ -0,0 +1,78 @@
    +require 'spec_helper'
    +
    +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do
    +  let(:user) { create(:user_with_addresses) }
    +  let(:params) { { order: order_attributes } }
    +  let(:store) { @default_store }
    +
    +  include_context 'API v2 tokens'
    +
    +  describe 'checkout#update' do
    +    subject :patch_update do
    +      patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer
    +    end
    +
    +    let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) }
    +    let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } }
    +
    +    context 'with address attributes' do
    +      context 'with new address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_attributes: {
    +              firstname: 'John',
    +              lastname: 'Doe',
    +              address1: '51 Guild Street',
    +              address2: '2nd floor',
    +              city: 'London',
    +              phone: '079 4721 9458',
    +              zipcode: 'SE25 3FZ',
    +              state_name: 'England',
    +              country_id: create(:country).id
    +            }
    +          }
    +        end
    +
    +        it 'updates address from new attributes' do
    +          expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John')
    +        end
    +      end
    +
    +      context 'with existing address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_id: existing_address.id
    +          }
    +        end
    +
    +        context 'with logged in user' do
    +          let(:existing_address) { create(:address, user: user) }
    +
    +          it 'updates address from existing address' do
    +            expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id)
    +          end
    +        end
    +
    +        context 'with guest user' do
    +          let(:user) { nil }
    +
    +          context 'with address that belongs to existing user' do
    +            let(:existing_address) { create(:address, user: create(:user)) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +
    +          context 'with address that belongs to guest user' do
    +            let(:existing_address) { create(:address, user: nil) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • core/app/models/spree/order/address_book.rb+12 4 modified
    @@ -19,13 +19,17 @@ def clone_billing_address
           end
     
           def bill_address_id=(id)
    +        return if bill_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['bill_address_id'] = address.id
    -          bill_address.reload
             else
               self['bill_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_bill_address
           end
     
           def bill_address_attributes=(attributes)
    @@ -34,13 +38,17 @@ def bill_address_attributes=(attributes)
           end
     
           def ship_address_id=(id)
    +        return if ship_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['ship_address_id'] = address.id
    -          ship_address.reload
             else
               self['ship_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_ship_address
           end
     
           def ship_address_attributes=(attributes)
    
  • core/spec/models/spree/order_spec.rb+60 2 modified
    @@ -1448,7 +1448,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
     
       describe "bill_address_id=" do
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         subject { order.bill_address_id = address.id }
    @@ -1467,6 +1469,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order bill address to nil' do
    +        expect { subject }.to change { order.bill_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { bill_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.bill_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order bill address to nil' do
    +          expect { subject }.to change { order.bill_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#bill_address_attributes=' do
    @@ -1513,7 +1542,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
     
       describe "ship_address_id=" do
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         subject { order.ship_address_id = address.id }
    @@ -1532,6 +1563,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order ship address to nil' do
    +        expect { subject }.to change { order.ship_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { ship_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.ship_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order ship address to nil' do
    +          expect { subject }.to change { order.ship_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#ship_address_attributes=' do
    
6650f96356fa

Merge commit from fork

https://github.com/spree/spreeSzymon ChorobskiFeb 5, 2026via ghsa
3 files changed · +150 6
  • api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 0 added
    @@ -0,0 +1,78 @@
    +require 'spec_helper'
    +
    +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do
    +  let(:user) { create(:user_with_addresses) }
    +  let(:params) { { order: order_attributes } }
    +  let(:store) { @default_store }
    +
    +  include_context 'API v2 tokens'
    +
    +  describe 'checkout#update' do
    +    subject :patch_update do
    +      patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer
    +    end
    +
    +    let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) }
    +    let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } }
    +
    +    context 'with address attributes' do
    +      context 'with new address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_attributes: {
    +              firstname: 'John',
    +              lastname: 'Doe',
    +              address1: '51 Guild Street',
    +              address2: '2nd floor',
    +              city: 'London',
    +              phone: '079 4721 9458',
    +              zipcode: 'SE25 3FZ',
    +              state_name: 'England',
    +              country_id: create(:country).id
    +            }
    +          }
    +        end
    +
    +        it 'updates address from new attributes' do
    +          expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John')
    +        end
    +      end
    +
    +      context 'with existing address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_id: existing_address.id
    +          }
    +        end
    +
    +        context 'with logged in user' do
    +          let(:existing_address) { create(:address, user: user) }
    +
    +          it 'updates address from existing address' do
    +            expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id)
    +          end
    +        end
    +
    +        context 'with guest user' do
    +          let(:user) { nil }
    +
    +          context 'with address that belongs to existing user' do
    +            let(:existing_address) { create(:address, user: create(:user)) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +
    +          context 'with address that belongs to guest user' do
    +            let(:existing_address) { create(:address, user: nil) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • core/app/models/spree/order/address_book.rb+12 4 modified
    @@ -14,13 +14,17 @@ def clone_billing_address
           end
     
           def bill_address_id=(id)
    +        return if bill_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['bill_address_id'] = address.id
    -          bill_address.reload
             else
               self['bill_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_bill_address
           end
     
           def bill_address_attributes=(attributes)
    @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes)
           end
     
           def ship_address_id=(id)
    +        return if ship_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['ship_address_id'] = address.id
    -          ship_address.reload
             else
               self['ship_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_ship_address
           end
     
           def ship_address_attributes=(attributes)
    
  • core/spec/models/spree/order_spec.rb+60 2 modified
    @@ -1686,7 +1686,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.bill_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1703,6 +1705,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order bill address to nil' do
    +        expect { subject }.to change { order.bill_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { bill_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.bill_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order bill address to nil' do
    +          expect { subject }.to change { order.bill_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#bill_address_attributes=' do
    @@ -1762,7 +1791,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.ship_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1779,6 +1810,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order ship address to nil' do
    +        expect { subject }.to change { order.ship_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { ship_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.ship_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order ship address to nil' do
    +          expect { subject }.to change { order.ship_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#ship_address_attributes=' do
    
ff7cfcfcfe0c

Merge commit from fork

https://github.com/spree/spreeSzymon ChorobskiFeb 5, 2026via ghsa
3 files changed · +150 6
  • api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 0 added
    @@ -0,0 +1,78 @@
    +require 'spec_helper'
    +
    +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do
    +  let(:user) { create(:user_with_addresses) }
    +  let(:params) { { order: order_attributes } }
    +  let(:store) { @default_store }
    +
    +  include_context 'API v2 tokens'
    +
    +  describe 'checkout#update' do
    +    subject :patch_update do
    +      patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer
    +    end
    +
    +    let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) }
    +    let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } }
    +
    +    context 'with address attributes' do
    +      context 'with new address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_attributes: {
    +              firstname: 'John',
    +              lastname: 'Doe',
    +              address1: '51 Guild Street',
    +              address2: '2nd floor',
    +              city: 'London',
    +              phone: '079 4721 9458',
    +              zipcode: 'SE25 3FZ',
    +              state_name: 'England',
    +              country_id: create(:country).id
    +            }
    +          }
    +        end
    +
    +        it 'updates address from new attributes' do
    +          expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John')
    +        end
    +      end
    +
    +      context 'with existing address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_id: existing_address.id
    +          }
    +        end
    +
    +        context 'with logged in user' do
    +          let(:existing_address) { create(:address, user: user) }
    +
    +          it 'updates address from existing address' do
    +            expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id)
    +          end
    +        end
    +
    +        context 'with guest user' do
    +          let(:user) { nil }
    +
    +          context 'with address that belongs to existing user' do
    +            let(:existing_address) { create(:address, user: create(:user)) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +
    +          context 'with address that belongs to guest user' do
    +            let(:existing_address) { create(:address, user: nil) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • core/app/models/spree/order/address_book.rb+12 4 modified
    @@ -14,13 +14,17 @@ def clone_billing_address
           end
     
           def bill_address_id=(id)
    +        return if bill_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['bill_address_id'] = address.id
    -          bill_address.reload
             else
               self['bill_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_bill_address
           end
     
           def bill_address_attributes=(attributes)
    @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes)
           end
     
           def ship_address_id=(id)
    +        return if ship_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['ship_address_id'] = address.id
    -          ship_address.reload
             else
               self['ship_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_ship_address
           end
     
           def ship_address_attributes=(attributes)
    
  • core/spec/models/spree/order_spec.rb+60 2 modified
    @@ -1576,7 +1576,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.bill_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1593,6 +1595,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order bill address to nil' do
    +        expect { subject }.to change { order.bill_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { bill_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.bill_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order bill address to nil' do
    +          expect { subject }.to change { order.bill_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#bill_address_attributes=' do
    @@ -1652,7 +1681,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.ship_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1669,6 +1700,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order ship address to nil' do
    +        expect { subject }.to change { order.ship_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { ship_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.ship_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order ship address to nil' do
    +          expect { subject }.to change { order.ship_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#ship_address_attributes=' do
    
29282d1565ba

Merge commit from fork

https://github.com/spree/spreeSzymon ChorobskiFeb 5, 2026via ghsa
3 files changed · +150 6
  • api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 0 added
    @@ -0,0 +1,78 @@
    +require 'spec_helper'
    +
    +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do
    +  let(:user) { create(:user_with_addresses) }
    +  let(:params) { { order: order_attributes } }
    +  let(:store) { @default_store }
    +
    +  include_context 'API v2 tokens'
    +
    +  describe 'checkout#update' do
    +    subject :patch_update do
    +      patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer
    +    end
    +
    +    let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) }
    +    let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } }
    +
    +    context 'with address attributes' do
    +      context 'with new address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_attributes: {
    +              firstname: 'John',
    +              lastname: 'Doe',
    +              address1: '51 Guild Street',
    +              address2: '2nd floor',
    +              city: 'London',
    +              phone: '079 4721 9458',
    +              zipcode: 'SE25 3FZ',
    +              state_name: 'England',
    +              country_id: create(:country).id
    +            }
    +          }
    +        end
    +
    +        it 'updates address from new attributes' do
    +          expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John')
    +        end
    +      end
    +
    +      context 'with existing address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_id: existing_address.id
    +          }
    +        end
    +
    +        context 'with logged in user' do
    +          let(:existing_address) { create(:address, user: user) }
    +
    +          it 'updates address from existing address' do
    +            expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id)
    +          end
    +        end
    +
    +        context 'with guest user' do
    +          let(:user) { nil }
    +
    +          context 'with address that belongs to existing user' do
    +            let(:existing_address) { create(:address, user: create(:user)) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +
    +          context 'with address that belongs to guest user' do
    +            let(:existing_address) { create(:address, user: nil) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • core/app/models/spree/order/address_book.rb+12 4 modified
    @@ -14,13 +14,17 @@ def clone_billing_address
           end
     
           def bill_address_id=(id)
    +        return if bill_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['bill_address_id'] = address.id
    -          bill_address.reload
             else
               self['bill_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_bill_address
           end
     
           def bill_address_attributes=(attributes)
    @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes)
           end
     
           def ship_address_id=(id)
    +        return if ship_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['ship_address_id'] = address.id
    -          ship_address.reload
             else
               self['ship_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_ship_address
           end
     
           def ship_address_attributes=(attributes)
    
  • core/spec/models/spree/order_spec.rb+60 2 modified
    @@ -1866,7 +1866,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.bill_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1883,6 +1885,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order bill address to nil' do
    +        expect { subject }.to change { order.bill_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { bill_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.bill_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order bill address to nil' do
    +          expect { subject }.to change { order.bill_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#bill_address_attributes=' do
    @@ -1942,7 +1971,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.ship_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1959,6 +1990,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order ship address to nil' do
    +        expect { subject }.to change { order.ship_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { ship_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.ship_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order ship address to nil' do
    +          expect { subject }.to change { order.ship_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#ship_address_attributes=' do
    
902d301ac83f

Merge commit from fork

https://github.com/spree/spreeSzymon ChorobskiFeb 5, 2026via ghsa
3 files changed · +150 6
  • api/spec/controllers/spree/api/v2/storefront/checkout_controller_spec.rb+78 0 added
    @@ -0,0 +1,78 @@
    +require 'spec_helper'
    +
    +describe 'Spree::Api::V2::Storefront::CheckoutController', type: :request do
    +  let(:user) { create(:user_with_addresses) }
    +  let(:params) { { order: order_attributes } }
    +  let(:store) { @default_store }
    +
    +  include_context 'API v2 tokens'
    +
    +  describe 'checkout#update' do
    +    subject :patch_update do
    +      patch '/api/v2/storefront/checkout', params: params, headers: headers_bearer
    +    end
    +
    +    let(:order) { create(:order_with_line_items, user: user, state: 'address', store: store, bill_address_id: nil, ship_address_id: nil) }
    +    let(:headers_bearer) { { 'X-Spree-Order-Token' => order.token } }
    +
    +    context 'with address attributes' do
    +      context 'with new address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_attributes: {
    +              firstname: 'John',
    +              lastname: 'Doe',
    +              address1: '51 Guild Street',
    +              address2: '2nd floor',
    +              city: 'London',
    +              phone: '079 4721 9458',
    +              zipcode: 'SE25 3FZ',
    +              state_name: 'England',
    +              country_id: create(:country).id
    +            }
    +          }
    +        end
    +
    +        it 'updates address from new attributes' do
    +          expect { patch_update }.to change { order.reload.bill_address&.firstname }.to('John')
    +        end
    +      end
    +
    +      context 'with existing address attributes' do
    +        let(:order_attributes) do
    +          {
    +            bill_address_id: existing_address.id
    +          }
    +        end
    +
    +        context 'with logged in user' do
    +          let(:existing_address) { create(:address, user: user) }
    +
    +          it 'updates address from existing address' do
    +            expect { patch_update }.to change { order.reload.bill_address_id }.from(nil).to(existing_address.id)
    +          end
    +        end
    +
    +        context 'with guest user' do
    +          let(:user) { nil }
    +
    +          context 'with address that belongs to existing user' do
    +            let(:existing_address) { create(:address, user: create(:user)) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +
    +          context 'with address that belongs to guest user' do
    +            let(:existing_address) { create(:address, user: nil) }
    +
    +            it 'does not update guest user address' do
    +              expect { patch_update }.not_to change { order.reload.bill_address_id }
    +            end
    +          end
    +        end
    +      end
    +    end
    +  end
    +end
    
  • core/app/models/spree/order/address_book.rb+12 4 modified
    @@ -14,13 +14,17 @@ def clone_billing_address
           end
     
           def bill_address_id=(id)
    +        return if bill_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['bill_address_id'] = address.id
    -          bill_address.reload
             else
               self['bill_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_bill_address
           end
     
           def bill_address_attributes=(attributes)
    @@ -29,13 +33,17 @@ def bill_address_attributes=(attributes)
           end
     
           def ship_address_id=(id)
    +        return if ship_address_id == id
    +
             address = Spree::Address.find_by(id: id)
    -        if address && address.user_id == user_id
    +        # rubocop:disable Style/ConditionalAssignment
    +        if address && user_id.present? && address.user_id == user_id
               self['ship_address_id'] = address.id
    -          ship_address.reload
             else
               self['ship_address_id'] = nil
             end
    +        # rubocop:enable Style/ConditionalAssignment
    +        reset_ship_address
           end
     
           def ship_address_attributes=(attributes)
    
  • core/spec/models/spree/order_spec.rb+60 2 modified
    @@ -1668,7 +1668,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.bill_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1685,6 +1687,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order bill address to nil' do
    +        expect { subject }.to change { order.bill_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { bill_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.bill_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order bill address to nil' do
    +          expect { subject }.to change { order.bill_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#bill_address_attributes=' do
    @@ -1744,7 +1773,9 @@ def create_adjustment(label, order_or_line_item, amount, source)
         subject { order.ship_address_id = address.id }
     
         let(:user) { create(:user) }
    -    let(:order) { create(:order, user: user) }
    +    let(:order) { create(:order, user: user, bill_address: bill_address, ship_address: ship_address) }
    +    let(:bill_address) { create(:address, user: user) }
    +    let(:ship_address) { create(:address, user: user) }
         let(:address) { create(:address, user: user) }
     
         context 'when assigned address exist' do
    @@ -1761,6 +1792,33 @@ def create_adjustment(label, order_or_line_item, amount, source)
             end
           end
         end
    +
    +    context 'when assigned address does not belong to user' do
    +      let(:address) { create(:address, user: create(:user)) }
    +
    +      it 'sets order ship address to nil' do
    +        expect { subject }.to change { order.ship_address_id }.to(nil)
    +      end
    +    end
    +
    +    context 'with guest user' do
    +      let(:user) { nil }
    +      let(:address) { create(:address, user: nil) }
    +
    +      context 'when assigning the same existing address' do
    +        let(:address) { ship_address }
    +
    +        it 'does nothing' do
    +          expect { subject }.not_to(change { order.ship_address_id })
    +        end
    +      end
    +
    +      context 'when assigning a different existing address' do
    +        it 'sets order ship address to nil' do
    +          expect { subject }.to change { order.ship_address_id }.to(nil)
    +        end
    +      end
    +    end
       end
     
       describe '#ship_address_attributes=' do
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

13

News mentions

0

No linked articles in our index yet.