Security model
User modules are third-party code. readout contains them with defense-in-depth, so even a malicious module is tightly bounded.
- Isolated iframe. Each surface renders in an iframe with
sandbox="allow-scripts"and noallow-same-origin, giving it an opaque origin. It cannot script the overlay, read its auth token, or touch host APIs. The only channel ispostMessage, mediated by the broker. - Locked-down CSP. Module assets are served with
connect-src 'none', so a module cannot reach the network directly — every request must go throughps.host.fetch. - The host is the trust boundary. Read-only data streams and playback actions are permission-checked;
the privileged capabilities — storage, notifications,
net.fetch— are handled by the host, which re-checks the manifest's permissions on every call. A tampered frontend can't grant a module anything its manifest didn't declare. - Physical storage namespacing. Each module's storage is stored under its own id, so a module can't read another's data even if it lies about a key.
net.fetch hardening
When a module calls ps.host.fetch, the host:
- allows http(s) only, and requires the host to match the module's
allowedDomains; - runs an SSRF guard that re-validates the actually-connected IP — private, loopback, link-local, CGNAT, and multicast ranges are blocked, which also defeats DNS-rebinding;
- sends no cookies and follows no redirects;
- allow-lists request headers and strips
Set-Cookiefrom the response; - caps the response (≈5 MB / 15 s); and
- applies a per-module rate limit.
Even sandboxed, users should only install modules they trust. The consent prompt shows exactly what each module can do before it runs.