Security

Generated code runs nowhere near your account.

Sites built here are user-authored code, so the only question that matters is what origin it executes in. This page is the answer, in enough detail to check.

One origin per site

A preview is served from p-<id>.<sandbox>, a published site from <slug>.<sandbox> — never from this domain.

The app is unreachable there

Enforced in both directions: our routes 404 on a sandbox host, and generated paths 404 on ours.

Credentials are encrypted at rest

Provider keys, OAuth tokens and secret environment variables are AES-256-GCM, and secrets never reach a browser.

A transcript read is on the record

It requires a stated reason and writes an audit row before returning anything, so the log cannot be emptied by failing.

Isolation is the hostname

Every generated site gets its own origin. A preview is served at p-<projectId>.<sandbox> and a published site at <slug>.<sandbox>, where the sandbox domain is never this application’s own. One shared sandbox host would let any generated site read every other site’s localStorage — and therefore its Supabase session — so the per-site hostname is the property everything else rests on.

Three rules hold it up, and all three are enforced rather than documented:

  • The application is unreachable on a sandbox host. Without this, a generated page could call /api/… on its own origin and receive an authenticated answer.
  • Generated paths are refused on the application host. Without this, generated JavaScript would execute on the origin holding your session cookie.
  • A preview token and the subdomain must name the same project.A valid token replayed on another project’s subdomain would put one project’s code inside another’s origin, which is exactly the leak the first rule exists to prevent.

This replaced an earlier design that ran generated sites in an opaque origin. That worked and had a cost: storage is unavailable in an opaque origin, so a generated app could not use Supabase auth at all. Per-site hostnames fixed that without weakening anything.

Our own pages carry a separate, stricter policy

Application pages are served with a nonce-based Content Security Policy built per request, and there is no external script host— not for analytics, not for the code editor, not for the payment provider’s checkout. Monaco is copied into our own origin rather than loaded from a CDN, which is what makes a nonce plus strict-dynamic the whole policy rather than a policy with a hole in it.

Two consequences worth naming, because both are trade-offs we accepted. Analytics is measured server-side, so we lose scroll depth and session replay and gain not loading a third-party bundle into a page holding your session. And card details are handled entirely on the payment provider’s own hosted page, so nothing sensitive ever touches this origin.

Your credentials

  • Provider and integration keys — AES-256-GCM encrypted at rest. Shown to you only as a hint (last four characters); we cannot display the original.
  • Environment variables— split by whether they may reach a browser. A generated site runs entirely in the visitor’s browser, so a “public” variable is compiled into a file the whole internet can download. We refuse to store a credential-shaped value as public rather than warning about it.
  • Passwords — hashed, never recoverable. Sign in with Google and we store no password at all.
  • Your Supabase project — created in your own organisation. We hold a token to apply the migrations you approve; you can revoke it at any time and keep the database.

Row Level Security is not optional

Every table the build engine creates must enable Row Level Security and declare a policy in the same statement. A migration that omits either is refused, not flagged — the most common way generated backends leak data is a table that was created correctly and secured “later”.

Who can read your conversations

Operators of this instance can, and rather than claim otherwise, here is exactly what that costs them. Access is by named capability rather than an admin flag, and the supportrole deliberately cannot read a conversation at all — so for most people who work on this product, the answer to “can they read my messages” is no.

For the roles that can, a read requires a stated reason and writes an audit row beforethe transcript is returned, so a read that fails part-way is still recorded. Granting roles re-checks the database rather than trusting the session token, so an operator removed this morning cannot act on a token minted yesterday. None of this stops a determined operator — nothing inside an application can — but it turns “who has read my messages” from unanswerable into a query.

Anywhere we fetch a URL you chose

The build engine can fetch a page, and you can connect an external tool server. Both make our server request an address supplied through user input, which is the shape of an SSRF — and the private address an attacker wants is usually our own network. Both paths go through one shared private-address check rather than an inline test per call site.

What we do not claim

  • We are not certified against SOC 2 or ISO 27001. If you need an audited attestation, we are not there yet and would rather say so.
  • Generated code is reviewed by rules, not by a person. It is written to enforced skills and audited in the running page — but you should read it, which is the entire point of being handed it.
  • Credit figures are estimates from a local copy of provider prices; the provider’s own billing is the source of truth.

Reporting something

If you have found a vulnerability, tell us through the contact page and mark it as security. We will not pursue anybody who reports a genuine issue in good faith and gives us a reasonable chance to fix it before publishing.

Read the code it writes.

Every claim on this page is about how your project is isolated and stored. The best way to judge the rest is to build something and open the files.