Appearance
Security overview
FlowRunner is a multi-tenant platform, so its security is layered: authentication, per-workspace roles, an encrypted secrets vault, content sanitization, hardened public endpoints, and a component sandbox all sit between a visitor and your data. This page maps those layers and is honest about what is fully enforced today versus what is a signed foundation still being built out.
The trust boundaries
Everything the browser sends is untrusted. The app server authenticates it, checks the caller's role, scopes every query to a workspace, and only then touches MongoDB. Component config UIs run in a sandboxed iframe that can talk to the host only through a permission-gated message bridge.
The seven layers
1 — Component / node sandbox. A node's configuration UI loads in an <iframe sandbox> and reaches the host only via a postMessage RPC bridge. Each call is gated by a declared permission (project.read, project.write, network.declared), and network calls must match the manifest's origin allowlist. Two honest caveats: the sandbox covers the config UI only — node logic itself runs server-side in the engine, which today executes first-party built-in nodes only (there is no server-side sandbox for untrusted third-party code yet); and true per-plugin isolation assumes per-origin hosting. Production currently serves the built-in config UIs from one trusted origin, which is fine because they are first-party — real third-party isolation needs wildcard-subdomain, per-origin hosting. See /security/sandbox and /api/node-authoring.
2 — Authentication + sessions. Sign-in uses Auth.js v5 with a JWT session strategy; passwords are verified with bcrypt, and a per-email lockout throttles brute force (8 attempts / 15 min). Edge middleware guards the whole /api surface, and each handler re-checks auth() as defense-in-depth. See /security/auth.
3 — Workspaces, roles + per-route authz. The tenant unit is a workspace. Membership carries a role — owner > admin > editor > viewer — and every route calls requireWorkspaceRole / requireProjectRole to assert a minimum. Security-relevant actions land in a per-workspace audit log. See /security/access-control and /guide/workspaces.
4 — Encrypted secrets vault. Secrets are stored per workspace, encrypted at rest with AES-256-GCM under a 32-byte SECRETS_KEY. In flows, ${secrets.X} resolves from the vault — never from process.env — so credentials never live in a project document. Without the key configured, the vault is simply unavailable (fail-safe, not silently plaintext). See /security/secrets and /triggers/secrets.
5 — Content safety + gated uploads. All rich text passes through a central DOMPurify sanitizer at every render and export boundary — a strict tag allowlist, javascript:/data: URLs blocked, rel="noopener" forced; the raw-HTML block is off the palette. File uploads are public but rate-limited and capped at 10 MB, stored in GridFS, and served back only to project members as a nosniff attachment. See /security/content-safety and /data/uploads.
6 — Hardened public endpoints. A flow's HTTP trigger enforces fail-closed, constant-time auth (bearer / basic / API key via timingSafeEqual): an unset expected secret rejects every request rather than letting anything through. Public form submits add a honeypot and server-side required-field validation, and Mongo-backed rate limiting covers sign-in, waitlist, invites, runs, and submits. See /triggers/http and /data/submissions.
7 — Tenant data isolation. Every query is scoped by workspaceId; secrets, audit logs, submissions, and uploads are all partitioned per workspace and gated to members, so one tenant can never read another's data. See /security/access-control and /deploy/env.
NOTE
Third-party trust chain is signed but evolving. Marketplace manifests can be Ed25519-signed and verified (tamper-evidence), but publisher accounts, a registry, SRI, per-plugin CSP, and a server-side execution sandbox are still being built. Until they ship, only first-party built-in nodes run in the engine.
TIP
Self-hosting? /security/self-hosting covers the environment variables (AUTH_SECRET, SECRETS_KEY, CRON_SECRET) that turn these layers on.