persistent-ai

@persistent-ai/fireflow-sandbox (0.26.2)

Published 2026-08-10 15:29:09 +00:00 by ak

Installation

@persistent-ai:registry=
npm install @persistent-ai/fireflow-sandbox@0.26.2
"@persistent-ai/fireflow-sandbox": "0.26.2"

About this package

@persistent-ai/fireflow-sandbox

Isolated container environments for FireFlow — code execution, testing, and deployment inside ephemeral sandboxes, brokered through DBOS durable workflows.

What It Is

This package gives FireFlow flows and LLM agents a safe place to run untrusted or generated code. Each sandbox is an ephemeral, isolated container provisioned through the OpenSandbox runtime. A flow can create a container, run commands in it, stream output, move files in and out of the VFS, deliver secrets, reach services on container ports over HTTP/WebSocket, and tear the container down — all as durable operations that survive worker crashes.

Sandboxes are owner-bound: every operation is checked against the fireflow_sandbox_instances record, and only the caller who created a sandbox may act on it. Containers carry a TTL and are tracked by a durable watcher workflow that keeps their lifecycle status in sync.

Key properties:

  • Durable — every sandbox operation runs as DBOS steps with automatic crash recovery
  • Owner-only access — ACL check is the first step of every operation
  • Path-scoped — optional deny-first access rules restrict read / readwrite / execute by container path
  • Secret-safe.ffsecret references are resolved late, in the node, via ECDH; plaintext never crosses a DBOS boundary
  • Observable — operations emit events to a DBOS stream; long-running commands stream stdout/stderr to PortStream
  • Composable — nodes use @Passthrough() ports so sandboxId, access rules, and endpoint config chain across a flow

How It Works

Operation pattern

Sandbox operations are exposed to nodes via ISandboxContextService (context.services.sandbox), implemented by SandboxContextService. Every method follows the same three-phase shape, each phase wrapped in its own DBOS step so it is checkpointed and replay-safe:

Step 1: ACL check     — load sandbox_instances row, verify caller is owner,
                        optionally enforce path access rules
Step 2: Operation     — call the OpenSandbox SDK (create / run / pause / sync / ...)
Step 3: Event emit    — update the DB record + DBOS.writeStream(sandbox-events)

Short-lived operations (create, execute, kill, pause, resume, renew, getStatus, resolveEndpoint, httpRequest, writeSecretFile) are awaited inline. Long-running operations (executeStream, syncFromVFS, syncToVFS) return an ISandboxWorkflowHandle ({ workflowId, getResult, getStatus }) so a node can fire-and-forget or await completion. Their output is written to a DBOS stream as it is produced and the stream is closed at workflow level on completion.

The steps run inside the current execution workflow (so they share its DBOS context and stream lifetime). A dedicated fireflow-sandbox WorkflowQueue is created at module level — before DBOS.launch() — so a future deployment can route sandbox operations to specialized sandbox workers. The SandboxWorkflows class provides the same operations as standalone @DBOS.workflow() methods for that queue.

Lifecycle watcher

SandboxContextService.create() starts a SandboxWatcherWorkflow child workflow with the deterministic ID sandbox-watcher:${sandboxId}. It polls the container state every 10s inside a single DBOS step (the loop, including its delays, lives in one step — on restart DBOS re-runs the step fresh rather than replaying each poll) and syncs status / terminatedAt on the sandbox_instances row, stopping on any terminal state or when the container disappears.

Access control

checkSandboxACL() is the first step of every operation: it loads the sandbox record, rejects unknown / non-owner / terminated sandboxes. On top of ownership, checkSandboxPathAccess() enforces optional deny-first path rules with three access levels — read, readwrite, execute. Empty rules mean "allow all" (backward compatible); once any rule is set, access defaults to deny unless an allow rule matches the path prefix and level.

Secret delivery

resolveSecretMappingsToEnvs() resolves an array of .ffsecret VFS references to plaintext environment variables using the same ECDH flow as the vault secret nodes: read the .ffsecret file → request the secret from the vault with a per-batch ECDH public key → vault re-encrypts to that key pair → decrypt locally → extract the field. This must run in node.execute(context), where the ExecutionContext (and its ECDH key pair) exists; the resolved env map is then handed to the sandbox service via an in-memory call, never serialized across a DBOS boundary. Secret scope can be the caller or the flowOwner. SandboxWriteSecretFileNode writes a resolved secret to a file inside the container for credential-file workflows.

Endpoint proxying

resolveEndpoint() and httpRequest() resolve a container port to its OpenSandbox endpoint URL (with routing headers) behind the ACL check — the raw endpoint URL is never exposed to the flow graph. The HTTP/WebSocket nodes use this to talk to services running inside a sandbox. Owner-authenticated browser access to those ports (OAuth/PKCE) is provided separately by the proxy mounted on fireflow-execution-api; this package contributes the SandboxProxyConfig type and the proxy_config DB column that drive it.

Package Exports

The package ships client-safe and server-only entry points. Importing the server entry registers the sandbox nodes via their @Node() decorators.

Export Environment Contents
@persistent-ai/fireflow-sandbox Client + Server Type definitions, access-rule schema, event types, config schemas
@persistent-ai/fireflow-sandbox/server Server only Everything: DB schema, nodes, services, steps, types, workflows (triggers node registration)
@persistent-ai/fireflow-sandbox/nodes Server only The 15 sandbox node classes (importing registers them)
@persistent-ai/fireflow-sandbox/db Server only sandboxInstances Drizzle table + SandboxInstanceRow / SandboxInstanceInsert types
@persistent-ai/fireflow-sandbox/types Client + Server All type / schema definitions (re-exported from the root entry)
// Frontend / shared — types and schemas only
import { SandboxAccessRule, type SandboxEventPayload } from '@persistent-ai/fireflow-sandbox'

// Backend — full surface (services, workflows, steps, nodes)
import { SandboxContextService, SandboxWorkflows, checkSandboxACL } from '@persistent-ai/fireflow-sandbox/server'

// Schema sharing (executor / trpc re-export the table from here)
import { sandboxInstances } from '@persistent-ai/fireflow-sandbox/db'

Nodes

15 nodes in the sandbox category. Importing /server or /nodes registers them in the NodeRegistry; all are marked execution: { dbos: true }.

Node Purpose
SandboxCreateNode Provision a container (image, resources, users, env, access rules, secrets, network policy, proxy config) → returns sandboxId
SandboxExecuteNode Run a command and return stdout / stderr / exit code (blocking)
SandboxExecuteStreamNode Run a long-lived command, streaming stdout/stderr/exit chunks to a PortStream
SandboxKillNode Terminate the container
SandboxPauseNode Pause a running container
SandboxResumeNode Resume a paused container
SandboxRenewNode Extend the container TTL
SandboxStatusNode Read-only liveness / state / optional metrics (works on terminated sandboxes too)
SandboxSyncFromVFSNode Transfer files VFS → container (filters, gitignore, empty-dir handling)
SandboxSyncToVFSNode Transfer files container → VFS (globs, exclude patterns, mirror mode, optional commit)
SandboxWriteSecretFileNode Resolve a .ffsecret and write it as a file inside the container
SandboxHttpRequestNode Authenticated HTTP request to a container port (endpoint resolution hidden)
SandboxHttpStreamNode Streaming HTTP response from a container port
SandboxWebSocketNode WebSocket connection to a container port
SandboxStreamSplitterNode Split a sandbox output stream into separate stdout / stderr / exit channels

Database

One table, fireflow_sandbox_instances (Drizzle, fireflow_ prefix), defined in src/db/schema.ts. It is the ownership / ACL entity for every sandbox operation.

Column Type Notes
id text PK Sandbox ID (from OpenSandbox)
owner_id text Caller who created the sandbox — checked on every operation
execution_id / workflow_id text Originating execution / workflow
image text Container image
status text creating / running / paused / terminated
visibility text private (default)
workspace_id text Associated VFS workspace
access_rules jsonb SandboxAccessRule[]
sandbox_users / default_uid jsonb / int Per-command uid/gid isolation
metadata / resource_spec jsonb Tags, CPU/memory spec
proxy_config jsonb { defaultPort?, exposedPorts? } for the endpoint proxy
created_at / expires_at / terminated_at timestamp TTL + lifecycle tracking

Indexes: owner_id, execution_id, status.

Events

Operations write a SandboxEventPayload to the sandbox-events DBOS stream:

SANDBOX_CREATED, SANDBOX_KILLED, SANDBOX_PAUSED, SANDBOX_RESUMED, SANDBOX_RENEWED, COMMAND_COMPLETED, SYNC_FROM_VFS_COMPLETED, SYNC_TO_VFS_COMPLETED.

