Skip to content

[#20967] fix(store-api): read all fields of the compressed _criteria parameter - #162

Closed
BrocksiNet wants to merge 13 commits into
mirror/pr-20967-basefrom
mirror/pr-20967
Closed

BrocksiNet wants to merge 13 commits into
mirror/pr-20967-basefrom
mirror/pr-20967

Conversation

@BrocksiNet

@BrocksiNet BrocksiNet commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Mirror of an upstream pull request, opened for automated review. Do not merge.

Upstream shopware#20967
Author @mkucmus
Base trunk at merge-base b5e77c66bffd
Head 6d9653889efa

Original description

1. Why is this change necessary?

Store API clients send cacheable reads as GET and put the request body into _criteria. The cache ADR asks SDKs to pick GET or POST automatically, so both have to return the same result. Today they do not.

What it fixes

  • Criteria fields inside _criteria are ignored on some routes: limit, includes and excludes on /category, /cms, /landing-page, /media, /find-variant and the cart, and limit on /search. The routes answer 200 with the wrong data. #19863 fixed the same problem for routes with a Criteria argument.
  • /search-suggest answers 400 when search is inside _criteria, although /search reads it from there.
  • The sw-include-search-info header is ignored when _criteria is present.
  • includes that is not an array gives 500 instead of 400.

What it extends

  • Other route parameters are now read from _criteria too, for example slots, depth, buildTree, options, switchedGroup and onlyAvailable. Before, _criteria meant the criteria only, and these had to be sent as plain query parameters. Inside _criteria they were ignored without an error, so /find-variant returned a different variant than asked for. #13643 already did this for the listing parameters (p, order, filters).

What it enables

2. What does this change do, exactly?

  • New CompressedCriteriaRequestListener: on every Store API GET request, it copies the fields of _criteria into the query parameters before the controller runs. So _criteria works as a compressed query string. #13643 did the same for product listings.
  • A field in _criteria wins over a plain query parameter with the same name. Plain parameters keep working.
  • Values inside _criteria are read as strings, as in a query string, and a field set to null counts as not sent, as in a POST body. The criteria keep their JSON types.
  • Why every route: a client can already send each field as a plain query parameter, so nothing new becomes reachable and the same checks apply. The listener runs after the access key check, and cache keys use the original URL. Earlier versions of this PR limited the rule to some routes, which needed reflection and skipped some routes without notice.
  • RequestCriteriaBuilder applies plain query parameters next to _criteria and respects sw-include-search-info. This also affects the Admin API GET list and detail routes.
  • includes and excludes are validated in StoreApiResponseListener and ScriptStoreApiRoute.
  • The soft-purge refresh of the HTTP cache now passes a copy of the request to the kernel, like the HTTP cache itself does. Before, the listener changed the query of the request that the refreshed response was stored with, so it was stored under a key that no lookup uses. This already happened on trunk for the listing routes.
  • CompressedCriteriaListingProcessor from #13643 now skips Store API requests. There the listener already copied the fields as query-string values, and the processor overwrote them again with their JSON types. Outside the Store API the processor works as before. This is intended: the PR changes nothing outside the Store API, so there is no BC break. Deprecating the processor can follow in a separate PR.
  • RELEASE_INFO, the OpenAPI schema and the cache ADR describe the rule.

Limits

  • Only requests with _criteria change. Plain GET and POST requests behave as before.
  • Store API caching is only active with CACHE_REWORK or v6.8. Until then, the gain is correct GET results.
  • Fields that a route reads from the POST body only, such as the listing filter flags (manufacturer-filter), still do not work over GET. This is a follow-up.
  • _criteria now means more than criteria. This is a public contract that core has to keep.

3. Describe each step to reproduce the issue or behaviour.

  1. Send POST /store-api/category/{id} with the body {"limit":2,"includes":{"category":["id","cmsPage"]}}.
  2. Encode the same body: node -e 'console.log(require("zlib").gzipSync(JSON.stringify({limit:2,includes:{category:["id","cmsPage"]}})).toString("base64url"))'
  3. Send GET /store-api/category/{id}?_criteria=<encoded>.

Before: GET returns the full category with the default number of products. After: GET returns the same as POST.

4. Please link to the relevant issues (if any).

5. Checklist

  • I have written tests and verified that they fail without my change
  • I have updated developer-facing release notes if this change is relevant for external developers:
    • Add a short entry to RELEASE_INFO-6.<major>.md under “Upcoming” for informational changes, including the consequences of the change and how it affects external developers.
    • Add an UPGRADE section in UPGRADE-6.<next-major>.md for breaking changes (what/why/impact/how to adapt).
    • See the Documenting a Release Process for details.
  • I have written or adjusted the documentation and agent skills according to my changes
  • This change has comments for package types, values, functions, and non-obvious lines of code
  • I have read the contribution requirements and fulfilled them

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Store API GET requests now support compressed _criteria across routes, allowing request fields to be supplied in the URL. Criteria values take precedence over matching query parameters, while other query parameters remain in effect.
    • API documentation now describes compressed criteria support on applicable routes.
  • Bug Fixes

    • Invalid includes or excludes values and malformed compressed criteria now return a 400 response instead of being ignored or causing a server error.

@BrocksiNet BrocksiNet added mirror Mirror of an upstream pull request review-pending Mirror is still waiting for its review labels Sep 29, 2026
@BrocksiNet

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 69996163-f01d-4429-bd68-e8355bce14d4

📥 Commits

Reviewing files that changed from the base of the PR and between b5e77c6 and 6d96538.

📒 Files selected for processing (36)
  • RELEASE_INFO-6.7.md
  • adr/2025-09-15-store-api-cache-strategy.md
  • src/Core/Content/Product/SalesChannel/Listing/Processor/CompressedCriteriaListingProcessor.php
  • src/Core/Framework/Adapter/Cache/Message/RefreshHttpCacheMessageHandler.php
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/components/parameters/criteria.json
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/paths/category.json
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/paths/cms.json
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/paths/landing-page.json
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/paths/media.json
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/paths/product.json
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/paths/search-suggest.json
  • src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/paths/search.json
  • src/Core/Framework/Api/EventListener/CompressedCriteriaRequestListener.php
  • src/Core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilder.php
  • src/Core/Framework/DependencyInjection/api.php
  • src/Core/Framework/Script/Api/ScriptStoreApiRoute.php
  • src/Core/System/SalesChannel/Api/ResponseFields.php
  • src/Core/System/SalesChannel/Api/StoreApiResponseListener.php
  • src/Core/System/SalesChannel/SalesChannelException.php
  • tests/integration/Core/Checkout/Cart/SalesChannel/CartLoadRouteTest.php
  • tests/integration/Core/Checkout/Order/SalesChannel/OrderRouteTest.php
  • tests/integration/Core/Content/Category/SalesChannel/CategoryListRouteTest.php
  • tests/integration/Core/Content/Category/SalesChannel/CategoryRouteTest.php
  • tests/integration/Core/Content/LandingPage/SalesChannel/LandingPageRouteTest.php
  • tests/integration/Core/Content/Product/SalesChannel/Listing/ProductListingRouteTest.php
  • tests/integration/Core/Content/Product/SalesChannel/ProductSearchRouteTest.php
  • tests/integration/Core/Framework/Script/Api/ScriptStoreApiRouteTest.php
  • tests/integration/Core/System/Snippet/SalesChannel/SnippetRouteTest.php
  • tests/unit/Core/Content/Product/SalesChannel/Listing/Processor/CompressedCriteriaListingProcessorTest.php
  • tests/unit/Core/Framework/Adapter/Cache/Message/RefreshHttpCacheMessageHandlerTest.php
  • tests/unit/Core/Framework/Api/EventListener/CompressedCriteriaRequestListenerTest.php
  • tests/unit/Core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilderTest.php
  • tests/unit/Core/Framework/Script/Api/ScriptStoreApiRouteTest.php
  • tests/unit/Core/System/SalesChannel/Api/ResponseFieldsTest.php
  • tests/unit/Core/System/SalesChannel/Api/StoreApiResponseListenerTest.php
  • tests/unit/Core/System/SalesChannel/SalesChannelExceptionTest.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Store API GET requests can now read route parameters from compressed _criteria, with criteria values taking precedence over matching query parameters. The change updates response-field validation, declares _criteria on additional Store API routes, and adds unit and integration tests.

Changes

Store API compressed criteria

Layer / File(s) Summary
Expand compressed criteria in requests
src/Core/Framework/Api/EventListener/CompressedCriteriaRequestListener.php, src/Core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilder.php, src/Core/Framework/DependencyInjection/api.php, src/Core/Content/Product/SalesChannel/Listing/Processor/CompressedCriteriaListingProcessor.php, src/Core/Framework/Adapter/Cache/Message/RefreshHttpCacheMessageHandler.php, tests/unit/Core/Framework/Api/EventListener/CompressedCriteriaRequestListenerTest.php, tests/unit/Core/Framework/DataAbstractionLayer/Search/RequestCriteriaBuilderTest.php, tests/unit/Core/Content/Product/SalesChannel/Listing/Processor/CompressedCriteriaListingProcessorTest.php, tests/unit/Core/Framework/Adapter/Cache/Message/RefreshHttpCacheMessageHandlerTest.php
A controller-event listener expands compressed criteria for Store API GET requests. The criteria builder retains unrelated query parameters and lets criteria values replace matching values. The listing processor leaves these requests to the listener, and the cache handler passes a clone of the request to the kernel.
Declare and verify route support
src/Core/Framework/Api/ApiDefinition/Generator/Schema/StoreApi/..., RELEASE_INFO-6.7.md, adr/2025-09-15-store-api-cache-strategy.md, tests/integration/Core/Checkout/..., tests/integration/Core/Content/..., tests/integration/Core/Framework/Script/Api/ScriptStoreApiRouteTest.php, tests/integration/Core/System/Snippet/SalesChannel/SnippetRouteTest.php
OpenAPI declarations and documentation describe compressed criteria on Store API GET routes. Integration tests cover compressed criteria on cart, order, category, landing-page, listing, search, script, and snippet routes.
Read and validate response fields
src/Core/System/SalesChannel/Api/ResponseFields.php, src/Core/System/SalesChannel/Api/StoreApiResponseListener.php, src/Core/System/SalesChannel/SalesChannelException.php, src/Core/Framework/Script/Api/ScriptStoreApiRoute.php, tests/unit/Core/System/SalesChannel/Api/*, tests/unit/Core/System/SalesChannel/SalesChannelExceptionTest.php, tests/unit/Core/Framework/Script/Api/ScriptStoreApiRouteTest.php, tests/integration/Core/Content/Category/SalesChannel/CategoryRouteTest.php, tests/integration/Core/Framework/Script/Api/ScriptStoreApiRouteTest.php
ResponseFields::fromRequest() reads includes and excludes from request parameters. It rejects non-array values other than null, and invalid-type exceptions now use HTTP 400. The response listener and script route use this request-based parsing.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant StoreApiRequest
  participant CompressedCriteriaRequestListener
  participant RequestCriteriaBuilder
  StoreApiRequest->>CompressedCriteriaRequestListener: Send GET request with _criteria
  CompressedCriteriaRequestListener->>CompressedCriteriaRequestListener: Decode criteria and update query parameters
  CompressedCriteriaRequestListener->>RequestCriteriaBuilder: Pass expanded request query
Loading

Merge Risk: ⚪ Minimal · up to 6d965

The changed request behavior has coverage across the affected routes, and no actionable merge blocker is established.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 6d965

A shared input change affects many Store API routes. The inspected identity and cache controls limit apparent exposure, but consistent validation and authorization of the newly readable parameters across routes remains unconfirmed.

Retained concerns

  • Medium · security · inferred: Compressed fields can now replace or remove query parameters across Store API GET routes before their consumers run. Equivalent validation and authorization for every newly reachable route parameter has not been established; no specific bypass was verified.
Security review details

Security Blast Radius

  • inferred — An external Store API GET client can supply compressed fields to multiple route consumers, including script hooks and order reads. This broadens input reachability, not the demonstrated privilege of an individual request.

Security Findings and Attack Paths

  • inferred — No authorization bypass is established for the inspected order path: it filters by sales channel and requires a customer or a deep-link guest flow with rate limiting and credential validation. Enforcement across other expanded route consumers remains unverified.

Trust Boundaries and Controls

  • observed — For the inspected identity path, access-key and context-token handling uses headers and authenticated request attributes rather than the query fields the listener mutates. The listener therefore does not directly replace those identity inputs.

Resilience and Maintainability Implications

  • observed — The cache refresh handler writes using the unmutated request, deletes its lock only after a successful write, and restores trusted-proxy configuration even on failure. The idempotency of a partially completed store write remains outside the inspected evidence.

Hardening Proposals

  • proposed — Verify compressed-versus-plain GET authorization and validation parity for route-specific query consumers, particularly script hooks and guest-order parameters, before treating the expanded contract as uniformly enforced.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 100 functions across 26 files. (10 skippe… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reading all fields from compressed _criteria for Store API requests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 22.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 100 functions across 26 files. (10 skipped: 10 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@BrocksiNet BrocksiNet added review-done Mirror has been reviewed and removed review-pending Mirror is still waiting for its review labels Sep 29, 2026
@BrocksiNet

Copy link
Copy Markdown
Owner Author

Upstream PR no longer requests a review from you - closing this mirror.

@BrocksiNet BrocksiNet closed this Oct 5, 2026
@BrocksiNet
BrocksiNet deleted the mirror/pr-20967 branch October 5, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mirror Mirror of an upstream pull request review-done Mirror has been reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants