Skip to content

Fix cross-store IDOR in store-panel customer product price/personalization#725

Merged
KrzysztofPajak merged 2 commits into
developfrom
fix-store-customer-product-idor
Jul 5, 2026
Merged

Fix cross-store IDOR in store-panel customer product price/personalization#725
KrzysztofPajak merged 2 commits into
developfrom
fix-store-customer-product-idor

Conversation

@KrzysztofPajak

Copy link
Copy Markdown
Member

Summary

Security fix found during a review of Grand.Web.Store. The store-panel CustomerController had four actions that mutated customer product data by raw id, with no store-ownership check:

  • UpdateProductPrice(model)
  • DeleteProductPrice(id)
  • UpdatePersonalizedProduct(model)
  • DeletePersonalizedProduct(id)

Every other action in this controller funnels through GetStoreCustomer() (which enforces customer.StoreId == CurrentStoreId and registered-only), but these four bypassed it. The underlying CustomerViewModelService loads the CustomerProductPrice / CustomerProduct by id and saves/deletes it without any store filter, so a store manager could pass an id belonging to another store''s customer and update their custom price or delete their personalized products (cross-store write/delete IDOR).

Fix

Resolve the entity first, take its CustomerId, and run it through the existing GetStoreCustomer() gate before delegating to the service — the same pattern already used by UpdateCart / DeleteCart in this controller. If the entity is missing or its customer is not a registered customer of the current store, the action no-ops.

  • No domain model changes.
  • No shared service (AdminShared) changes.
  • Change is confined to Grand.Web.Store/Controllers/CustomerController.cs (+20 lines).

Note: this controller is gated behind Customer:RegisterCustomersPerStore, so the issue only applies to per-store customer installations, but the missing authorization check is fixed regardless.

Test plan

  • Grand.Web.Store builds clean
  • Manual: as store A manager, confirm updating/deleting a product price or personalized product for a store A customer still works
  • Manual: confirm the same actions with an id belonging to a store B customer now no-op instead of mutating

🤖 Generated with Claude Code

…ation

The store-panel CustomerController exposed four actions that mutated
CustomerProductPrice / CustomerProduct entities by raw id without
verifying the underlying customer belongs to the current store:
UpdateProductPrice, DeleteProductPrice, UpdatePersonalizedProduct,
DeletePersonalizedProduct. The AdminShared view-model service loads
these entities by id and saves/deletes them with no store filter, so a
store manager could update or delete another store customer's custom
price or personalized products by supplying a foreign id.

Resolve the entity, take its CustomerId and run it through the existing
GetStoreCustomer() gate (store + registered check) before mutating -
matching the pattern already used by UpdateCart/DeleteCart. No domain
model or shared service changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 5, 2026 09:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a cross-store IDOR in Grand.Web.Store’s store-panel CustomerController by adding a store-ownership gate before mutating customer product price and personalization records, aligning these actions with the controller’s existing GetStoreCustomer() enforcement pattern.

Changes:

  • Injects ICustomerProductService to resolve CustomerProductPrice / CustomerProduct by raw id.
  • Adds store-ownership validation (GetStoreCustomer(entity.CustomerId)) before update/delete operations for product price and personalized products.
  • Ensures missing entities or cross-store access attempts no-op rather than mutating data.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Web/Grand.Web.Store/Controllers/CustomerController.cs
Comment thread src/Web/Grand.Web.Store/Controllers/CustomerController.cs
Comment thread src/Web/Grand.Web.Store/Controllers/CustomerController.cs Outdated
Comment thread src/Web/Grand.Web.Store/Controllers/CustomerController.cs
Comment thread src/Web/Grand.Web.Store/Controllers/CustomerController.cs
Comment thread src/Web/Grand.Web.Store/Controllers/CustomerController.cs Outdated
- Add [HttpPost] to UpdateProductPrice/DeleteProductPrice/
  UpdatePersonalizedProduct/DeletePersonalizedProduct so the
  [AutoValidateAntiforgeryToken] on BaseStoreController is enforced
  (the Kendo grids already call them via POST) - closes the CSRF gap
  Copilot flagged.
- Delete the already-resolved entity via ICustomerProductService instead
  of re-loading by id in the view-model service, removing the redundant
  query and the TOCTOU exception on a concurrent delete.
- Update CustomerControllerTests to supply the new ICustomerProductService
  constructor dependency (fixes the CI build break).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 5, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
6.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@KrzysztofPajak KrzysztofPajak merged commit 7f555e4 into develop Jul 5, 2026
6 of 8 checks passed
@KrzysztofPajak KrzysztofPajak deleted the fix-store-customer-product-idor branch July 5, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants