Missing Authorization in saleor/saleor
Description
Missing authorization in Saleor prior to 3.1.2 allowed staff users with only manage_users permission to access order information of any user via GraphQL.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Missing authorization in Saleor prior to 3.1.2 allowed staff users with only `manage_users` permission to access order information of any user via GraphQL.
Vulnerability
A missing authorization check in Saleor prior to version 3.1.2 allows staff users with only the manage_users permission to retrieve order information of any user via the GraphQL user.orders field. The issue was fixed in commit 521dfd6394f3926a77c60d8633c058e16d0f916d, which added a requirement for the manage_orders permission to access this field [2].
Exploitation
An attacker with a staff account that has been granted the manage_users permission (but not manage_orders) can exploit this by sending a crafted GraphQL query to fetch the orders field on any user. No additional authentication or user interaction is required beyond having a valid staff account with the insufficient permissions [2].
Impact
The attacker can view order details (including order IDs and potentially sensitive order data) of any user in the system, leading to unauthorized information disclosure. The compromise is limited to read access of order information; no write or execute capabilities are gained [2].
Mitigation
Upgrade to Saleor version 3.1.2 or later, which includes the fix requiring the manage_orders permission for accessing user.orders. No workarounds have been published for earlier versions [2][3].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
saleorPyPI | < 3.1.2 | 3.1.2 |
Affected products
2Patches
1521dfd6394f3Require manage orders for fetching `user.orders` (#9128)
4 files changed · +104 −11
CHANGELOG.md+3 −0 modified@@ -6,6 +6,9 @@ All notable, unreleased changes to this project will be documented in this file. # Unreleased ### Breaking changes +- Require manage orders for fetching `user.orders` - #9128 by @IKarbowiak + - only staff with `manage orders` and can fetch customer orders + - the customer can fetch his own orders, except drafts ### Other changes - Filter Customer/Order/Sale/Product/ProductVariant by datetime of last modification - #9137 by @rafalp
saleor/graphql/account/tests/benchmark/test_account.py+7 −2 modified@@ -105,7 +105,9 @@ def test_query_staff_user( user_id = graphene.Node.to_global_id("User", staff_user.pk) variables = {"id": user_id} response = staff_api_client.post_graphql( - query, variables, permissions=[permission_manage_staff] + query, + variables, + permissions=[permission_manage_staff, permission_manage_orders], ) content = get_graphql_content(response) data = content["data"]["user"] @@ -352,10 +354,13 @@ def test_delete_staff_members( def test_customers_query( staff_api_client, permission_manage_users, + permission_manage_orders, users_for_customers_benchmarks, count_queries, ): - staff_api_client.user.user_permissions.set([permission_manage_users]) + staff_api_client.user.user_permissions.set( + [permission_manage_users, permission_manage_orders] + ) content = get_graphql_content(staff_api_client.post_graphql(CUSTOMERS_QUERY)) assert content["data"]["customers"] is not None
saleor/graphql/account/tests/test_account.py+87 −6 modified@@ -181,6 +181,7 @@ def test_query_customer_user( gift_card_expiry_date, address, permission_manage_users, + permission_manage_orders, media_root, settings, ): @@ -199,7 +200,9 @@ def test_query_customer_user( query = FULL_USER_QUERY ID = graphene.Node.to_global_id("User", customer_user.id) variables = {"id": ID} - staff_api_client.user.user_permissions.add(permission_manage_users) + staff_api_client.user.user_permissions.add( + permission_manage_users, permission_manage_orders + ) response = staff_api_client.post_graphql(query, variables) content = get_graphql_content(response) data = content["data"]["user"] @@ -268,6 +271,7 @@ def test_query_customer_user_with_orders( customer_user, order_list, permission_manage_users, + permission_manage_orders, ): # given query = FULL_USER_QUERY @@ -291,24 +295,61 @@ def test_query_customer_user_with_orders( # when response = staff_api_client.post_graphql( - query, variables, permissions=[permission_manage_users] + query, + variables, + permissions=[permission_manage_users, permission_manage_orders], ) # then content = get_graphql_content(response) user = content["data"]["user"] assert {order["node"]["id"] for order in user["orders"]["edges"]} == { - graphene.Node.to_global_id("Order", order.pk) - for order in [order_unfulfilled, order_unconfirmed] + graphene.Node.to_global_id("Order", order.pk) for order in order_list } +def test_query_customer_user_with_orders_no_manage_orders_perm( + staff_api_client, + customer_user, + order_list, + permission_manage_users, +): + # given + query = FULL_USER_QUERY + order_unfulfilled = order_list[0] + order_unfulfilled.user = customer_user + + order_unconfirmed = order_list[1] + order_unconfirmed.status = OrderStatus.UNCONFIRMED + order_unconfirmed.user = customer_user + + order_draft = order_list[2] + order_draft.status = OrderStatus.DRAFT + order_draft.user = customer_user + + Order.objects.bulk_update( + [order_unconfirmed, order_draft, order_unfulfilled], ["user", "status"] + ) + + id = graphene.Node.to_global_id("User", customer_user.id) + variables = {"id": id} + + # when + response = staff_api_client.post_graphql( + query, variables, permissions=[permission_manage_users] + ) + + # then + assert_no_permission(response) + + def test_query_customer_user_app( app_api_client, customer_user, address, permission_manage_users, permission_manage_staff, + permission_manage_orders, media_root, app, ): @@ -327,7 +368,9 @@ def test_query_customer_user_app( query = FULL_USER_QUERY ID = graphene.Node.to_global_id("User", customer_user.id) variables = {"id": ID} - app.permissions.add(permission_manage_staff, permission_manage_users) + app.permissions.add( + permission_manage_staff, permission_manage_users, permission_manage_orders + ) response = app_api_client.post_graphql(query, variables) content = get_graphql_content(response) @@ -365,6 +408,41 @@ def test_query_customer_user_app_no_permission( assert_no_permission(response) +def test_query_customer_user_with_orders_by_app_no_manage_orders_perm( + app_api_client, + customer_user, + order_list, + permission_manage_users, +): + # given + query = FULL_USER_QUERY + order_unfulfilled = order_list[0] + order_unfulfilled.user = customer_user + + order_unconfirmed = order_list[1] + order_unconfirmed.status = OrderStatus.UNCONFIRMED + order_unconfirmed.user = customer_user + + order_draft = order_list[2] + order_draft.status = OrderStatus.DRAFT + order_draft.user = customer_user + + Order.objects.bulk_update( + [order_unconfirmed, order_draft, order_unfulfilled], ["user", "status"] + ) + + id = graphene.Node.to_global_id("User", customer_user.id) + variables = {"id": id} + + # when + response = app_api_client.post_graphql( + query, variables, permissions=[permission_manage_users] + ) + + # then + assert_no_permission(response) + + def test_query_staff_user( staff_api_client, staff_user, @@ -993,6 +1071,7 @@ def test_user_with_cancelled_fulfillments( staff_api_client, customer_user, permission_manage_users, + permission_manage_orders, fulfilled_order_with_cancelled_fulfillment, ): query = """ @@ -1013,7 +1092,9 @@ def test_user_with_cancelled_fulfillments( """ user_id = graphene.Node.to_global_id("User", customer_user.id) variables = {"id": user_id} - staff_api_client.user.user_permissions.add(permission_manage_users) + staff_api_client.user.user_permissions.add( + permission_manage_users, permission_manage_orders + ) response = staff_api_client.post_graphql(query, variables) content = get_graphql_content(response) order_id = graphene.Node.to_global_id(
saleor/graphql/account/types.py+7 −3 modified@@ -364,9 +364,13 @@ def resolve_orders(root: models.User, info, **kwargs): def _resolve_orders(orders): requester = get_user_or_app_from_context(info.context) if not requester.has_perm(OrderPermissions.MANAGE_ORDERS): - orders = list( - filter(lambda order: order.status != OrderStatus.DRAFT, orders) - ) + # allow fetch requestor orders (except drafts) + if root == info.context.user: + orders = list( + filter(lambda order: order.status != OrderStatus.DRAFT, orders) + ) + else: + raise PermissionDenied() return create_connection_slice( orders, info, kwargs, OrderCountableConnection
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.