Skip to content

Multi-Tenant Security

Shoehorn uses PostgreSQL Row-Level Security (RLS) to provide database-level tenant isolation. This ensures that tenants cannot access each other’s data, even if application-level bugs exist.

  1. Every data table has a tenant_id column
  2. RLS policies filter all SELECT, INSERT, UPDATE, and DELETE operations
  3. Before each query, the session variable app.current_tenant_id is set
  4. PostgreSQL automatically enforces tenant isolation at the database level
-- Example RLS policy
CREATE POLICY tenant_isolation_select ON entities
FOR SELECT
USING (tenant_id = current_setting('app.current_tenant_id')::text);

Shoehorn always uses a two-user model for database security. There is no single-user mode.

Terminal window
# Migration user - schema changes (BYPASSRLS)
MIGRATION_DATABASE_URL=postgresql://shoehorn_user:pass@host:5432/shoehorn
# Runtime user - application queries (NOBYPASSRLS)
DATABASE_URL=postgresql://app_user:pass@host:5432/shoehorn
UserPurposeCan Bypass RLS
shoehorn_userDatabase owner, schema migrationsYes
app_userAll runtime queriesNo (NOBYPASSRLS)

The app_user has NOBYPASSRLS, meaning RLS policies are always enforced. Even for single-tenant deployments, RLS runs with a fixed tenant ID injected by the middleware.

The tenant ID is extracted from the authenticated user’s JWT claims. It is never taken from client input.

StrategyConfigurationSource
provider_claimTENANT_ID_CLAIM_KEY=azpJWT claim value
urn_claimTENANT_ID_CLAIM_KEY=urn:zitadel:iam:org:idURN in JWT claim
group_prefixTENANT_GROUP_PREFIX=tenant:Group membership prefix
env_varDEFAULT_TENANT_ID=defaultEnvironment variable fallback

The tenant ID is stored alongside the API key in the database. When the key is validated, the tenant is loaded from the key record.

Every table storing user or tenant-specific data has RLS enabled:

  • catalog_entities - Service catalog
  • teams - Team definitions
  • team_members - Team membership
  • repositories - Repository data
  • governance_actions - Governance tracking
  • gitops_resources - GitOps state
  • k8s_cluster_instances - Kubernetes workloads
  • scorecard_definitions - Scorecard rules
  • api_keys - API key records
  • And all other tenant-scoped tables

The RLS model provides:

  • SOC 2 compliance: Database-level access control
  • HIPAA-ready: Data isolation at the storage layer
  • Audit trail: All data access is tenant-scoped
  • Defense in depth: Even application bugs cannot leak cross-tenant data