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.