Skip to content

fix: never throw when encoding or decoding URI components - #2011

Merged
dinwwwh merged 1 commit into
middleapi:mainfrom
dinwwwh:claude/safe-uri-encode-decode-ed2e03
Sep 12, 2026
Merged

fix: never throw when encoding or decoding URI components#2011
dinwwwh merged 1 commit into
middleapi:mainfrom
dinwwwh:claude/safe-uri-encode-decode-ed2e03

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Sep 12, 2026

Copy link
Copy Markdown
Member

encodeURIComponent throws a URIError on a lone surrogate, so a path param or router key containing one crashed the OpenAPI link, pathToHttpPath, and the static file redirect. @orpc/shared now exports safeEncodeURIComponent and safeDecodeURIComponent (replacing tryDecodeURIComponent), every package call site uses them, and ESLint rejects the raw globals in package sources.

Fixes

  • Lone surrogates are encoded as U+FFFD instead of throwing, the same replacement URLSearchParams and String.prototype.toWellFormed apply.
  • Malformed percent escapes still decode to the raw input, as before.
  • no-restricted-globals replaces the ban/ban entry: the ban plugin only inspects call expressions, so a bare reference like path.map(encodeURIComponent) was never flagged. Test files stay exempt.

Performance

  • No overhead on well-formed input: encode is a bare try/catch around the native call. A JS pre-scan for "all unreserved" input costs about 4ns per char, more than the native encoder itself, so none was added.
  • Decode skips the decoder entirely when the input has no %, 2-8x faster on typical path segments.

Testing

  • Encode matches the native encoder for every non-surrogate BMP code point; lone surrogate and malformed decode cases are covered.
  • Lint, type-check, and the shared, openapi, client, and node suites pass.

encodeURIComponent throws a URIError on a lone surrogate, so a path param
or router key containing one crashed the OpenAPI link, pathToHttpPath and
the static file redirect. @orpc/shared now exports safeEncodeURIComponent
and safeDecodeURIComponent (replacing tryDecodeURIComponent): lone
surrogates are encoded as U+FFFD like URLSearchParams does, malformed
percent escapes decode to the raw input, and input without a percent sign
skips the decoder entirely.

The raw globals are now rejected by no-restricted-globals in package
sources. The previous ban/ban entry only checked call expressions, so a
bare reference such as path.map(encodeURIComponent) was never flagged.
@pkg-pr-new

pkg-pr-new Bot commented Sep 12, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2011

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@2011

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2011

@orpc/client

npm i https://pkg.pr.new/@orpc/client@2011

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2011

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@2011

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2011

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2011

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2011

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@2011

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@2011

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@2011

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2011

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2011

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@2011

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2011

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2011

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2011

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2011

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2011

@orpc/server

npm i https://pkg.pr.new/@orpc/server@2011

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@2011

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2011

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@2011

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@2011

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@2011

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@2011

commit: 8c08b41

@codspeed-hq

codspeed-hq Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 11.2%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 29 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
serve file 3.5 ms 3.9 ms -11.2%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing dinwwwh:claude/safe-uri-encode-decode-ed2e03 (8c08b41) with main (9ad5039)

Open in CodSpeed

@pullfrog pullfrog Bot 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.

✅ No new issues found.

Reviewed changes — a single commit replacing tryDecodeURIComponent with never-throwing URI helpers, updating every package call site, and swapping the lint guard.

  • New safeEncodeURIComponent / safeDecodeURIComponent in packages/shared/src/uri.ts (replacing tryDecodeURIComponent): encode falls back to replacing lone surrogates with U+FFFD when the native call throws; decode early-returns inputs without % and returns malformed input unchanged.
  • Call sites routed through the safe helpers in shared/src/http.ts, the OpenAPI link codec and matcher, the client batch plugin, and the node static-file handler — behavior is unchanged for well-formed input.
  • Lint guard swapped from ban/ban to no-restricted-globals (scoped to packages/*/src/**, tests exempt), which also catches bare references like map(encodeURIComponent).
  • Tests added for well-formed encode parity (incl. a full non-surrogate BMP sweep), lone-surrogate encode, and malformed/valid decode.

Verified locally: the affected shared/openapi/node/client suites pass (261 tests), no-restricted-globals fires on bare references, and a fuzz comparison shows safeEncodeURIComponent is exactly encodeURIComponent(value.toWellFormed()) over exhaustive surrogate combinations and 200k random inputs. The lookbehind regex is supported by every runtime in the documented requirements.mdx matrix (Safari 16.4 is both the floor and lookbehind's first version).

Pullfrog  | View workflow run | Using openrouter/deepseek/deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit 8f1f9e4 into middleapi:main Sep 12, 2026
9 of 10 checks passed
@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

1 participant