Skip to content

fix: directions_tool exclude parameter could inject query parameters - #234

Merged
mattpodwysocki merged 1 commit into
mainfrom
fix/directions-exclude-parameter-injection
Jul 29, 2026
Merged

fix: directions_tool exclude parameter could inject query parameters#234
mattpodwysocki merged 1 commit into
mainfrom
fix/directions-exclude-parameter-injection

Conversation

@mattpodwysocki

Copy link
Copy Markdown
Contributor

What changed

Fixes a query-parameter-injection defect in directions_tool's exclude parameter, present in both the server-side request builder and its hand-ported client-side twin in the map preview iframe.

The underlying issue

A point(<lng> <lat>) exclude entry was validated by splitting on spaces and destructuring only the first two tokens:

const [lngStr, latStr] = coordStr.split(' ');

Any extra content after those two tokens — e.g. point(0 0 &injected=evil) — was silently dropped by the destructuring and never inspected or rejected.

That raw value then reached the outbound Mapbox Directions API request through a hand-rolled encoder that escaped ,, (, ), and space, but not & or =:

function encodeExclude(value: string): string {
  return value
    .replace(/,/g, '%2C')
    .replace(/\(/g, '%28')
    .replace(/\)/g, '%29')
    .replace(/ /g, '%20');
}

...and was concatenated directly onto the query string rather than through URLSearchParams, which every other parameter uses:

queryString += `&exclude=${encodeExclude(input.exclude)}`;

Together, these two defects let a caller-supplied exclude value add new query parameters or override existing ones (e.g. alternatives) on the authenticated Directions API request.

Fix

  1. Validation (DirectionsTool.input.schema.ts): a point(...) entry's interior must now match /^(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)$/ — exactly two numbers and nothing else — instead of accepting anything past the first two space-separated tokens.
  2. Encoding (buildDirectionsRequestUrl.ts and its client-side twin in directionsAppHtml.ts): exclude is now appended via queryParams.append('exclude', input.exclude) like every other parameter, instead of a hand-rolled encoder + string concatenation. URLSearchParams percent-encodes the full character set correctly, including &/=, which the custom encoder missed.

Applying the fix to both the server builder and the client-side self-fetch twin closes it at both call sites, even though the client twin already had an independent isSafeDirectionsParams regex guard that happened to reject &/= before reaching this code path.

Testing

  • Two new tests confirm a point(...) entry with trailing content is now rejected by the schema with a clear message, and that the URL builder itself can no longer inject/duplicate a query parameter even if a malformed value reached it directly.
  • Extended the existing server/client parity test with the same injection-shaped input, confirming both sides encode it identically and safely.
  • Updated one pre-existing test assertion: URLSearchParams encodes a literal space as + rather than the old encoder's %20 — both are standard and decode identically server-side.
  • Full suite passes (763 tests), typecheck/lint/build clean.

Note on scope

This only touches directions_tool's exclude handling. I checked for the same anti-pattern (hand-rolled encoder + manual string concatenation bypassing URLSearchParams) elsewhere in the codebase — it doesn't appear anywhere else.

A point(<lng> <lat>) exclude entry was validated by splitting on spaces
and reading only the first two tokens, so any extra content after them
(e.g. "point(0 0 &injected=evil)") was never inspected or rejected.
That value then reached the outbound Directions API request through a
hand-rolled encoder that escaped ",", "(", ")", and space but not "&"
or "=", concatenated directly onto the query string instead of going
through URLSearchParams like every other parameter. Together these let
a caller-supplied exclude value add or override arbitrary query
parameters on the authenticated request.

Fixes both defects: point(...) entries must now be exactly two numbers
and nothing else, and exclude is now appended via URLSearchParams (in
both the server-side request builder and its hand-ported client-side
twin) so it's always correctly percent-encoded regardless of content.
@mattpodwysocki
mattpodwysocki requested a review from a team as a code owner July 29, 2026 15:49
@mattpodwysocki
mattpodwysocki merged commit 96260a8 into main Jul 29, 2026
5 checks passed
@mattpodwysocki
mattpodwysocki deleted the fix/directions-exclude-parameter-injection branch July 29, 2026 18:43
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.

2 participants