Skip to content

fix(tsx): refuse packages with capabilities on the web, where the page cannot isolate them - #195

Merged
houko merged 2 commits into
mainfrom
fix/web-sandbox-fail-closed
Nov 15, 2026
Merged

houko merged 2 commits into
mainfrom
fix/web-sandbox-fail-closed

Conversation

@houko

@houko houko commented Nov 15, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

On the web every js.Runtime is a hidden iframe of the page's origin, and a sandboxed package runs in such a realm. Nothing removes the realm's own browser globals, so package code reaches the page's XMLHttpRequest, WebSocket, navigator (clipboard included), localStorage and parent without going through Go. Capabilities and Sandbox.Grant therefore held only the requests the runtime and core make; a package's storage partition and a pkg.Trust kept in the page's localStorage (accepted builds, revocations) were open to it. The docs promised capability enforcement on every target with no web exception.

This makes the web fail closed:

  • Sandbox.start (and so tsx.Sandboxed, tsx.View and tsx.Load) refuses on the web a sandbox with non-nil Capabilities or Grant with the new tsx.ErrUnisolated, whose message names the missing isolation, unless the host sets the new Sandbox.WebUnisolated, which states the package runs with the page's authority. View and Load always set Capabilities (the package's own), so every package is refused on the web by default. Other targets ignore the field.
  • A sandbox with neither Capabilities nor Grant still runs on the web: its contract is already "reaches what the app's code does" on every target, which is exactly what the page's realm gives it. This keeps nsgo dev -limits without capabilities and tsx.Sandboxed used for limits alone working. On the web such a sandbox's Storage partition and Host do not confine it either, which docs/architecture/tsx.md now says.
  • nsgo dev -limits -target web for an app whose nsgo.yaml declares capabilities: now shows the ErrUnisolated refusal, as a host's View of the published package would, instead of running the app.
  • tsx.Load checks before running the package and returns the refusal to its caller. (Inside the child scope's Run, a panic goes to the nearest core.Catch instead.)
  • The decision is one function, Sandbox.isolation(browser bool), tested on every target headless; a web-target test shows View and Load refusing and the opted-in view running.
  • examples/plugins does not opt in: on the web it shows the refusal for both packages; its tests assert that on the web and skip the older-build test there (no build is ever accepted).
  • docs/architecture/tsx.md lists the exception in "The page's engine", the Sandbox and Signed packages sections and the departures table; the public TSX guide (en, zh) says how the web behaves and what WebUnisolated gives up; the roadmap gains "Packages isolated on the web".

Left out: proposal item 2, real isolation on the web (an opaque-origin sandbox="allow-scripts" iframe or a Worker, with the runtime's Go-backed APIs over postMessage / MessageChannel). The browser engine's Value API is synchronous and would need an asynchronous bridge for sandboxed realms, which is a separate project; it is recorded on the roadmap.

Verification

  • go tool nsgo test ./tsx
  • go tool nsgo test -target web ./tsx (every sandbox, view and plugin test passes; TestDevToolsNameTSXLines fails on the web on main too)
  • go tool nsgo test ./examples/plugins
  • go tool nsgo test -target web ./examples/plugins
  • go tool nsgo check
  • go tool nsgo check -target all

TestTheWebRefusesWhatItCannotIsolate fails on the web target without the check in Sandbox.start (both packages run).

Screenshots

None

@github-actions github-actions Bot added size/M conflict Conflicts with the base branch; resolve it first documentation Improvements or additions to documentation tsx bug Something isn't working and removed conflict Conflicts with the base branch; resolve it first labels Nov 15, 2026
@houko
houko force-pushed the fix/web-sandbox-fail-closed branch from cb1e5d4 to 73f5b22 Compare November 15, 2026 06:23
@github-actions github-actions Bot added conflict Conflicts with the base branch; resolve it first and removed conflict Conflicts with the base branch; resolve it first labels Nov 15, 2026
@houko
houko force-pushed the fix/web-sandbox-fail-closed branch from 73f5b22 to 5cc54fc Compare November 15, 2026 12:59
@github-actions github-actions Bot added conflict Conflicts with the base branch; resolve it first and removed conflict Conflicts with the base branch; resolve it first labels Nov 15, 2026
…e cannot isolate them

On the web every js.Runtime is a hidden iframe of the page's origin, so a sandboxed package's realm reaches the page's XMLHttpRequest, WebSocket, navigator, localStorage and window past Go. Its capabilities held only the requests the runtime and core make, and its storage partition and a Trust kept in the page's storage were open to it, while the docs promised enforcement on every target.

Sandbox.start, and so Sandboxed, View and Load, now refuses on the web a sandbox with Capabilities or a Grant with ErrUnisolated, unless the host sets Sandbox.WebUnisolated to run it with the page's authority. A sandbox with neither still runs: it reaches what the app's code does on every target. Load returns the refusal rather than sending it to a Catch.

examples/plugins shows the refusal on the web. docs/architecture/tsx.md, the public TSX guide and the roadmap record the exception and the isolation still to come.
@houko
houko force-pushed the fix/web-sandbox-fail-closed branch from 5cc54fc to 1584ae1 Compare November 15, 2026 13:44
@github-actions github-actions Bot removed the conflict Conflicts with the base branch; resolve it first label Nov 15, 2026
@houko
houko merged commit c12742b into main Nov 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation size/M tsx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant