Commit a60b6fd4387 for woocommerce

commit a60b6fd43873aa0739de010d23aeae832f4a4663
Author: Ján Mikláš <neosinner@gmail.com>
Date:   Mon Nov 2 17:04:42 2026 +0100

    Update `react-router-dom` from v6 to v7 to resolve security alerts (#70431)

    * Update react-router-dom from 6.3.0 to 7.18.5

    Moves WooCommerce admin and @woocommerce/navigation to the v7 line.
    7.18.0 is the first release that fixes GHSA-wrjc-x8rr-h8h6 (open
    redirect via backslash in Link/useNavigate), which has no v6 fix.
    v7 needs React 18 and Node 20, both already required here.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Cast the admin history object for the v7 HistoryRouter

    react-router types HistoryRouter's history prop with its own History
    interface, which also lists createURL and encodeLocation. The router
    only calls those when they exist, and the history v5 object from
    @woocommerce/navigation covers the rest, so cast it at both call sites.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Keep synchronous route updates in the admin HistoryRouter

    v7's HistoryRouter wraps location updates in React.startTransition by
    default. Admin code and extensions read the current route from
    getHistory() and getQuery(), which change immediately, so a deferred
    router render could show the old page for the new query. Pass
    useTransitions={ false } to keep v6's synchronous updates.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Resolve the react-router-dom copy under @automattic/components to v7

    @automattic/components 2.2 declares react-router-dom ^6.23.1 but never
    imports it, so its 6.30.3 copy only kept the advisories open in the
    lockfile. Override it to the same 7.18.5 the admin uses. Remove the
    override once @automattic/components is on 5.x, which drops the
    dependency.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Remove the unused react-router-dom v5 type package from admin

    react-router-dom has shipped its own types since v6, and TypeScript
    already resolves those instead of @types/react-router-dom 5.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Add changelog entries for the react-router-dom v7 update

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Drop react-router-dom from @woocommerce/navigation

    The package only imported the Location type from react-router-dom, and
    the value it describes is history's location. Import Location from
    history, which is already a dependency and is what react-router-dom 6.3
    re-exported. The published types stay as they were in 9.0.0, and
    extensions that install the package no longer pull in react-router v7.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Pass the location pathname to admin pages instead of self-matching it

    The layout matched location.pathname against itself with useMatch() to
    get the page's url. v7 decodes the pathname before matching but not the
    pattern, so a path with a %XX sequence no longer matched, and the page
    got an undefined path and match.url. Use location.pathname directly,
    which is what the self-match returned. Add tests for pages registered
    through the pages filter with a param, a splat and an encoded path.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    * Mark the Payments settings navigate() calls as ignored promises

    react-router v7 types useNavigate()'s function as returning
    void | Promise<void>, so no-floating-promises flags the two call sites.
    Mark them with void, as the surrounding code does for other
    fire-and-forget calls.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01NM9YXwNubtgw5tAZP9Wg86

    ---------

    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

diff --git a/packages/js/navigation/changelog/update-deps-security-react-router b/packages/js/navigation/changelog/update-deps-security-react-router
new file mode 100644
index 00000000000..f7491efced4
--- /dev/null
+++ b/packages/js/navigation/changelog/update-deps-security-react-router
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Remove the react-router-dom dependency; useConfirmUnsavedChanges now types its location with history.
diff --git a/packages/js/navigation/package.json b/packages/js/navigation/package.json
index 037d52c126d..145732c1f73 100644
--- a/packages/js/navigation/package.json
+++ b/packages/js/navigation/package.json
@@ -74,8 +74,7 @@
 		"@wordpress/notices": "catalog:wp-min",
 		"@wordpress/url": "catalog:wp-min",
 		"history": "^5.3.0",
-		"qs": "^6.11.2",
-		"react-router-dom": "~6.3.0"
+		"qs": "^6.11.2"
 	},
 	"peerDependencies": {
 		"lodash": "^4.17.0",
diff --git a/packages/js/navigation/src/hooks/use-confirm-unsaved-changes.ts b/packages/js/navigation/src/hooks/use-confirm-unsaved-changes.ts
index ff1e8bbd45a..d707946adda 100644
--- a/packages/js/navigation/src/hooks/use-confirm-unsaved-changes.ts
+++ b/packages/js/navigation/src/hooks/use-confirm-unsaved-changes.ts
@@ -2,7 +2,7 @@
  * External dependencies
  */
 import { __ } from '@wordpress/i18n';
-import { Location } from 'react-router-dom';
+import { Location } from 'history';
 import { useEffect, useMemo } from '@wordpress/element';

 /**
diff --git a/plugins/woocommerce/changelog/update-deps-security-react-router b/plugins/woocommerce/changelog/update-deps-security-react-router
new file mode 100644
index 00000000000..25038e541bb
--- /dev/null
+++ b/plugins/woocommerce/changelog/update-deps-security-react-router
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Update the bundled react-router-dom library from version 6 to 7 to fix reported security issues.
diff --git a/plugins/woocommerce/client/admin/client/layout/index.tsx b/plugins/woocommerce/client/admin/client/layout/index.tsx
index d5395d411e7..c54bd1ddba9 100644
--- a/plugins/woocommerce/client/admin/client/layout/index.tsx
+++ b/plugins/woocommerce/client/admin/client/layout/index.tsx
@@ -5,10 +5,10 @@ import { compose } from '@wordpress/compose';
 import { useSelect } from '@wordpress/data';
 import {
 	unstable_HistoryRouter as HistoryRouter,
+	type HistoryRouterProps,
 	Route,
 	Routes,
 	useLocation,
-	useMatch,
 	useParams,
 } from 'react-router-dom';
 import { identity, isFunction } from 'lodash';
@@ -68,9 +68,8 @@ const BaseLayout = ( { page }: { page: Page } ) => {
 		[]
 	);
 	const location = useLocation();
-	const matchFromRouter = useMatch( location.pathname );
 	const params = useParams();
-	const match = { params, url: matchFromRouter?.pathname };
+	const match = { params, url: location.pathname };
 	const isMarketplacePage = location.pathname.includes( '/extensions' );

 	usePageClasses( page );
@@ -196,7 +195,14 @@ export const _Layout = () => {
 	const basename = path.substring( 0, path.lastIndexOf( '/' ) );

 	return (
-		<HistoryRouter history={ getHistory() }>
+		<HistoryRouter
+			// The `history` v5 object provides what HistoryRouter uses at runtime; only the types differ.
+			history={
+				getHistory() as unknown as HistoryRouterProps[ 'history' ]
+			}
+			// Render route changes synchronously, as v6 did, so routes stay in step with getQuery().
+			useTransitions={ false }
+		>
 			{ /* @ts-expect-error basename is not typed */ }
 			<Routes basename={ basename }>
 				{ pages
diff --git a/plugins/woocommerce/client/admin/client/layout/test/index.test.js b/plugins/woocommerce/client/admin/client/layout/test/index.test.js
index a392a5e8f03..f2d408b9313 100644
--- a/plugins/woocommerce/client/admin/client/layout/test/index.test.js
+++ b/plugins/woocommerce/client/admin/client/layout/test/index.test.js
@@ -160,4 +160,51 @@ describe( 'Layout', () => {
 			removeFilter( PAGES_FILTER, namespace );
 		} );
 	} );
