Skip to content

Content safety & uploads

How FlowRunner keeps untrusted content — rich text you paste, files your visitors attach, and data your public forms receive — from turning into an attack on you, your team, or your visitors.

A no-code builder deals in content it didn't write: text pasted from the web, HTML in a display block, files uploaded by strangers, form fields filled in by bots. This page describes the specific defenses the code enforces at each of those boundaries.

HTML is sanitized at every boundary

Rich text (headings, buttons, links, alerts, typography) can contain HTML. Every string that reaches the DOM or a generated export passes through one central sanitizer, sanitizeHtml, built on DOMPurify. There is deliberately no "trusted HTML" path — nothing skips it.

The sanitizer applies a strict allowlist:

RuleEffect
Allowed tagsFormatting only — b, i, a, p, ul/ol/li, h1h6, code, pre, etc.
Allowed attributeshref, target, rel only — every on* event handler is stripped.
Allowed URLshttp(s):, mailto:, tel:, #, / only — javascript: and data: links are removed.
Link hardeningEvery <a> gets rel="noopener noreferrer" so a new tab can't reach window.opener.

Because sanitizing happens at both the render boundary (preview and published form) and the export boundary (the Next.js and React Native code you download), a malicious <script> stored in a project can never execute — this blocks stored XSS even for content saved before you owned the workspace.

NOTE

The old Raw HTML block ("HTML / Code") is deprecated and hidden from the palette — it was the widest XSS surface for non-technical builders. Existing documents that still contain it keep rendering, but its content is sanitized on the way out just like everything else, and you can no longer add a new one.

File uploads: public in, member-gated out

Form visitors aren't signed in, so the upload endpoint has to be public. It is fenced on three sides:

  • Rate-limited per client IP (30 uploads/minute), returning 429 with Retry-After on a burst.
  • Size-capped at 10 MB per file — a larger file is rejected with 413 and nothing is stored.
  • Project must exist — uploads are refused unless they target a real, non-deleted project.

Getting a file back out is a different story. The download endpoint requires you to be signed in and a member (viewer or above) of the file's project. Even then, bytes are served as an attachment with Content-Disposition: attachment, X-Content-Type-Options: nosniff, and Cache-Control: private, no-store. That combination means an uploaded HTML or SVG file can't execute in the app's origin — the browser downloads it instead of rendering it, and won't MIME-sniff it into something executable. See File uploads for the visitor-facing behavior.

Public form submissions

The submission receiver is public too, and defends the same way plus two extra checks:

  • Honeypot — a hidden _hp field a human never sees. When a bot fills it, the server responds 201 OK (so the bot doesn't learn to adapt) but silently drops the submission.
  • Server-side required-field validation — client-side validation is bypassable, so the server independently re-checks that every required field is present and rejects with 400 and the list of missing names. See Validation.
  • Plus per-IP rate limiting and a size cap (~100 KB per submission).

Flow HTTP endpoints fail closed

Flows exposed over HTTP authenticate with fail-closed logic. If an auth type is configured but its expected secret is empty or an unresolved ${secrets.X}, every request is rejected — an unset secret can never accidentally allow anonymous access. Credentials are compared with a constant-time check (timingSafeEqual), so an attacker can't recover a token by measuring response times. See HTTP triggers.

WARNING

Server-side flow execution is not sandboxed yet. Only first-party, built-in nodes run in the engine today; running untrusted third-party node logic server-side is a design goal, not a current guarantee. Until that lands, only install nodes you trust. See Sandboxing.

See also

FlowRunner — the no-code platform for small businesses.