Integration with Flow Execution

The package plugs into fireflow-executor at three points:

  1. Node registration — the executor imports @persistent-ai/fireflow-sandbox/server (via serialization.ts) so the sandbox node classes register at startup.
  2. Context serviceExecutionWorkflows constructs new SandboxContextService(dbosService, db, callerId, vfs, vfsWrite, sandboxWatcher) and injects it as context.services.sandbox, so sandbox nodes can broker operations using the flow's DBOS context, VFS access, and the caller identity for ACL.
  3. Watcher + queue wiringServiceFactory creates the singleton SandboxWatcherWorkflow (a DBOS ConfiguredInstance) and the executor's queue.ts imports the module-level sandboxQueue, both before DBOS.launch().

The sandbox_instances table is also re-exported through fireflow-trpc's Postgres schema so migrations and the rest of the platform share a single definition.

Configuration

OpenSandbox connection settings are read from the environment by createConnectionConfig() (cached per worker process):

Variable Default Description
OPENSANDBOX_DOMAIN localhost:8080 OpenSandbox API host
OPENSANDBOX_API_KEY (none) API key (optional)
OPENSANDBOX_PROTOCOL http http or https
OPENSANDBOX_USE_SERVER_PROXY false Route SDK calls through the server proxy
OPENSANDBOX_REQUEST_TIMEOUT 30 Request timeout (seconds)

Sandbox queue concurrency:

Variable Default Description
DBOS_SANDBOX_QUEUE_CONCURRENCY 30 Global concurrency across workers for the sandbox queue
DBOS_SANDBOX_WORKER_CONCURRENCY 3 Per-worker concurrency

Directory Structure

src/
├── index.ts        # Client-safe exports (types)
├── server.ts       # Server-only barrel (db, nodes, services, steps, types, workflows)
├── db/             # sandbox_instances Drizzle schema
├── types/          # Access rules, events, config schemas (resource/network/proxy/secrets/...)
├── nodes/          # 15 sandbox node classes
├── services/       # SandboxContextService + OpenSandbox connection config
├── steps/          # SDK calls, ACL, file transfer, glob utils, secret resolution
└── workflows/      # SandboxWorkflows, SandboxWatcherWorkflow, sandbox queue

Development

pnpm --filter @persistent-ai/fireflow-sandbox build       # Compile
pnpm --filter @persistent-ai/fireflow-sandbox typecheck    # Type check
pnpm --filter @persistent-ai/fireflow-sandbox dev          # tsc watch
pnpm --filter @persistent-ai/fireflow-sandbox test         # Vitest
pnpm --filter @persistent-ai/fireflow-sandbox test:coverage

Dependencies

Package Purpose
@alibaba-group/opensandbox Sandbox container runtime SDK
@dbos-inc/dbos-sdk Durable workflow execution
@persistent-ai/fireflow-types Node base classes, decorators, execution context, secret wrapping
ignore / minimatch Gitignore + glob filtering for file sync
nanoid Durable workflow ID generation
turndown HTML → Markdown for HTTP responses
ws WebSocket connections to container ports

Peer dependencies: drizzle-orm, pg, superjson.

License

Business Source License 1.1 (BUSL-1.1) — see LICENSE.txt.

Dependencies

Dependencies

ID Version
@alibaba-group/opensandbox ^0.1.5
@dbos-inc/dbos-sdk ^4.25.14
@persistent-ai/fireflow-types 0.26.2
ignore ^7.0.5
minimatch ^10.2.4
nanoid ^5.1.7
nanoid-dictionary ^5.0.0
turndown ^7.2.2
ws ^8.21.1

Development Dependencies

ID Version
@persistent-ai/typescript-config 0.26.2
@types/node ^25.5.0
@types/turndown ^5.0.6
@types/ws ^8.18.1
vitest ^4.1.0

Peer Dependencies

ID Version
drizzle-orm ^0.45.2
pg ^8.22.0
superjson ^2.2.6
Details
npm
2026-08-10 15:29:09 +00:00
1
BUSL-1.1
159 KiB
Assets (1)
Versions (5) View all
0.29.4 2026-08-18
0.29.1 2026-08-18
0.29.0 2026-08-18
0.28.0 2026-08-17
0.26.2 2026-08-10