Skip to content

fix(framework,medusa): reject sorting admin orders by computed fields with a 400#15809

Closed
hhuang2088 wants to merge 4 commits into
medusajs:developfrom
hhuang2088:fix/add_and_enforce_order_sort_allow_list
Closed

fix(framework,medusa): reject sorting admin orders by computed fields with a 400#15809
hhuang2088 wants to merge 4 commits into
medusajs:developfrom
hhuang2088:fix/add_and_enforce_order_sort_allow_list

Conversation

@hhuang2088

@hhuang2088 hhuang2088 commented Jun 22, 2026

Copy link
Copy Markdown

What

Sorting the admin orders list (GET /admin/orders) by a computed field —
any total (total, subtotal, tax_total, item_*, shipping_*,
original_*, credit_line_*, …), payment_status, or fulfillment_status
currently throws a 500, because these are not persisted columns on the order
table (MikroORM: "Trying to order by not existing property Order.total").
This PR rejects sorting by these fields with a clean 400.

Why

The orders list query config never guarded the order query param, so it
flowed unchecked to the data layer. This mirrors the filtering fix in #15262
(nonFilterableFields), but for sorting — the counterpart was missing.

Per review feedback, the guard is a deny list rather than an allow list:
an allow list would 400 real columns (email, status, currency_code, …)
that sorted fine before — a breaking change. Every field in the deny list
previously threw a 500, so forbidding them changes no working request.

How

  • Added an optional forbiddenOrderBy to QueryConfig (@medusajs/types).
    Unlike allowed, it governs only sorting, not field selection — so a
    computed field like total stays selectable while being rejected as a sort
    key.
  • Enforced it in prepareListQuery (@medusajs/framework), alongside the
    untouched pre-existing allowed check — no other route changes behavior.
  • Defined forbiddenAdminOrderSortFields on the orders list config: the
    computed totals (mirroring shouldIncludeTotals in the order module
    service), payment_status, fulfillment_status, and summary.

Testing

  • Unit (packages/core/framework/src/http/utils/__tests__/get-query-config.spec.ts):
    added cases for prepareListQuery — rejecting a sort field in
    forbiddenOrderBy (ascending and descending), allowing one outside it,
    rejecting a forbidden field even when it is in allowed (sort decoupled
    from field selection), and still enforcing allowed when forbiddenOrderBy
    is set.
  • Integration (integration-tests/http/__tests__/order/admin/order.spec.ts):
    added cases under GET /admin/orders — 400 when sorting by total,
    -payment_status, fulfillment_status, subtotal, -shipping_total,
    credit_line_tax_total, or original_item_subtotal; 200 when sorting by
    real columns -created_at, display_id, email, and status (including
    ones outside any allow list, proving no regression for existing sorts).
    The pre-existing fields=id,total&order=-created_at test still passes,
    proving field selection is unaffected.

Closes #15353

🤖 Generated with Claude Code

Sorting GET /admin/orders by a computed field (total, payment_status,
fulfillment_status) reached MikroORM and threw a 500, since those are
not persisted columns on the order table.

Add an optional `allowedOrderBy` to QueryConfig, enforced in
prepareListQuery's order guard (falling back to `allowed` so existing
routes are unaffected), and supply the orders list config with the
sortable column allow list (display_id, created_at, updated_at) that
mirrors the admin dashboard's sort options. Invalid sort fields now
return a clean 400 instead of a 500.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hhuang2088
hhuang2088 requested a review from a team as a code owner June 22, 2026 22:13
@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 88e3865

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 79 packages
Name Type
@medusajs/framework Patch
@medusajs/medusa Patch
@medusajs/types Patch
@medusajs/test-utils Patch
@medusajs/analytics Patch
@medusajs/api-key Patch
@medusajs/auth Patch
@medusajs/cache-inmemory Patch
@medusajs/cache-redis Patch
@medusajs/caching Patch
@medusajs/cart Patch
@medusajs/currency Patch
@medusajs/customer Patch
@medusajs/event-bus-local Patch
@medusajs/event-bus-redis Patch
@medusajs/file Patch
@medusajs/fulfillment Patch
@medusajs/index Patch
@medusajs/inventory Patch
@medusajs/link-modules Patch
@medusajs/locking Patch
@medusajs/notification Patch
@medusajs/order Patch
@medusajs/payment Patch
@medusajs/pricing Patch
@medusajs/product Patch
@medusajs/promotion Patch
@medusajs/rbac Patch
@medusajs/region Patch
@medusajs/sales-channel Patch
@medusajs/settings Patch
@medusajs/stock-location Patch
@medusajs/store Patch
@medusajs/tax Patch
@medusajs/translation Patch
@medusajs/user Patch
@medusajs/workflow-engine-inmemory Patch
@medusajs/workflow-engine-redis Patch
@medusajs/analytics-local Patch
@medusajs/analytics-posthog Patch
@medusajs/auth-emailpass Patch
@medusajs/auth-github Patch
@medusajs/auth-google Patch
@medusajs/caching-redis Patch
@medusajs/file-local Patch
@medusajs/file-s3 Patch
@medusajs/fulfillment-manual Patch
@medusajs/locking-postgres Patch
@medusajs/locking-redis Patch
@medusajs/notification-local Patch
@medusajs/notification-sendgrid Patch
@medusajs/payment-stripe Patch
@medusajs/draft-order Patch
@medusajs/loyalty-plugin Patch
@medusajs/core-flows Patch
integration-tests-http Patch
@medusajs/medusa-oas-cli Patch
@medusajs/js-sdk Patch
@medusajs/modules-sdk Patch
@medusajs/orchestration Patch
@medusajs/utils Patch
@medusajs/workflows-sdk Patch
@medusajs/admin-bundler Patch
@medusajs/dashboard Patch
@medusajs/oas-github-ci Patch
@medusajs/http-types-generator Patch
@medusajs/cli Patch
@medusajs/deps Patch
@medusajs/eslint-plugin Patch
@medusajs/telemetry Patch
@medusajs/admin-sdk Patch
@medusajs/admin-shared Patch
@medusajs/admin-vite-plugin Patch
@medusajs/icons Patch
@medusajs/toolbox Patch
@medusajs/ui-preset Patch
create-medusa-app Patch
medusa-dev-cli Patch
@medusajs/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@medusa-os-bot

