Malla runs untrusted content, user formulas and visitor input next to customer secrets. These are the boundaries that keep them apart.
Two populations, never mixed
Editor users and site visitors have separate tables, separate session cookies and separate signing keys. Editor sessions are HTTP-only, Secure, SameSite=Lax cookies with CSRF checks; passwords are hashed with scrypt. Visitor sessions are scoped to the customer’s domain and live in the site’s own database.
Authorization
- Every api route checks the caller’s role on the site; unknown and forbidden sites both answer 404.
- Production publishes, slug changes, domains and integration writes need an admin.
- Realtime rooms verify a short-lived token and the page’s site; viewers are read-only and content editors may only change text.
- Realtime validates every update against the schemas before storing or relaying it.
Expressions
No eval and no new Function. The interpreter rejects window, document, globalThis, constructor, __proto__ and prototype, has no assignment or loops, and caps source length, depth, evaluated nodes, string and array sizes. The same interpreter evaluates row rules and page access rules on the server.
The browser is untrusted
Client workflows and bindings can be read and changed by visitors, so they never hold authority. Page access is enforced on the server, client queries of gated pages pass the same gate, and writes go through server workflows and row rules. In rules, an anonymous user has id: NaN so ownership checks never match ownerless rows.
Outbound requests
Integrations reach only their declared hosts. Server fetches require HTTPS, resolve hostnames over DNS-over-HTTPS and block private, loopback and link-local answers (redirects re-checked), and cap time and response size. runJs runs in a Worker Loader isolate with no bindings and no secrets.
Styles and HTML
Style values that could break out of a rule are rejected; property names are validated. The renderer escapes text and sanitizes URLs. Rich text is stored as structured JSON, never raw HTML. Custom site code runs only on published sites, never in the editor.
Abuse protection
Forms use Turnstile, a honeypot and per-IP and per-form limits, and store IPs hashed. Public endpoints, client queries, login and sign-up use Cloudflare rate limiting.