Skip to content

fix: prevent path traversal in style and tileset tool URL construction#103

Merged
zmofei merged 6 commits into
mainfrom
fix/path-traversal-security
May 5, 2026
Merged

fix: prevent path traversal in style and tileset tool URL construction#103
zmofei merged 6 commits into
mainfrom
fix/path-traversal-security

Conversation

@zmofei

@zmofei zmofei commented May 4, 2026

Copy link
Copy Markdown
Member

Summary

Five MCP tools concatenated user-supplied path parameters directly into Mapbox API URLs without input validation or URL encoding. Because Node.js fetch uses the WHATWG URL parser, ../ sequences in a styleId or tilesetId input were normalized client-side before the request was sent — allowing the request to reach unintended API endpoints.

This PR applies a two-layer defense, hardens output schema validation across all affected tools, and adds two additional security fixes discovered during review.

Changes

Input validation — allowlist regex on all path parameters

  • Add src/tools/shared/styleId.schema.ts — shared Zod schema with regex /^[a-z0-9][a-z0-9-]*[a-z0-9]$/, which accepts both user-owned style IDs and built-in Mapbox style names (standard, streets-v12, navigation-day-v1, etc.) while rejecting path separators, dots, percent-encoded sequences, and null bytes
  • Apply styleIdSchema to DeleteStyleTool, PreviewStyleTool, RetrieveStyleTool, and UpdateStyleTool input schemas via the shared module
  • Add format validation to TilequeryTool tilesetId input: /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/ (enforces owner.tileset-name format)

URL encoding — encodeURIComponent at every construction site

Wrap both username (extracted from JWT) and styleId/tilesetId in encodeURIComponent at every URL construction site across all five tools. This encodes /%2F, preventing any remaining path segments from being interpreted as directory traversal by the URL parser.

Affected files: DeleteStyleTool.ts, PreviewStyleTool.ts (URL + resource URI), RetrieveStyleTool.ts, TilequeryTool.ts, UpdateStyleTool.ts

Output schema validation — hard-fail on mismatch

Replace the previous silent fallback behavior (returning raw unvalidated API responses) with explicit isError: true responses when the API response does not match the declared output schema. This prevents unintended API responses from being forwarded to callers.

Applied to: RetrieveStyleTool, UpdateStyleTool, TilequeryTool, ListStylesTool, ListTokensTool, CreateTokenTool

Remove now-unused BaseTool.validateOutput() method.

Cross-origin Link header rejection (ListTokensTool)

ListTokensTool auto-paginates by following the Link: rel=next response header. Without origin validation, a malicious or compromised response could redirect pagination to an attacker-controlled host and exfiltrate the access token via the appended access_token query parameter.

Added origin validation: next-page URLs whose origin does not match the configured API endpoint are rejected with a warning log. Pagination stops rather than following a cross-origin redirect.

Token redaction from logs and error messages

Added redactToken() utility that strips access_token=... query parameter values from strings before they reach log output or MCP client error responses. Network errors (DNS failure, timeout) from Node.js include the full request URL in their message — without redaction, the access token would be forwarded to callers in error responses.

Applied to: catch-block error messages in MapboxApiBasedTool.run(), debug-level URL log in ListTokensTool.

Tests

  • Add test/security/path-traversal.test.ts with 52 tests covering schema rejection, valid ID acceptance, URL encoding, and response schema mismatch behavior
  • Add cross-origin Link header rejection test in ListTokensTool.test.ts
  • Fix test assertions for Prettier 3.8.x import formatting, Zod v4 error codes, and @mcp-ui/server v6.1.0 mimeType changes

Test plan

  • npm test passes (576/576)
  • npx tsc --noEmit — zero errors
  • npx vitest run test/security/path-traversal.test.ts — 52/52 pass

zmofei added 2 commits May 4, 2026 14:14
Five tools (RetrieveStyle, DeleteStyle, UpdateStyle, PreviewStyle,
TilequeryTool) concatenated user-supplied path parameters directly
into Mapbox API URLs without validation or encoding. Because Node.js
fetch uses the WHATWG URL parser, `../` sequences were normalized
before sending, allowing requests to reach unintended API endpoints.

Changes:
- Add shared `styleIdSchema` with allowlist regex that rejects path
  separators, dots, percent-encoded sequences, and null bytes
- Apply `styleIdSchema` to all four style tools via a shared module
  (src/tools/shared/styleId.schema.ts)
- Add format validation to TilequeryTool tilesetId (owner.name format)
- Wrap both username and styleId/tilesetId in `encodeURIComponent`
  at every URL construction site (defense-in-depth)
- Replace silent fallback in output schema validation with explicit
  `isError: true` responses across all API tools, preventing
  unintended API responses from being forwarded to callers
- Remove now-unused BaseTool.validateOutput() method
- Add test/security/path-traversal.test.ts with 52 tests covering
  schema rejection, valid ID acceptance, URL encoding, and response
  schema mismatch behavior
…rades

- Reformat dynamic imports to match Prettier 3.8.x style
- Update Zod v4 error code assertions (invalid_value, too_big)
- Update mimeType assertions for @mcp-ui/server v6.1.0 (text/html;profile=mcp-app)
- Fix PreviewStyleTool test mimeType expectation
@zmofei
zmofei force-pushed the fix/path-traversal-security branch from cc9ee0b to 11cdd28 Compare May 4, 2026 11:52
- Validate that pagination next-page URLs from Link response headers
  share the same origin as the configured API endpoint; cross-origin
  URLs are rejected to prevent access token exfiltration
- Add redactToken() utility that strips access_token query parameter
  values from strings before they reach log output or MCP client error
  responses (network errors include the full request URL in their
  message which would otherwise expose the token)
- Remove full URL from info/debug level pagination log messages
@zmofei
zmofei force-pushed the fix/path-traversal-security branch from 8bb1936 to 064a3a1 Compare May 4, 2026 12:14
@zmofei
zmofei marked this pull request as ready for review May 4, 2026 12:21
@zmofei
zmofei requested a review from a team as a code owner May 4, 2026 12:21
Comment thread src/tools/retrieve-style-tool/RetrieveStyleTool.ts Outdated
Comment thread src/tools/tilequery-tool/TilequeryTool.ts
Comment thread src/tools/retrieve-style-tool/RetrieveStyleTool.ts
Comment thread src/tools/retrieve-style-tool/RetrieveStyleTool.ts
@zmofei
zmofei merged commit c3b9808 into main May 5, 2026
1 check passed
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