Skip to main content

Tenancy and Isolation

This page documents the tenancy model and access controls as implemented in the current codebase. It is not a formal security audit.


Organization Isolation

Projects are owned by organizations and scoped by orgId. Access checks typically use:

  • requireOrgAccess() to verify org existence and membership.
  • requireProjectAccess() to verify a project belongs to an org and that the user is a member.

No evidence found of cross-org joins in these helpers, but isolation depends on routes calling them consistently.


Role-Based Access Control

Roles are hierarchical: owner > admin > developer > viewer.

Permissions (from rbac-definitions.ts):

  • delete_org: owner only
  • manage_billing, manage_members: owner, admin
  • manage_apps, manage_schemas, run_engines, manage_keys: owner, admin, developer
  • view_usage, view_audit: all roles

Enforcement helpers:

  • requireMinimumRole() for role thresholds
  • requireSessionRole() and requireSessionPermission() for session-scoped checks

RBAC is only as strong as its usage in routes. UI restrictions are not security boundaries.


Key Scoping

API keys are project-scoped and stored in api_keys.

Publishable keys (pk_):

  • Intended for browser usage
  • Domain-locked via project_domains checks during license verification or /api/track
  • Unregistered domains may be downgraded to Free tier or rejected if domain caps are exceeded

Secret keys (sk_):

  • Intended for server/headless usage
  • Not domain-locked
  • Must be kept server-side

SSO Enforcement

SSO enforcement is driven by org_sso_configs:

  • When enforceSso is true for a verified domain, non-SSO login methods are blocked.
  • validateAuthMethod() enforces this for magic link and OAuth flows.
  • SSO handover tokens (purpose = SSO_HANDOVER) are minted server-side in the SSO callback and allow session handover after IdP authentication.

Known Edge Cases

The following behaviors are present in the current codebase and should be understood as limitations:

  1. System projects (projects.isSystem = true) bypass domain cap checks in /api/track.
  2. Entitlement tokens are cached in memory for the current session only; caches are not security boundaries and tokens are re-verified.
  3. Webhook secrets are encrypted at rest (AES-256-GCM) and only returned on creation; the database stores the encrypted value.
  4. The org_sso_configs.clientSecret column is labeled "Encrypted!" but encryption is not visible in code.
  5. Transform DSL supports row filtering, but the import pipeline rejects row-count changes; behavior may differ if you use lower-level transform APIs directly.

What This Page Does Not Guarantee

  • This is not a security certification or penetration test.
  • Route-level isolation depends on consistent use of authz helpers.
  • Custom integrations can bypass server enforcement if they do not use these checks.