Skip to content

Tenant Isolation

Bridge Town is a multi-tenant platform. Every piece of data — models, data sources, tokens, audit logs — belongs to exactly one tenant. Tenants cannot see or access each other’s data.

PostgreSQL Row-Level Security enforces isolation at the database level. Every request sets the tenant context before executing any queries:

SET LOCAL app.current_tenant_id = '<tenant-uuid>';

RLS policies on every table filter rows to only those matching the current tenant. This means:

  • A SQL injection cannot access another tenant’s data
  • Application bugs cannot leak cross-tenant data
  • Even raw database queries are scoped
  1. Request arrives with a Bearer token (JWT or API token)
  2. Auth middleware validates the token and extracts tenant_id and user_id
  3. Database session is opened with SET LOCAL app.current_tenant_id
  4. Route handler executes — all queries are automatically scoped
  5. Session closes — tenant context is cleared

Bridge Town supports two authentication methods:

MethodFormatUse case
Auth0 JWTeyJ... (contains .)Web UI sessions
API tokenbtk_... (argon2id hashed)MCP clients, CLI, CI/CD

Both methods resolve to the same (tenant_id, user_id) pair at authentication time. The auth middleware auto-detects the format.

For MCP tool calls specifically, an authenticated Auth0 JWT session may additionally resolve to a different (tenant_id, user_id) pair per call: a caller with active memberships in more than one workspace can pass a workspace_id argument to a tool to run that one call against another workspace they belong to (see docs/mcp-workspace-aware-access-prd.md in the repo for the full contract). Each such call still opens its database session with SET LOCAL app.current_tenant_id scoped to the target workspace, so RLS isolation is unchanged — only the workspace being targeted moves. API tokens never get this: a token’s (tenant_id, user_id) pair is fixed to the workspace it was issued in for every call.

Each tenant’s models are stored in a dedicated namespace within the internal model storage layer. All model storage operations are scoped to the authenticated tenant’s namespace — cross-tenant access at the storage layer is not possible.

Model execution runs in Docker containers with --network none. Containers are created per-run and destroyed after execution. No persistent state, no network access, no cross-tenant contamination.

Bridge Town does not invoke server-side language models. You connect your own AI agent (Claude, Claude Code, Codex, or any MCP-compatible client) to Bridge Town using your own model provider account.

Your agent calls Bridge Town’s MCP tools on your behalf. The language model that interprets your instructions runs inside your agent’s session — Bridge Town never sees your prompts or model reasoning. Bridge Town stores only the structured inputs and outputs of MCP tool calls, model files, run outputs, data-source snapshots, and audit events.

This architecture means:

  • Your prompts are governed solely by your model provider’s privacy policy.
  • Bridge Town has zero visibility into your agent’s reasoning or conversation history.
  • Tenant isolation at the data layer applies to all MCP tool outputs and stored artifacts.

See the data path reference for the canonical explanation of what Bridge Town receives and stores in an agent session.