The security model

Most security incidents on multi-tenant platforms are not exotic. They are one tenant reaching another’s data, one missing check on one endpoint, one piece of user content that turns out to be executable. CTXR is designed so that the common failure modes are structurally impossible rather than carefully avoided. This page explains how.

A space is a boundary

A space is a directory, and everything inside it belongs to that space alone. There is no shared database, no global user table, and no query that spans spaces. The boundary is physical, not a WHERE clause that someone could forget — the isolation-by-default principle from Foundations, here enforced.

This matters most at the front door. When a request arrives, the platform resolves which space it belongs to from the subdomain, before anything else runs. There is no window in which a request exists without a space attached, and therefore no “what if someone crafts a request that crosses tenants” — the request handler has already decided whose space this is by the time any logic executes. Because a space is just a directory, this also means an editor or key from one space is simply unknown to another, and error messages are written so they don’t reveal whether content in another space exists. The fact that everything is file-based is what makes this the foundation every other protection sits on.

This isn’t only about spaces. The platform serves three kinds of host — the landing page, the management modules (platform, account, onboard), and the spaces themselves — and every one is resolved from its subdomain into its own isolated directory before any logic runs. An account app or a management module is a boundary in exactly the way a space is; there is no privileged back channel between them, and the management domains pass through the same hardening described below.

Layered authorization

Security at CTXR is a sandwich, not a single lock. Authentication establishes who you are. Permissions decide what you may do. Property protection governs which fields you may touch. These gates are independent and checked separately, so breaking one is not enough to get through — an attacker who somehow forged a session still faces the permission check, and an actor with the right permission still cannot write protected fields.

The first two layers — passwordless sign-in, the nine atomic permissions, the role presets, and the single symmetric actor model — have their own page: Access & permissions. The third layer, property protection, is below.

Content that cannot be tampered with

Every node records who created it and who last edited it. These are set by the platform, never by the submitter. On every write path the incoming data is stripped of @-prefixed and internal fields, and of creator and editor values, before any logic runs — so a crafted request cannot impersonate another actor or backdate content. The creator and editor fields are also removed from public output, preventing actor enumeration.

Rich text is safe by construction rather than by sanitizing. Editors produce a structured document model, and the platform renders it through a trusted generator. There is no point at which raw HTML from a user is trusted and then scrubbed — the dangerous path simply does not exist, which closes off the most common cross-site-scripting vector in content platforms.

A hardened API

The editing API is locked down regardless of how a space is configured. Cross-origin requests are accepted only from the platform’s own domain family; everything else is rejected by the browser before it reaches your space. Request bodies are size-bounded and rate-limited. Uploads are restricted to a curated whitelist of image, audio, video, and document types, and SVG is blocked outright because it can carry executable script.

These protections are always on. A multi-tenant platform cannot rely on every space owner configuring security correctly, so the defaults are the safe ones and there is no switch that turns them off.

Making a whole space private

By default a space’s live content is public. Turning on view gating in the space’s settings flips that: every live page then requires the view permission, so only signed-in actors can see anything. This turns a space into a private site — useful for internal documentation, a client portal, or a staging environment that isn’t ready for the world. View gating governs pages; raw uploaded files remain reachable by URL (see Privacy and GDPR).

What CTXR deliberately does not do

Being honest about the edges matters as much as listing the protections. A few things are out of scope by design rather than by oversight:

  • No encryption at rest of stored files. Isolation, edge gating and minimal stored data carry the load; a key-management layer would add operational weight without changing the threat model for a content platform. Don’t treat uploads as a secure vault.
  • No per-node version history. A node has a draft and a live version, not a full revision log. Soft-delete gives you a window to recover a deletion, but it is not an audit of every past edit.
  • No second factor beyond email. The platform does not layer its own MFA on top of the email code, which is why hardening the editor’s mailbox is the controller’s job (covered under Privacy and GDPR).

Knowing where the line sits lets you put the right compensating control on the other side of it.