Skip to content

Security and trust

osra treats peers as semi-trusted: malformed payloads are handled cleanly, origin filters window messages in both directions, but nothing on the wire is authentication. This page spells out exactly which guarantees each option gives you, and which it does not.

On window transports, origin (default '*') does two things: it is the postMessage targetOrigin for outbound envelopes, and inbound messages whose event.origin doesn’t match are dropped:

// host page
const
const channel: unknown
channel
= await
expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], {
...;
}, {
...;
}>(value: {
...;
}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
(
const hostApi: {
getUser: () => Promise<{
name: string;
}>;
}
hostApi
, {
transport: Transport & {
readonly emit: Window;
readonly receive: Window & typeof globalThis;
}
transport
: {
emit: Window
emit
:
const iframe: HTMLIFrameElement & {
readonly contentWindow: Window;
}
iframe
.
contentWindow: Window

The contentWindow property returns the Window object of an HTMLIFrameElement.

MDN Reference

contentWindow
,
receive: Window & typeof globalThis
receive
:
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
},
origin?: string | undefined
origin
: 'https://widget.example',
})
// inside the iframe
const
const host: unknown
host
= await
expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], {
...;
}, {
...;
}>(value: {
...;
}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
(
const widgetApi: {
notify: (text: string) => Promise<string>;
}
widgetApi
, {
transport: Transport & {
readonly emit: Window;
readonly receive: Window & typeof globalThis;
}
transport
: {
emit: Window
emit
:
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
.
parent: Window

The Window.parent property is a reference to the parent of the current window or subframe.

MDN Reference

parent
,
receive: Window & typeof globalThis
receive
:
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
},
origin?: string | undefined
origin
: 'https://host.example',
})

Always set origin for cross-origin window messaging. Two caveats:

  • Events without an origin (worker messages, custom transports) bypass the check; it is only meaningful where the platform stamps event.origin. Non-window transports (Worker, MessagePort, WebSocket, ServiceWorkerContainer, WebExtension, custom) are not origin-filtered; WebSocket/ServiceWorker events carry their own unrelated origins, so filtering there would be a footgun.
  • The filter is not applied to custom function receives, which only get key/name filtering.

One outbound exception: the unsolicited announce beacon is posted with targetOrigin '*' regardless of the configured origin. Until a freshly created cross-origin iframe commits its document, its window still holds the initial about:blank (which inherits the embedder’s origin), so a strict targetOrigin would be dropped by the browser with a mismatch error on every retry.

This is safe because:

  • the beacon carries only channel identifiers (key, name, uuid), no data,
  • whatever answers it must still pass the inbound origin filter,
  • every other envelope (announce replies, init, messages, close) is only sent after the peer’s own message proved its committed origin, and keeps the strict targetOrigin.

Consequence: a wrong-origin embedder can observe the beacon’s identifiers, but cannot complete a handshake or receive any data.

runtime.onMessage / onConnect listeners receive untrusted input when paired with onMessageExternal / onConnectExternal. osra does no sender validation; the MessageContext passed to custom receive listeners exposes sender, and you must check sender.id / sender.url yourself before letting messages reach an exposed value. See low-level messaging for the MessageContext shape.

What a malicious same-channel peer can still do

Section titled “What a malicious same-channel peer can still do”

Be honest about the model: a peer that can post on your transport is semi-trusted. It can:

  • complete the handshake first (first wins),
  • spoof envelope uuids to address your connections, including sending { type: 'close' } to tear down another peer’s connection,
  • feed malformed boxes (which reject your handshake),
  • call anything you exposed,
  • flood you with announces or port traffic.

DoS-hardening is not complete. Don’t expose privileged functions on channels where untrusted code can post; on windows, pin origin; in extensions, validate senders.