fix(router-core): prevent lossy string coercion in defaultParseSearch (#7650)#7679
fix(router-core): prevent lossy string coercion in defaultParseSearch (#7650)#7679sanjibani wants to merge 1 commit into
Conversation
defaultParseSearch ran JSON.parse on every string value, which silently
coerced opaque strings that happen to be valid JSON into JSON values:
?codAut=662E41 -> { codAut: 6.62e+43 }
?id=723421968459640832 -> { id: 723421968459640800 } (precision lost)
?sig=abcdef0123456789 -> { sig: 4.22696e+15 } (mangled)
?ulid=01H8XGJWBWBAQ4ZEXAMPLE -> { ulid: 134... (mangled)
The default parser now uses a roundtrip check: a parsed value is only
accepted when re-serializing it with stringify reproduces the original
string. This rejects any lossy conversion while preserving the
existing behavior for the documented JSON-shaped values.
parseSearchWith also grows an optional second 'stringify' argument so
users with a non-JSON serializer can pass a matching re-serializer.
Default is JSON.stringify, so the existing defaultParseSearch export
keeps the same call shape.
Fixes TanStack#7650
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Opened as draft to work around a cross-fork GraphQL |
Summary
defaultParseSearchcurrently runsJSON.parseon every value, which silently coerces opaque strings that happen to be valid JSON into JSON values:?codAut=662E41{ codAut: 6.62e+43 }{ codAut: '662E41' }?id=723421968459640832{ id: 723421968459640800 }(precision lost){ id: '723421968459640832' }?sig=abcdef0123456789{ sig: 4.2269...e+15 }(mangled){ sig: 'abcdef0123456789' }?ulid=01H8XGJWBWBAQ4ZEXAMPLE{ ulid: 1.34... (mangled) }{ ulid: '01H8XGJWBWBAQ4ZEXAMPLE' }These values commonly come from upstream systems: identity provider codes, social auth IDs, ULID/UUIDv7, large Snowflake/ClickHouse keys, hex signatures, etc. They parse to valid JSON because the JSON grammar accepts hex-digit-only strings as numbers, but the parsed value cannot be re-serialized back to the original string.
The default parser now uses a roundtrip check: a parsed value is only accepted when re-serializing it with
stringifyreproduces the original string. The existing behavior for the documented JSON-shaped values (?foo=1→ number,?foo={"a":1}→ object, etc.) is preserved because those values roundtrip cleanly.Changes
packages/router-core/src/searchParams.ts:parseSearchWithnow accepts an optionalstringifyparameter (defaults toJSON.stringify) and uses it to verify that the parsed value re-serializes back to the original string before replacing it.defaultParseSearch = parseSearchWith(JSON.parse)keeps the same call shape, so no consumer breaks.packages/router-core/tests/searchParams.test.ts:opaque string roundtriptest.eachblock covering the four failure modes from defaultParseSearch corrupts string search params that look like JSON numbers (e.g. "662E41", large integers) — lossy and unrecoverable on inbound URLs #7650.Notes
parseSearchWithis part of the public surface (re-exported fromrouter-core,react-router,solid-router,vue-router). The newstringifyparameter is optional with aJSON.stringifydefault, so existing callers are unaffected.defaultParseSearchonly, butparseSearchWithis the documented customization point for this behavior, so the roundtrip lives at that boundary.>= 13chars of all digits/hex. Rejected because it would be a workaround forJSON.parserather than a correctness fix and would miss the662E41→6.62e+43case (6 chars).Fixes #7650
Test plan
pnpm exec vitest run packages/router-core/tests/searchParams.test.ts— 44/44 pass (40 existing + 4 new)pnpm exec tsc --noEmit -p packages/router-core— no errorsopaque string roundtripblock fails onmainand passes on this branch.