Appearance
Component sandbox
Every node's settings form is a small web page written by whoever built that node. This page explains exactly how FlowRunner stops one of those forms from reading your session, your other projects, your secrets, or your data — and, just as honestly, where that boundary does and does not reach today.
What actually runs where
When you select a node on the canvas, its config UI loads. That UI is the only part of a node that a component author ships to your browser. It does one job: read and write that one node's saved settings.
The part of a node that does the work — the engine handler that runs when your flow executes — is first-party server code that lives in the FlowRunner app itself. Components do not run in your flow's server context. So even a hostile config UI can, at worst, write bad settings into the single node it is attached to. It never touches the server, your database credentials, or another node's execution.
The two walls
Wall 1 — a separate browser origin. The config UI is loaded into an iframe:
html
<iframe sandbox="allow-scripts allow-same-origin"
src="https://components.flowrunner.io/…"></iframe>The src points at the components origin, which is different from the editor app's origin. Because the browser's same-origin policy is what enforces isolation, the iframe cannot read the app's cookies, its localStorage, or its DOM — it is a different site as far as the browser is concerned. The sandbox attribute grants only allow-scripts (so the form can run) and allow-same-origin (so it keeps its own origin identity for its own storage). It is not granted allow-forms, allow-popups, or allow-top-navigation, so it cannot submit forms elsewhere, open windows, or navigate the page around you.
Wall 2 — one typed door. The iframe has no direct handle to the host. The only channel between them is a typed postMessage RPC bridge.
The host only accepts messages whose source is that exact iframe, and every host→guest reply is pinned to the iframe's own origin (resolveTargetOrigin) — so if the frame is ever navigated away, project data cannot be delivered to a stranger.
What a component can see and do
On startup the host hands the guest a scoped context (getContext) and nothing more:
| Included | Deliberately excluded |
|---|---|
| This node's own saved settings | Your login session / auth token |
| Editor theme (light/dark colours) | Any secret values |
| Limited project info: project/file id, name, folder path | Other projects or files |
| Declared upstream field names (for reference pickers) | Cookies, localStorage, the app DOM |
Every action beyond reading that context is gated by permissions the component declares in its manifest. The host refuses any RPC whose permission was not approved:
| RPC method | Required permission |
|---|---|
getContext | none (context is already scoped) |
getProjectData | project.read |
setProjectData, dispatchEvent | project.write |
requestApiCall | network.declared |
network.declared is the strictest: a component cannot fetch anywhere it likes. The host makes the request for it, and only if the target origin appears in the manifest's network allowlist — otherwise it is rejected as ENDPOINT_NOT_DECLARED. (storage.read/storage.write exist in the permission vocabulary but map to no method yet; they are reserved.)
WARNING
Be clear about the limits of this today. Built-in nodes are first-party and trusted. The per-origin isolation model is the foundation for a future third-party marketplace, but the current production deploy serves all built-in config UIs from a single trusted origin (components.flowrunner.io, path-based per node). That is safe because they are all first-party — but true third-party isolation needs per-origin hosting (a wildcard subdomain per plugin, as used in development) so one plugin cannot share an origin with another. And note the other half of the boundary: untrusted third-party server-side execution is not sandboxed at all yet — only config UIs are isolated. Running node logic you don't control on the server is not supported.
The trust chain being built
To let a marketplace verify that a plugin was published by its author and not tampered with in transit, FlowRunner includes Ed25519 manifest signing (src/lib/marketplace/signing.ts): a manifest is canonicalised (keys sorted deterministically) and signed, and the signature is verified against the publisher's public key before install. This is the cryptographic primitive; publisher accounts, a registry, and the install flow build on top of it.
See also
- /api/sandbox — the full RPC protocol and message shapes
- /api/node-authoring — where the trusted engine boundary sits
- /security/secrets — why secret values never reach a component
- /security/self-hosting — configuring the components origin