medusa-os-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

Thanks for the contribution! Initial automated review looks good.

Well-scoped fix for issue #15353: sorting the admin orders list by computed fields (total, payment/fulfillment status) returned a 500; this adds an optional allowedOrderBy to QueryConfig and an admin orders sort allowlist so invalid sort keys return a clean 400. PR template is complete, a changeset is included, and integration tests cover both 400 and 200 cases. The fallback (allowedOrderBy || allowed) preserves existing routes. Non-blocking consideration: the allowlist (display_id, created_at, updated_at) also narrows sorting by other valid persisted columns like status/version, which previou

Triggered by: new PR opened

Cover the new allowedOrderBy behavior in prepareListQuery: rejecting a
disallowed sort field, allowing a permitted one, preferring allowedOrderBy
over allowed (sort decoupled from field selection), and sorting by a field
present only in allowedOrderBy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread .changeset/order-sort-allow-list.md Outdated
"@medusajs/types": patch
---

fix(framework,medusa): reject sorting the admin orders list by non-sortable fields with a 400

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.

Should be fix(framework,medusa,types): reject sorting the admin orders list by non-sortable fields with a 400

* they aren't columns on the order table, so ordering by them throws at the
* database layer.
*/
export const allowedAdminOrderSortFields = [

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.

This will make it a breaking change for anyone already sorting on the core order list endpoint by something other than these three fields. To avoid this, I think it might be better to expose the inverse forbiddenOrderBy and then set these three, making the corresponding changes inside prepareListQuery

…ny list

Per review feedback on medusajs#15809: an allow list of three fields would 400
any other real column that sorted fine before. Replace allowedOrderBy
with forbiddenOrderBy and forbid only the computed fields from medusajs#15353
(total, payment_status, fulfillment_status), which previously threw 500
at the database layer — so nothing that worked before changes behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hhuang2088 hhuang2088 closed this Jul 13, 2026
@hhuang2088
hhuang2088 deleted the fix/add_and_enforce_order_sort_allow_list branch July 13, 2026 06:06
@hhuang2088 hhuang2088 reopened this Jul 13, 2026
@medusa-os-bot

medusa-os-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

Thanks for the contribution! A few items need to be addressed before this can move forward:

Well-scoped, well-tested change that turns the 500 into a clean 400 for the three dashboard sort fields, and the allowed-fallback keeps other routes unchanged. One gap: the deny-list only covers total, payment_status, fulfillment_status, but the admin orders list exposes many other computed totals (subtotal, tax_total, discount_total, item_total, shipping_total and their original_/credit_line_ variants), so GET /admin/orders?order=subtotal still 500s — the same bug class. Consider an allow-list of persisted columns instead. Also, the PR description still refers to allowedOrderBy, but the code

  • packages/medusa/src/api/admin/orders/query-config.ts: deny-list misses other computed totals (subtotal, tax_total, discount_total, item_total, shipping_total, original_*) so order=subtotal etc. still

Triggered by: new commit pushed

The initial deny list only covered the three fields from medusajs#15353, but
every computed total (subtotal, tax_total, discount_total, item_*,
shipping_*, original_*, credit_line_*) hits the same bug class —
none are columns on the order table, so sorting by them threw 500.
Mirror the order module's shouldIncludeTotals list plus the computed
statuses and summary. All of these previously errored, so forbidding
them is not a behavior change for any working request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hhuang2088 hhuang2088 changed the title fix(framework,medusa): enforce sort allow list on admin orders list fix(framework,medusa): reject sorting admin orders by computed fields with a 400 Jul 14, 2026
@medusa-os-bot

medusa-os-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Thanks for the contribution! Initial automated review looks good.

Re-review: the prior gap (deny-list missing computed totals beyond total/payment_status/fulfillment_status) is now resolved — forbiddenAdminOrderSortFields covers all computed totals, and code/description consistently use forbiddenOrderBy (deny-list). Logic in prepareListQuery correctly strips the '-' prefix before checking and rejects with a clean 400. Denied fields are all computed (not persisted columns), so no previously-working sort regresses. Template complete, changeset present, unit + integration tests cover 400 and 200 cases. No new blockers found.

Triggered by: new commit pushed

@hhuang2088

Copy link
Copy Markdown
Author

Superseded by #16070 — same fix reworked per the review feedback here (sort allow list → forbiddenOrderBy deny list covering all computed fields), rebased onto the latest develop and squashed into a single commit, on a branch named for the final approach. Closing in favor of that PR.

@hhuang2088 hhuang2088 closed this Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Error trying to sort orders by: Total / Fulfillment status / Payment status

2 participants