Skip to content

Remove query-string dependency to fix decode-uri-component DoS (CVE-2026-45822) - #11387

Open
bipul724 wants to merge 2 commits into
marmelab:masterfrom
bipul724:fix/11380-remove-query-string
Open

bipul724 wants to merge 2 commits into
marmelab:masterfrom
bipul724:fix/11380-remove-query-string

Conversation

@bipul724

@bipul724 bipul724 commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Fixes #11380

ra-core, ra-ui-materialui, ra-data-json-server and ra-data-simple-rest depend on query-string@^7.1.3, which pulls in decode-uri-component@0.2.2. That version is affected by CVE-2026-45822 / GHSA-vcc3-ghjq-m6fr (DoS via malformed percent-encoded input), and it's reachable through URL query parsing (useListParams, useRecordFromLocation).

Upgrading isn't an option: query-string@8+ (which uses the patched decode-uri-component@^0.5.0) is ESM-only, and ra-core ships a CJS build.

Solution

Vendor query-string@9.5.1 into ra-core, together with its dependencies decode-uri-component@0.5.0 and split-on-first@3.0.0, under packages/ra-core/src/util/vendor/.

  • Each vendored file keeps its upstream MIT license and lists its modifications at the top: converted to TypeScript, trimmed to parse()/stringify() and the helpers they use, replaceAll() replaced for the ES2020 target, and Prettier formatting.
  • A thin wrapper ra-core/src/util/queryString.ts exposes parseQueryString() and stringifyQueryString(), and all internal usages now go through it.
  • fetchUtils.queryParameters now points to stringifyQueryString, so its signature is unchanged and it still accepts the query-string StringifyOptions. There's no API change, unlike the URLSearchParams approach suggested in the issue, which would have dropped that parameter.
  • ra-ui-materialui, ra-data-json-server, ra-data-simple-rest and the demo use fetchUtils.queryParameters instead of importing query-string directly. That keeps them compatible with older ra-core 5.x peers, where queryParameters is still the original query-string stringify.
  • query-string is removed from every package.json. query-string, decode-uri-component, filter-obj, split-on-first and strict-uri-encode are gone from yarn.lock.

I chose vendoring over a custom parser because it keeps the exact upstream behavior (key sorting, null/undefined handling, array formats, + decoding, malformed input) instead of reimplementing it. The upstream test suites are ported alongside the code.

Compatibility with query-string@7

  • stringify() and parse() of valid input: 100% identical on 40,000 random cases.
  • Malformed input (e.g. %E0%A4%A, %, %zz): never throws. The only differences come from known bugs in the old decode-uri-component@0.2.2.

How To Test

  • yarn test-unit packages/ra-core/src/util runs the ported upstream tests for parse/stringify/decodeUriComponent plus new tests for the wrapper.
  • yarn test-unit packages/ra-core/src/controller/list packages/ra-core/src/form packages/ra-data-simple-rest packages/ra-ui-materialui/src/list/filter packages/ra-ui-materialui/src/button covers the call sites.
  • In the demo, open a list, apply filters/sort/pagination, reload the page and check that the URL and the restored state are the same as before. Also try "Clone" on a record and the saved queries in the filter menu.
  • yarn why decode-uri-component should return nothing.

Additional Checks

  • The PR targets master for a bugfix or a documentation fix, or next for a feature
  • The PR includes unit tests (if not possible, describe why)
  • The PR includes one or several stories (if not possible, describe why): no UI change. The existing useSavedQueries story is updated to use the new helper.
  • The documentation is up to date: the docs still show import { stringify } from 'query-string' in user-land examples (DataProviderWriting.md, ListTutorial.md, Tutorial.md). Happy to switch them to fetchUtils.queryParameters if you'd like.

…ity (CVE-2026-45822)

query-string@7 depends on decode-uri-component@0.2.2, which is vulnerable
to a DoS (GHSA-vcc3-ghjq-m6fr). query-string@8+ is ESM-only and cannot be
used by the ra-core CJS build, so replace it with an internal helper that
reproduces the query-string@7 default output. Packages outside ra-core use
the existing fetchUtils.queryParameters to stay compatible with older
ra-core 5.x peers.

Fixes marmelab#11380

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 27, 2026 07:48

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot review overview

🟡 Changes recommended

The public fetchUtils.queryParameters API loses its supported options argument, introducing a patch-level compatibility break.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread packages/ra-core/src/dataProvider/fetch.ts
@fzaninotto

Copy link
Copy Markdown
Member

I'm a bit uncomfortable replacing this dep with a custom solution. We'll probably need more tests to check corner cases.

Also, if we're heading in this direction, why not use the latest version of query-string as a base instead of v7?

@bipul724

Copy link
Copy Markdown
Contributor Author

Thanks for the review!

I started from v7 because query-string v8+ and decode-uri-component 0.4+ are ESM-only, so ra-core's CJS build can't require them directly. But I agree that hand-written parsing code is riskier than proven code.

Proposal: vendor the latest query-string (9.5.1) and decode-uri-component (0.5.0) sources into ra-core (both MIT, with license headers kept), limited to the parse/stringify code we use, and port their upstream test suites so the corner cases are covered by the same tests upstream relies on. This would also keep the (object, options) signature of fetchUtils.queryParameters, which addresses the Copilot note about the public API.

I'd re-run my comparison against the current output to confirm the URLs react-admin generates don't change, and list any differences in the PR.

Does that sound like the right direction?

@fzaninotto

Copy link
Copy Markdown
Member

Yes, let's try that

… custom parser

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bipul724

Copy link
Copy Markdown
Contributor Author

Updated as discussed: query-string@9.5.1 and decode-uri-component@0.5.0 are now vendored with their upstream test suites ported (275 tests). Compared with query-string@7, stringify and parse of valid input are 100% identical on 40,000 random cases; the only differences are on malformed input, where the old decoder had known bugs. Details are in the description.

This branch has not been deployed

No deployments
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.

Fix decode-uri-component DoS (CVE-2026-45822) by dropping query-string

3 participants