Skip to content

Sandboxing & Permissions

When you add a WASM node to a workflow, you’re running code that was written by someone outside the Flow-Like core team. This guide explains how Flow-Like keeps that code contained, what the permission system does, and how to make informed trust decisions.

  • Every WASM node runs inside an isolated sandbox — it cannot access your files, network, or system unless explicitly allowed.
  • Nodes declare which permissions they need (e.g. network access, streaming). You see these before anything runs.
  • You can trust a package once and skip the prompt for future workflows.
  • If a node declares no permissions, it can only do pure computation — read inputs, return outputs.

Think of a WASM sandbox like a sealed room with no windows or doors. The code inside can think and calculate, but it can’t see or touch anything outside. Flow-Like only opens specific, controlled hatches when a node requests them.

Flow-Like uses Wasmtime, a production-grade WebAssembly runtime, to run every external node. Each node gets:

Isolation layerWhat it means
Separate memoryThe node has its own memory space. It cannot read or write the host process memory.
No filesystem accessUnless specifically granted node/user storage, the node cannot touch any files.
No network by defaultHTTP, WebSocket, and other network calls are blocked unless the node declares the permission.
CPU time limitsNodes have execution budgets. A runaway loop will be terminated, not your machine.
Memory capsEach node has a memory ceiling. It cannot allocate unbounded RAM.
Deterministic executionWASM execution is reproducible — same inputs produce same outputs.

This is fundamentally different from running a native plugin or a shell script, which typically has full access to your system.


Previously, permissions were declared in the package manifest (flow-like.toml). This was a static, package-wide declaration. Now, permissions are declared per-node directly in code. When a node’s get_node or get_nodes function returns its definition, it includes a list of permissions. This means:

  • Different nodes in the same package can request different permissions.
  • The permission list is always up to date with the actual code.
  • If a node requests no permissions, it needs nothing beyond basic computation.
PermissionWhat it allowsRisk level
streamingSend output incrementally as it’s produced (e.g. token-by-token LLM responses)Low — data only flows outward through the normal output channel
network:httpMake HTTP requests to external servicesMedium — the node can talk to the internet
network:websocketOpen persistent WebSocket connectionsMedium — similar to HTTP but long-lived
storage:readRead from the storage backendLow — read-only access to stored data
storage:writeWrite to the storage backendMedium — can persist data
storage:nodeAccess a private, per-node storage areaLow — scoped to this node only
storage:userAccess user-level storageMedium — shared across nodes
variablesRead and write flow variablesLow — scoped to the current workflow
cacheUse the execution cache to skip redundant workLow — performance optimization only
modelsAccess AI/ML models configured in Flow-LikeMedium — can invoke model inference
a2uiGenerate dynamic UI elements at runtimeLow — visual only, no system access
oauthUse OAuth authentication flowsMedium — involves user credentials
functionsCall registered host functionsMedium — depends on which functions are exposed

A node with an empty permissions list can only:

  • Read its input pins
  • Write to its output pins
  • Do computation in memory (math, string manipulation, data transformation)

It cannot call out to the network, read files, stream output, or invoke models. This is the safest category.


When you run a workflow that contains WASM nodes you haven’t previously approved, Flow-Like shows a confirmation dialog. It lists:

  1. Which packages are about to run
  2. What permissions each package needs (aggregated across all its nodes in the workflow)
  3. Trust options — how long your approval should last
OptionScopeWhen to use
One-timeThis execution onlyYou want to test something once
This eventAll executions triggered by this eventYou trust the workflow for this specific trigger
This boardAll executions of this boardYou trust the workflow regardless of how it’s triggered
Trust these packages everywhereAll workflows using these packagesYou fully trust the package author

Package-level trust is stored locally on your machine. It’s never sent to a server. You can clear it at any time from your browser’s local storage (keys prefixed with wasm-consent-package-).


WASM adds a small overhead compared to native Rust nodes. Here’s what to expect:

AspectImpactDetails
Startup~1-5 ms per nodeThe WASM module is compiled on first load and cached afterward
Execution~1.1-1.3x native speedWasmtime’s optimizing compiler produces near-native code
MemorySlightly higherEach node has its own memory space (default cap applies)
Host calls~0.1 ms per callCrossing the sandbox boundary (e.g. reading storage) has a small cost
CachingTransparentCompiled modules are cached — subsequent loads are nearly instant

For most workflows, the overhead is negligible. It becomes noticeable when:

  • A node is called thousands of times in a tight loop (consider batching)
  • The node makes many small host calls (consider batching reads/writes)
  • The node processes very large data in memory (watch the memory cap)

Native Rust nodes remain the best choice for performance-critical hot paths. WASM nodes are ideal for extensibility, integrations, and logic that changes frequently.


If you’re a node author, follow these guidelines:

// Good — only requests what this node actually uses
node! {
name: "fetch_data",
friendly_name: "Fetch Data",
description: "Downloads data from an API",
category: "Integrations/HTTP",
inputs: { exec: Exec, url: String },
outputs: { exec_out: Exec, body: String },
permissions: ["network:http"],
}
// Bad — requests everything "just in case"
node! {
name: "fetch_data",
// ...
permissions: ["network:http", "network:websocket", "storage:write",
"models", "oauth", "functions"],
}

Users will see the full permission list and may decline to run a node that asks for too much.

If your node only does computation, don’t declare any permissions:

node! {
name: "parse_csv",
friendly_name: "Parse CSV",
description: "Parses CSV text into structured data",
category: "Data/Transform",
inputs: { exec: Exec, csv_text: String },
outputs: { exec_out: Exec, rows: Json },
}

This node will show “No additional permissions requested” in the UI, which builds user trust.

Even if a node doesn’t declare a permission, the sandbox enforces restrictions. A node that tries to make an HTTP call without network:http will get an error, not silent access. Permissions are a contract between the node and the runtime.


Can a WASM node access my clipboard, camera, or microphone? No. The sandbox has no OS peripheral access.

Can a WASM node read other nodes’ data? No. Each node only sees its own input pins and memory.

Can a malicious node mine cryptocurrency? It could try to use CPU, but the execution time limit will terminate it quickly. And it has no network access to submit results unless network:http is granted.

What happens if I trust a package and it updates? Your trust is per package ID — if the same ID ships a new version, your trust persists. Review the changelog of packages you update.

Can I revoke trust? Yes. Clear localStorage entries starting with wasm-consent-package- in your browser devtools, or clear app data in the desktop app.

Are permissions the same as capabilities in the manifest? The manifest (flow-like.toml) previously held a static [permissions] section. This has been superseded by per-node declarations in code. The manifest capabilities (memory limits, timeouts) are still respected and layered on top.