> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenant isolation

> The four boundaries between workspaces — runtime, database, URL, config — and what enforces each one.

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="SECURITY" />

gavAI is multi-tenant, which means workspaces share a platform but not a process, a database, or a URL. This page documents the four boundaries that separate them, what enforces each one, and what behavior you can rely on when you put a production app on the platform. By the end you'll be able to name each layer of isolation and the exact response codes that signal a cross-workspace attempt was blocked.

If you're a security reviewer, the [boundaries table](#the-four-isolation-boundaries) is the architectural view. If you're a developer integrating, the [guarantees list](#what-you-can-rely-on-in-practice) is the operational view — these are the behaviors your code can depend on.

## The four isolation boundaries

Four distinct layers separate one workspace from another. Each layer is sufficient on its own to stop the most common cross-tenant attack; together they implement defense in depth.

<FeatureTable
  columns={[
{ key: "surface", label: "Surface" },
{ key: "mechanism", label: "Isolation mechanism" },
]}
  rows={[
{
  surface: "Runtime",
  mechanism: "Dedicated runtime instance per workspace — reads and writes only that workspace's data. No shared execution context between tenants.",
},
{
  surface: "Data",
  mechanism: "Separate managed Postgres database per workspace. There is no shared database or connection pool that crosses workspace boundaries.",
},
{
  surface: "URL",
  mechanism: "Each workspace is served at a customer-branded subdomain (`{slug}.gavai.app`) or a custom CNAME you attach via the Domains API.",
},
{
  surface: "Config",
  mechanism: "Theme, enabled capabilities, auth settings, and workflow definitions are scoped per workspace and never shared or inherited across workspaces.",
},
]}
/>

## What you can rely on in practice

These are the guarantees the platform enforces. State them this way: if any one of them ever fails, that's a security incident.

<ArrowList>
  <ArrowItem>A workspace's session token never grants access to another workspace's data. The runtime that handles a request can only reach that workspace's database and secrets.</ArrowItem>
  <ArrowItem>A workspace's API key is workspace-scoped at mint time. A `gak_live_*` key for workspace A cannot be used to read or write workspace B's resources — the API enforces this at the authorization layer before any data access occurs.</ArrowItem>
  <ArrowItem>`runQuery` enforces row-level access against the calling workspace's Postgres database only. There is no cross-database query path.</ArrowItem>
  <ArrowItem>Capability invocations — `collectPayment`, `sendEmail`, `storeFile`, and others — only reach the provisioned backends for the calling workspace. They cannot target another workspace's payment account, email sender, or object-store bucket.</ArrowItem>
</ArrowList>

## Cross-workspace operations

<Note>
  There is no cross-workspace API. If you operate multiple workspaces — for example, a parent organization with several child orgs — each workspace gets its own credentials and you orchestrate across them at the application layer. A request authenticated to workspace A cannot act on workspace B, regardless of how the token was minted.
</Note>

This is the design, not a limitation we'll lift later. Cross-tenant action paths are exactly what the four boundaries above are built to prevent. An "orchestrator" workspace that could reach into child workspaces would collapse the runtime boundary and the credential scoping at the same time.

## Verifying isolation empirically

You can confirm the isolation properties without inside knowledge of the platform. Issue a token scoped to workspace A. Attempt to call workspace B's resources. Every such attempt returns a `403` or `404` before any data is read.

<Tip>
  The response codes are deterministic, and they're chosen so neither one leaks information about the target workspace:
</Tip>

<ErrorTable
  errors={[
{ code: "403 insufficient_scope", cause: "Token is valid but scoped to a different workspace.", fix: "Mint a token under the workspace you're trying to call." },
{ code: "404 not_found", cause: "Resource path doesn't exist in the calling workspace's context.", fix: "Confirm the slug and ID. A 404 from a cross-tenant probe is indistinguishable from a 404 for a missing resource — by design." },
]}
/>

A `404` from a cross-tenant probe looks identical to a `404` for a resource that genuinely doesn't exist. You can't use the response to enumerate other workspaces or fingerprint their data.

## Perimeter controls at the runtime edge

Isolation is the inner boundary. Sitting in front of every workspace runtime is a per-tenant edge perimeter that the platform runs before any handler sees a request. These are the controls that shape what an attacker can even attempt before the four boundaries above kick in.

<ArrowList>
  <ArrowItem>**Per-token rate limits.** Every API token is metered independently. Bursts above the per-token ceiling return `429 rate_limited` with a `Retry-After` value; the limit is workspace-scoped so a noisy neighbor can never burn another workspace's budget.</ArrowItem>
  <ArrowItem>**Security headers on every response.** The runtime emits the standard hardening set — strict transport, content-type sniff protection, frame ancestor controls, referrer policy, and a Content Security Policy that scopes script execution to the workspace's allow-listed origins. Custom domains inherit the same policy under their own hostname.</ArrowItem>
  <ArrowItem>**SSRF classification on outbound fetches.** Capabilities that reach external URLs run the destination through an SSRF classifier before connecting. Loopback ranges, link-local addresses, internal CIDRs, and metadata endpoints are blocked at the perimeter; the call returns an `upstream_blocked` error rather than reaching the network.</ArrowItem>
  <ArrowItem>**CORS validation against allowed origins.** Each workspace declares its allowed embedding origins. Preflight requests and credentialed cross-origin calls are checked against that list and rejected if the origin is not declared — the runtime does not echo `Access-Control-Allow-Origin: *`.</ArrowItem>
  <ArrowItem>**CSRF protection for cookie-bound sessions.** End-user sessions use the standard `SameSite` + double-submit token pattern. Cross-site form submissions targeting a workspace's runtime are rejected before the handler runs.</ArrowItem>
  <ArrowItem>**Trusted Types enforcement.** The runtime serves a Trusted Types directive that prevents string-to-DOM sinks from executing untrusted markup, blocking a whole class of stored-XSS exploits even when an upstream component slips up.</ArrowItem>
</ArrowList>

## Account-level identity policy

Independent of the per-tenant runtime, the control-plane identity service enforces the policies that govern who can mint credentials for a workspace in the first place.

<ArrowList>
  <ArrowItem>**Password policy.** Minimum length and entropy thresholds are enforced at signup and on every password change. Reused or known-leaked passwords are rejected without leaking which condition matched.</ArrowItem>
  <ArrowItem>**MFA policy.** Workspaces can require MFA for sign-in and for elevated operations (token mint, domain attach, role change). Per-user one-time-password lockouts trigger on repeated incorrect codes and clear automatically after a cool-down.</ArrowItem>
  <ArrowItem>**Session lifetime.** Access sessions are short-lived; refresh tokens are single-use and rotate on every renewal. A session that has been idle past the workspace's configured inactivity window is invalidated server-side, not just client-side.</ArrowItem>
  <ArrowItem>**Strike counter on auth surfaces.** Authentication endpoints maintain a per-account strike counter. After too many failures in a short window the account is rate-limited at the perimeter — distinct from the per-token request limits — and the next attempt returns the same response shape whether the account exists or not.</ArrowItem>
</ArrowList>

## The named cross-tenant threats

These are the scenarios the boundaries are designed to stop. They are listed by name so a finding that matches one is unambiguous.

<ArrowList>
  <ArrowItem>**Cross-tenant data access.** A session authenticated to workspace A successfully reading or writing workspace B's data. This is the highest-severity bucket in the disclosure policy.</ArrowItem>
  <ArrowItem>**Vault ref resolution across workspaces.** A tokenized secret ref issued in workspace A resolving to a value when fetched by a request from workspace B.</ArrowItem>
  <ArrowItem>**Capability target hijack.** A capability invocation in workspace A reaching the provisioned backend of workspace B — sending email through B's email sender, charging B's payment account, writing to B's object-store bucket.</ArrowItem>
  <ArrowItem>**Session-fixation across workspaces.** A caller acting as a different workspace's user without valid credentials for that workspace.</ArrowItem>
</ArrowList>

## Reporting cross-tenant findings

<Warning>
  Cross-tenant access — a session authenticated to workspace A successfully reading or writing workspace B's data — is the highest-severity bucket in the disclosure policy. If you find a path that achieves this, stop immediately and email [security@gavai.io](mailto:security@gavai.io). Do not attempt to reproduce it beyond confirming the finding. Do not exfiltrate data from the second workspace. See [Reporting vulnerabilities](/security/disclosure) for the full process and response SLAs.
</Warning>

## Related

<Card title="Reporting vulnerabilities" icon="shield-exclamation" href="/security/disclosure">
  In-scope buckets, response SLA, the 90-day window, and where to send the report.
</Card>

<Card title="Scopes and least privilege" icon="key" href="/security/scopes">
  How tokens are scoped and how to mint a key that can only do one job.
</Card>
