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 onlymanage_billing,manage_members: owner, adminmanage_apps,manage_schemas,run_engines,manage_keys: owner, admin, developerview_usage,view_audit: all roles
Enforcement helpers:
requireMinimumRole()for role thresholdsrequireSessionRole()andrequireSessionPermission()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_domainschecks 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
enforceSsois 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:
- System projects (
projects.isSystem = true) bypass domain cap checks in/api/track. - Entitlement tokens are cached in memory for the current session only; caches are not security boundaries and tokens are re-verified.
- Webhook secrets are encrypted at rest (AES-256-GCM) and only returned on creation; the database stores the encrypted value.
- The
org_sso_configs.clientSecretcolumn is labeled "Encrypted!" but encryption is not visible in code. - 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.