+
+	describe( 'Pages added through the pages filter', () => {
+		const namespace = `woocommerce/woocommerce/test_ext_${ PAGES_FILTER }`;
+		const ExtensionPage = ( { params, path } ) => (
+			<p>{ `params=${ JSON.stringify( params ) } path=${ path }` }</p>
+		);
+
+		beforeEach( () => {
+			addFilter( PAGES_FILTER, namespace, ( pages ) => [
+				...pages,
+				{
+					breadcrumbs: [ 'Extension item' ],
+					container: ExtensionPage,
+					path: '/test-ext/:id',
+				},
+				{
+					breadcrumbs: [ 'Extension section' ],
+					container: ExtensionPage,
+					path: '/test-ext-section/*',
+				},
+			] );
+		} );
+
+		afterEach( () => {
+			removeFilter( PAGES_FILTER, namespace );
+		} );
+
+		it.each( [
+			[ '/test-ext/42', 'params={"id":"42"} path=/test-ext/42' ],
+			[
+				'/test-ext-section/a/b',
+				'params={"*":"a/b"} path=/test-ext-section/a/b',
+			],
+			[
+				'/test-ext/hello%20world',
+				'params={"id":"hello world"} path=/test-ext/hello%20world',
+			],
+		] )(
+			'should render %s with its params and path',
+			( pathname, expected ) => {
+				mockPath( pathname );
+				render( <Layout /> );
+
+				expect( screen.getByText( expected ) ).toBeInTheDocument();
+			}
+		);
+	} );
 } );
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/buttons/settings-button.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/buttons/settings-button.tsx
index 5abdb44b6c2..886974cfa0b 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/buttons/settings-button.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/buttons/settings-button.tsx
@@ -91,7 +91,7 @@ export const SettingsButton = ( {
 					void invalidateResolutionForStoreSelector(
 						'getPaymentGateway'
 					);
-					navigate( removeOriginFromURL( settingsHref ) );
+					void navigate( removeOriginFromURL( settingsHref ) );
 				} else {
 					window.location.href = settingsHref;
 				}
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx
index a6cbb0419ae..ef13cf859e4 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/payment-gateway-list/payment-gateway-list.tsx
@@ -151,7 +151,7 @@ export const PaymentGatewayList = ( {
 									id={ offlinePmsGroup.id }
 									className="transitions-disabled woocommerce-list__item clickable-list-item enter-done"
 									onClick={ () => {
-										navigate(
+										void navigate(
 											removeOriginFromURL(
 												offlinePmsGroup.management
 													._links.settings.href
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/index.tsx b/plugins/woocommerce/client/admin/client/settings-payments/index.tsx
index cce739d4b8f..415fdf94d68 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/index.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/index.tsx
@@ -6,6 +6,7 @@ import { Button, Placeholder, SelectControl } from '@wordpress/components';
 import React, { lazy, Suspense, useEffect } from '@wordpress/element';
 import {
 	unstable_HistoryRouter as HistoryRouter,
+	type HistoryRouterProps,
 	Route,
 	Routes,
 	useLocation,
@@ -294,7 +295,14 @@ export const SettingsPaymentsMainWrapper = () => {
 	return (
 		<>
 			<Header title={ __( 'Settings', 'woocommerce' ) } />
-			<HistoryRouter history={ getHistory() }>
+			<HistoryRouter
+				// The `history` v5 object provides what HistoryRouter uses at runtime; only the types differ.
+				history={
+					getHistory() as unknown as HistoryRouterProps[ 'history' ]
+				}
+				// Render route changes synchronously, as v6 did, so routes stay in step with getQuery().
+				useTransitions={ false }
+			>
 				<Routes>
 					<Route
 						path="/offline"
diff --git a/plugins/woocommerce/client/admin/package.json b/plugins/woocommerce/client/admin/package.json
index 80cdc4f79b5..9f2cebd09ff 100644
--- a/plugins/woocommerce/client/admin/package.json
+++ b/plugins/woocommerce/client/admin/package.json
@@ -122,7 +122,7 @@
 		"qs": "^6.11.2",
 		"react": "18.3.x",
 		"react-dom": "18.3.x",
-		"react-router-dom": "~6.3.0",
+		"react-router-dom": "~7.18.5",
 		"react-transition-group": "^4.4.5",
 		"react-visibility-sensor": "^5.1.1",
 		"redux": "^4.2.1",
@@ -151,7 +151,6 @@
 		"@types/node": "^24.1.0",
 		"@types/qs": "^6.9.10",
 		"@types/react": "18.3.x",
-		"@types/react-router-dom": "^5.3.3",
 		"@types/react-transition-group": "^4.4.10",
 		"@types/testing-library__jest-dom": "^5.14.9",
 		"@types/tinymce": "^4.6.9",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bf135fa4ead..737136f85e8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -236,6 +236,7 @@ overrides:
   '@wordpress/data-controls>@wordpress/api-fetch': 7.33.1
   '@wordpress/private-apis': ^1.33.1
   minimatch@<3.1.4: 3.1.5
+  '@automattic/components>react-router-dom': ~7.18.5
   react-resize-aware: 3.1.1
   sass: 1.69.5

@@ -1933,9 +1934,6 @@ importers:
       qs:
         specifier: ^6.11.2
         version: 6.16.2
-      react-router-dom:
-        specifier: ~6.3.0
-        version: 6.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
     devDependencies:
       '@babel/core':
         specifier: catalog:tooling
@@ -3074,8 +3072,8 @@ importers:
         specifier: 18.3.x
         version: 18.3.1(react@18.3.1)
       react-router-dom:
-        specifier: ~6.3.0
-        version: 6.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        specifier: ~7.18.5
+        version: 7.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       react-transition-group:
         specifier: ^4.4.5
         version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -3155,9 +3153,6 @@ importers:
       '@types/react':
         specifier: 18.3.x
         version: 18.3.28
-      '@types/react-router-dom':
-        specifier: ^5.3.3
-        version: 5.3.3
       '@types/react-transition-group':
         specifier: ^4.4.10
         version: 4.4.12(@types/react@18.3.28)
@@ -7635,10 +7630,6 @@ packages:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0

-  '@remix-run/router@1.23.2':
-    resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==}
-    engines: {node: '>=14.0.0'}
-
   '@rtsao/scc@1.1.0':
     resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}

@@ -8457,12 +8448,6 @@ packages:
     peerDependencies:
       '@types/react': 18.3.x

-  '@types/react-router-dom@5.3.3':
-    resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
-
-  '@types/react-router@5.1.20':
-    resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
-
   '@types/react-test-renderer@19.1.0':
     resolution: {integrity: sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==}

@@ -11882,6 +11867,10 @@ packages:
     resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
     engines: {node: '>= 0.6'}

+  cookie@1.1.1:
+    resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+    engines: {node: '>=18'}
+
   cookiejar@2.1.4:
     resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}

@@ -16960,29 +16949,22 @@ packages:
     peerDependencies:
       react: ^16.8.0 || 17.x

-  react-router-dom@6.3.0:
-    resolution: {integrity: sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==}
-    peerDependencies:
-      react: '>=16.8'
-      react-dom: '>=16.8'
-
-  react-router-dom@6.30.3:
-    resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==}
-    engines: {node: '>=14.0.0'}
-    peerDependencies:
-      react: '>=16.8'
-      react-dom: '>=16.8'
-
-  react-router@6.3.0:
-    resolution: {integrity: sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==}
+  react-router-dom@7.18.5:
+    resolution: {integrity: sha512-9HcfDMU6JJ/fp6gJZYGwNE6RknVGS4kBjH1HkNw959mCJi70TVwdipQWv2FEol9uGpi+SZTFtVpJPtEQfayeOw==}
+    engines: {node: '>=20.0.0'}
     peerDependencies:
-      react: '>=16.8'
+      react: '>=18'
+      react-dom: '>=18'

-  react-router@6.30.3:
-    resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==}
-    engines: {node: '>=14.0.0'}
+  react-router@7.18.5:
+    resolution: {integrity: sha512-dTgvcXDOrLPrlLHrcZAqWveiK1hMSqN+rGAJzegP8HtmNkfRxOoshGWBNX3ZJ0cZjOHkRdlUJtaIhZMLLyGyPw==}
+    engines: {node: '>=20.0.0'}
     peerDependencies:
-      react: '>=16.8'
+      react: '>=18'
+      react-dom: '>=18'
+    peerDependenciesMeta:
+      react-dom:
+        optional: true

   react-shallow-renderer@16.15.0:
     resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==}
@@ -17683,6 +17665,9 @@ packages:
   set-blocking@2.0.0:
     resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}

+  set-cookie-parser@2.7.3:
+    resolution: {integrity: sha512-irZHtTqurLna57KQG/s+fzOwv/J6XSH4cs8M/vsrY3uCaQ3S6+QJguAH0WdN47SXlk82voMZ4naWMQf8fdEoIg==}
+
   set-function-length@1.2.2:
     resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
     engines: {node: '>= 0.4'}
@@ -19560,7 +19545,7 @@ snapshots:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       react-modal: 3.16.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      react-router-dom: 6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react-router-dom: 7.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       react-slider: 2.0.6(react@18.3.1)
       utility-types: 3.10.0
       uuid: 9.0.1
@@ -19605,7 +19590,7 @@ snapshots:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       react-modal: 3.16.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      react-router-dom: 6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react-router-dom: 7.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       react-slider: 2.0.6(react@18.3.1)
       utility-types: 3.10.0
       uuid: 9.0.1
@@ -24032,8 +24017,6 @@ snapshots:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)

-  '@remix-run/router@1.23.2': {}
-
   '@rtsao/scc@1.1.0': {}

   '@sentry/core@9.47.1': {}
@@ -25114,17 +25097,6 @@ snapshots:
     dependencies:
       '@types/react': 18.3.28

-  '@types/react-router-dom@5.3.3':
-    dependencies:
-      '@types/history': 4.7.11
-      '@types/react': 18.3.28
-      '@types/react-router': 5.1.20
-
-  '@types/react-router@5.1.20':
-    dependencies:
-      '@types/history': 4.7.11
-      '@types/react': 18.3.28
-
   '@types/react-test-renderer@19.1.0':
     dependencies:
       '@types/react': 18.3.28
@@ -32886,6 +32858,8 @@ snapshots:

   cookie@0.7.2: {}

+  cookie@1.1.1: {}
+
   cookiejar@2.1.4: {}

   copy-anything@3.0.5:
@@ -38865,29 +38839,19 @@ snapshots:
     dependencies:
       react: 18.3.1

-  react-router-dom@6.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-router-dom@7.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
     dependencies:
-      history: 5.3.0
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
-      react-router: 6.3.0(react@18.3.1)
+      react-router: 7.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)

-  react-router-dom@6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-router@7.18.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
     dependencies:
-      '@remix-run/router': 1.23.2
+      cookie: 1.1.1
       react: 18.3.1
+      set-cookie-parser: 2.7.3
+    optionalDependencies:
       react-dom: 18.3.1(react@18.3.1)
-      react-router: 6.30.3(react@18.3.1)
-
-  react-router@6.3.0(react@18.3.1):
-    dependencies:
-      history: 5.3.0
-      react: 18.3.1
-
-  react-router@6.30.3(react@18.3.1):
-    dependencies:
-      '@remix-run/router': 1.23.2
-      react: 18.3.1

   react-shallow-renderer@16.15.0(react@18.3.1):
     dependencies:
@@ -39624,6 +39588,8 @@ snapshots:

   set-blocking@2.0.0: {}

+  set-cookie-parser@2.7.3: {}
+
   set-function-length@1.2.2:
     dependencies:
       define-data-property: 1.1.4
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 8aa7ad5e85e..616650b08fe 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -118,6 +118,8 @@ overrides:
     '@wordpress/data-controls>@wordpress/api-fetch': 'catalog:wp-min'
     '@wordpress/private-apis': 'catalog:wp-min'
     'minimatch@<3.1.4': 3.1.5
+    # @automattic/components 2.x lists react-router-dom but never imports it; drop once it is on 5.x.
+    '@automattic/components>react-router-dom': ~7.18.5
     react-resize-aware: 3.1.1
     sass: 1.69.5