Repository navigation
[remote-ui] Validate values received from the remote environment - #739
Open
greatscotty wants to merge 1 commit into
Open
greatscotty wants to merge 1 commit into
greatscotty wants to merge 1 commit into
Conversation
The remote environment is untrusted, but `createRemoteReceiver()` stored text and props without checking their shape, and the React host renderer spread remote props straight onto host components. Preact (via preact/compat) treats any object whose `constructor` resolves to `undefined` as a virtual node, and Vue treats any object flagged `__v_isVNode` as one; such objects survive the RPC layer, so a remote could render arbitrary markup on the host via text nodes or props. - Add validation helpers to `@remote-ui/core` and apply them in the receiver on mount, insert, text update, and props update (including fragment props): text is normalized to a string, props are rejected if they contain a framework-node shape, `__proto__` is rejected as a prop name, and component types must be strings. - `@remote-ui/react` `RemoteComponent` never passes `children`, `ref`, `key`, or `dangerouslySetInnerHTML` from remote props to host components. - Add tests exercising each route with smuggled payloads.
andrewiggins
requested changes
Oct 27, 2026
Contributor
There was a problem hiding this comment.
Can we also address the Vue code called out in the PR description?
I think we need to make the following changes to the vue package:
- Raise the
vuepeer dependency inpackages/vue/package.jsonto>=3.5.9 <4.0.0. Before 3.5.9, Vue treats any object child as a vnode (vuejs/core#12038), so the__v_isVNodecheck doesn't protect older versions. - In
packages/vue/src/host/RemoteComponent.ts, filter reserved props from remote props beforeh(Implementation, props), mirroringRESERVED_PROPSin the React host. Cover at leastinnerHTML,outerHTML,textContent,key,ref,onVnode*, and__proto__. Note to coveronVnode*we'll need a little bit more complex logic than aSetsince it's a prefix of props we need to cover.
| 'ref', | ||
| 'key', | ||
| 'dangerouslySetInnerHTML', | ||
| '__proto__', |
Contributor
There was a problem hiding this comment.
Let's add innerHTML and outerHTML here for preact/compat, with corresponding tests.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
remote-uicounterpart to #738.The remote environment is untrusted, but
createRemoteReceiver()stored text and props without checking their shape, and@remote-ui/react’sRemoteComponentspread remote props straight onto host components. Hosts that run this package onpreact/compatare exposed to Preact’s virtual-node detection (constructor === undefined), and the Vue host to Vue’s (__v_isVNode: true). Such objects survive@remote-ui/rpc’s encoder (it rebuilds objects by spreading own keys, so an ownconstructor: undefinedkey is preserved), so a remote could render arbitrary markup – includingdangerouslySetInnerHTML– on the host via a text node (H1 4072911) or a prop (H1 4133346).@remote-ui/corevalidation.ts, exported from the package:normalizeRemoteText,assertSafeRemoteProp/assertSafeRemoteProps,assertRemotePropName,assertRemoteComponentType.createRemoteReceiver()applies them innormalizeNode(mount, insert, fragment props),updateText, andupdateProps: text is normalized to a string (objects viaObject.prototype.toString, never the remote’stoString), props are rejected if any object in them hasconstructor === undefinedor__v_isVNode === true,__proto__is rejected as a prop name, component types must be strings. Validation runs before anything is retained or attached.@remote-ui/reactRemoteComponentnever passeschildren,ref,key,dangerouslySetInnerHTML(or__proto__) from remote props to the host implementation.Tests
packages/core/src/tests/validation.test.ts– helper coverage (Preact and Vue shapes, null prototypes, Map/Set/cyclic values, shapes hidden behind a redirected prototype,__proto__names).packages/core/src/tests/receiver.test.ts– receiver-level coverage for mount/insert/update/fragment-prop text and props.packages/react/src/tests/untrusted-values.test.tsx– end to end throughRemoteRendererinto jsdom.Before this change the exploit was confirmed end to end with
@remote-ui/core+@remote-ui/reactonpreact/compat: text mount/update, prop mount/update, text inside a fragment prop, anddangerouslySetInnerHTMLvia spread all injected attacker markup into the host DOM; all are closed after.Full suite (
loom test),loom type-check, andloom lintpass.cc @robin-drexler