fix: stop error-code lookups resolving through Object.prototype - #1913
Conversation
A wire error coded like 'toString' resolved map[error.code] to Object.prototype methods. In reconcileORPCError the resolved value has no .data schema, so the error was stamped defined/inferable with its payload never validated. Sibling lookups in the error constructor map, RPC/OpenAPI errorStatusMap, Effect catchORPCErrorCodes, and the smart coercion link plugin had the same prototype hole. Adds getOwn to @orpc/shared and uses it at every error-code lookup.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
ℹ️ One regression test cannot fail without its guard — minor.
Reviewed changes
getOwnhelper (@orpc/shared): newObject.hasOwn-based property read returningundefinedfor inherited keys liketoString, with targeted unit tests.- Error-code lookups hardened at five sites:
reconcileORPCError,createORPCErrorConstructorMap, EffectcatchORPCErrorCodes, and both RPC/OpenAPI handler codec status maps now usegetOwn(map, code)instead ofmap[code], so prototype-coded wire errors downgrade to unmapped instead of being promoted todefined/inferable, receivingstatus: [Function], or invokingObject.prototypemethods as handlers.
I verified the exploit claims empirically by temporarily reverting each source guard and re-running the corresponding test: all five functional guards fail their regression tests when reverted (e.g. encodeError returns [Function toString] as the status; catchORPCErrorCodes dies with Not a valid effect: [object Object]) and pass with the fix. The new getOwn unit tests and the reconciliation downgrade path are sound. One test does not pin anything — see the inline comment.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Object.prototype members never carry a .data property, so the smart coercion interceptor rethrows the original error with or without the getOwn guard; the test passed either way and pinned nothing.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Dropped the unfalsifiable
does not resolve error codes through Object.prototyperegression test fromsmart-coercion-link-plugin.test.ts— it passed with or without thegetOwnguard (noObject.prototypemember carries.data), so it pinned nothing. The prior review thread was resolved, and thegetOwnlookup atsmart-coercion-link-plugin.ts:71stays for consistency with the other five hardened sites.
Verified the file still has all 5 original tests passing (pnpm vitest run packages/json-schema/src/smart-coercion-link-plugin.test.ts). Nothing in the incremental delta raises new concerns; the remaining five regression tests each fail without their guard as established in the prior review.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…named codes Intent assertion for the smart coercion error path: a defined error coded like 'toString' must be rethrown untouched. It cannot distinguish the getOwn guard today (no Object.prototype member carries .data), but it protects this path against future refactors.
…ototype-named codes" This reverts commit a28e66d.

Error-code lookups like
map[error.code]resolved codes such astoStringorconstructorthroughObject.prototype. InreconcileORPCErrorthe resolved prototype method has no.dataschema, so a crafted wire error was stampeddefined/inferablewith its payload never validated. This adds agetOwnhelper to@orpc/sharedand uses it at every error-code lookup.Fixes
reconcileORPCErrorno longer promotes prototype-coded errors to defined; they downgrade like any unmapped code.encodeErrorreturn the default error status for prototype-coded errors instead of an invalidstatus: [Function]response.catchORPCErrorCodes(Effect) no longer invokesObject.prototype.toStringas an error handler.SmartCoercionLinkPluginno longer probes prototype members when looking up error data schemas.createORPCErrorConstructorMapconstructors treat prototype-named codes as undefined config.Testing
getOwn.pnpm lint,pnpm type:check, and affected package tests (182 files, 2070 tests) pass.