Skip to content

Validate values received from the remote environment - #740

Open
robin-drexler wants to merge 1 commit into
mainfrom
validate-untrusted-remote-values
Open

robin-drexler wants to merge 1 commit into
mainfrom
validate-untrusted-remote-values

Conversation

@robin-drexler

Copy link
Copy Markdown
Member

Non-fork copy of #738 (by @greatscotty) so CI runs against the main repo. Same commit (6385dd4), no changes.


What this changes

The remote environment is untrusted, but the host receivers stored text, attribute, and property values without checking their shape, and the Preact/React host renderers handed them straight to the framework. Preact treats any object whose constructor resolves to undefined as one of its own virtual nodes. Objects like that can’t be written as JSON but do survive postMessage (and the thread deserializer also lets a literal __proto__ key through, which has the same effect), so a remote could send, where the host expects a string, an object that Preact turns into real DOM – including dangerouslySetInnerHTML.

A fix that only coerced text nodes would have left attributes and properties open; this closes all of the routes at the receiver, which is the host’s trust boundary.

@remote-dom/core

  • New validation helpers exported from @remote-dom/core/receivers: normalizeRemoteText, normalizeRemoteAttribute(s), assertSafeRemoteProperty/assertSafeRemoteProperties, assertRemotePropertyName, assertRemoteEventListener(s), assertRemoteElementName, normalizeRemotePropertyUpdate.
  • RemoteReceiver applies them on insert and on updateText/updateProperty: text and attributes are normalized to strings (objects are described via Object.prototype.toString, never by calling the remote’s toString; nullish attribute updates remove the attribute), property values are rejected if any object in them has constructor === undefined, event listeners must be functions, __proto__ is rejected as a name, element names must be strings.
  • updateRemoteElementAttribute() now sends the same coerced string it stores locally (RemoteElement.updateRemoteAttribute() previously sent the raw argument).

@remote-dom/signals

  • SignalRemoteReceiver applies the same validation.

@remote-dom/preact, @remote-dom/react

  • usePropsForRemoteElement only passes string attributes through, and never takes children, ref, key, dangerouslySetInnerHTML (or __proto__) from remote properties, attributes, or slot names.

Tests

New tests send smuggled payloads (both the own-constructor and __proto__ variants, passed through a simulated structured-clone + deserializer) end to end through the real receiver and renderer into jsdom:

  • packages/preact/source/tests/untrusted-values.test.tsx – text (initial + update), attributes (initial + update), properties (initial, update, nested), spread props, ref override. Before this change 16/20 fail with attacker markup in the DOM; after, all pass.
  • packages/react/source/tests/untrusted-values.test.tsx – React’s $$typeof check prevents markup injection, but the host tree crashed (“Objects are not valid as a React child”); now renders plain text.
  • packages/core/source/tests/receivers-untrusted-values.test.ts, additions to SignalRemoteReceiver.test.ts and elements.test.ts – helper and receiver-level coverage, including __proto__ names, Map/Set/cyclic values, and vnode shapes hidden behind a redirected prototype.

Full suite, type-check and prettier pass.

Notes

  • Text/attributes are coerced rather than rejected so older remotes that send numbers keep working; vnode-shaped property values throw, since no legitimate data has that shape and there is no safe coercion.
  • A matching change for the remote-ui branch is in a separate PR.

The remote environment is untrusted, but receivers stored text, attribute,
and property values without checking their shape, and the Preact/React host
renderers handed them straight to the framework. Preact treats any object
whose `constructor` resolves to `undefined` as a virtual node; such objects
survive `postMessage`, so a remote could render arbitrary markup (including
`dangerouslySetInnerHTML`) on the host via text nodes, attributes, or
properties.

- Add validation helpers to `@remote-dom/core/receivers` and apply them in
  `RemoteReceiver` and `SignalRemoteReceiver` on insert and update: text and
  attributes are normalized to strings (nullish attributes are removed),
  property values are rejected if they contain a framework-node shape,
  event listeners must be functions, `__proto__` is rejected as a name, and
  element names must be strings.
- Preact and React renderers ignore non-string attributes and never take
  `children`, `ref`, `key`, or `dangerouslySetInnerHTML` from remote data.
- `updateRemoteElementAttribute()` sends the same coerced string it stores.
- Add tests exercising each route with smuggled payloads end to end.
@andrewiggins

Copy link
Copy Markdown
Contributor

I posted a review to #738 (link) but I can move it here if this the PR you want to carry forward with.

This branch has not been deployed

No deployments
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.

3 participants