Claude Code Security Patterns for Multi-Tenant SaaS Applications
Overview
Building secure multi-tenant SaaS applications with Claude Code requires careful architectural decisions around data isolation, authentication, authorization, and compliance. This guide provides comprehensive patterns for implementing tenant isolation, securing AI workloads, and maintaining compliance in production environments.
Multi-Tenant Architecture Models
1. Silo Model - Complete Isolation
interface SiloArchitecture {
// Each tenant gets dedicated resources
tenant: {
database: Database;
claudeApiKey: string;
computeInstance: ComputeResource;
storageAccount: StorageAccount;
};
}
class SiloTenantManager {
async provisionTenant(tenantId: string): Promise<TenantResources> {
// Create dedicated database
const database = await this.createDatabase(`db_${tenantId}`);
// Generate tenant-specific Claude API key
const apiKey = await this.generateTenantApiKey(tenantId);
// Provision isolated compute
const compute = await this.provisionCompute({
tenantId,
isolation: 'dedicated',
networkPolicy: 'isolated'
});
return {
database,
apiKey,
compute,
isolation: 'silo'
};
}
}2. Pool Model - Shared Resources
class PooledTenantManager {
private tenantContext: Map<string, TenantContext> = new Map();
async executeWithTenantContext<T>(
tenantId: string,
operation: (context: TenantContext) => Promise<T>
): Promise<T> {
const context = await this.getTenantContext(tenantId);
// Inject tenant context into all operations
return await this.withTenantIsolation(context, operation);
}
private async withTenantIsolation<T>(
context: TenantContext,
operation: (context: TenantContext) => Promise<T>
): Promise<T> {
// Set tenant context in async local storage
AsyncLocalStorage.run(context, async () => {
// Apply row-level security
await this.applyRowLevelSecurity(context.tenantId);
// Execute operation with tenant context
return await operation(context);
});
}
}3. Bridge Model - Hybrid Approach
class BridgeModelArchitecture {
async determineTenantIsolation(tenant: Tenant): Promise<IsolationStrategy> {
// Premium tenants get dedicated resources
if (tenant.tier === 'enterprise') {
return {
model: 'silo',
database: 'dedicated',
compute: 'dedicated',
claudeApi: 'dedicated-key'
};
}
// Standard tenants share resources with isolation
return {
model: 'pool',
database: 'shared-with-rls',
compute: 'shared-with-quotas',
claudeApi: 'shared-with-rate-limits'
};
}
}Data Isolation Strategies
1. Database-Per-Tenant Isolation
class DatabasePerTenantStrategy {
private connectionPools: Map<string, DatabasePool> = new Map();
async getConnection(tenantId: string): Promise<DatabaseConnection> {
if (!this.connectionPools.has(tenantId)) {
const pool = await this.createTenantPool(tenantId);
this.connectionPools.set(tenantId, pool);
}
return this.connectionPools.get(tenantId)!.getConnection();
}
private async createTenantPool(tenantId: string): Promise<DatabasePool> {
const dbName = `tenant_${tenantId}_db`;
return new DatabasePool({
host: process.env.DB_HOST,
database: dbName,
// Tenant-specific credentials
user: `tenant_${tenantId}_user`,
password: await this.getSecretForTenant(tenantId),
// Connection pool settings
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
}
}2. Schema-Per-Tenant with Row-Level Security
class SchemaPerTenantStrategy {
async setupTenantSchema(tenantId: string): Promise<void> {
const schemaName = `tenant_${tenantId}`;
await this.db.query(`
CREATE SCHEMA IF NOT EXISTS ${schemaName};
-- Create tenant-specific user
CREATE USER ${schemaName}_user WITH PASSWORD '${await this.generatePassword()}';
-- Grant schema access
GRANT USAGE ON SCHEMA ${schemaName} TO ${schemaName}_user;
GRANT CREATE ON SCHEMA ${schemaName} TO ${schemaName}_user;
-- Set default schema for user
ALTER USER ${schemaName}_user SET search_path TO ${schemaName};
`);
// Apply row-level security policies
await this.applyRLSPolicies(schemaName, tenantId);
}
private async applyRLSPolicies(schema: string, tenantId: string): Promise<void> {
// Enable RLS on all tables
const tables = await this.getTablesInSchema(schema);
for (const table of tables) {
await this.db.query(`
ALTER TABLE ${schema}.${table} ENABLE ROW LEVEL SECURITY;
-- Create policy for tenant isolation
CREATE POLICY tenant_isolation_policy ON ${schema}.${table}
FOR ALL
USING (tenant_id = current_setting('app.current_tenant')::uuid);
`);
}
}
}3. Shared Database with Tenant Context
class SharedDatabaseStrategy {
private tenantContext: AsyncLocalStorage<TenantContext>;
constructor() {
this.tenantContext = new AsyncLocalStorage();
}
async executeQuery<T>(
query: string,
params: any[],
tenantId: string
): Promise<T> {
return this.tenantContext.run({ tenantId }, async () => {
// Automatically inject tenant_id into all queries
const modifiedQuery = this.injectTenantFilter(query, tenantId);
// Add tenant_id to query parameters
const tenantParams = [...params, tenantId];
// Execute with tenant context
return await this.db.query(modifiedQuery, tenantParams);
});
}
private injectTenantFilter(query: string, tenantId: string): string {
// Parse and modify query to include tenant filter
const ast = parseSQL(query);
// Add WHERE tenant_id = ? to all SELECT/UPDATE/DELETE
if (ast.type === 'select' || ast.type === 'update' || ast.type === 'delete') {
ast.where = ast.where
? { type: 'and', left: ast.where, right: { type: 'eq', left: 'tenant_id', right: '?' } }
: { type: 'eq', left: 'tenant_id', right: '?' };
}
return generateSQL(ast);
}
}Claude Code Security Patterns
1. Tenant-Specific API Key Management
class TenantClaudeAPIManager {
private keyVault: KeyVault;
private rateLimiters: Map<string, RateLimiter> = new Map();
async getAPIKeyForTenant(tenantId: string): Promise<string> {
// Retrieve encrypted API key from vault
const encryptedKey = await this.keyVault.getSecret(`claude-api-${tenantId}`);
// Decrypt with tenant-specific key
const decryptionKey = await this.getTenantDecryptionKey(tenantId);
return this.decrypt(encryptedKey, decryptionKey);
}
async executeClaudeRequest(
tenantId: string,
request: ClaudeRequest
): Promise<ClaudeResponse> {
// Get tenant-specific API key
const apiKey = await this.getAPIKeyForTenant(tenantId);
// Apply tenant-specific rate limiting
const rateLimiter = this.getRateLimiterForTenant(tenantId);
await rateLimiter.checkLimit();
// Add tenant context to request
const enrichedRequest = {
...request,
headers: {
...request.headers,
'X-Tenant-ID': tenantId,
'X-API-Key': apiKey
},
metadata: {
tenantId,
timestamp: Date.now(),
requestId: generateRequestId()
}
};
// Execute with audit logging
return await this.executeWithAudit(enrichedRequest);
}
}2. Secure Prompt Isolation
class SecurePromptManager {
async buildTenantPrompt(
tenantId: string,
userPrompt: string,
context?: TenantContext
): Promise<SecurePrompt> {
// Validate and sanitize user input
const sanitizedPrompt = await this.sanitizePrompt(userPrompt);
// Get tenant-specific system prompt
const systemPrompt = await this.getTenantSystemPrompt(tenantId);
// Build secure prompt with isolation
return {
messages: [
{
role: 'system',
content: `${systemPrompt}\n\nTenant ID: ${tenantId}\nData Isolation: ENFORCED`
},
{
role: 'user',
content: sanitizedPrompt
}
],
security: {
tenantId,
dataAccessScope: context?.allowedResources || [],
prohibitedActions: ['cross-tenant-access', 'system-modification'],
maxTokens: this.getTenantTokenLimit(tenantId)
}
};
}
private async sanitizePrompt(prompt: string): Promise<string> {
// Remove potential injection attacks
const sanitized = prompt
.replace(/\{\{.*?\}\}/g, '') // Remove template injections
.replace(/system:/gi, '') // Remove system role attempts
.replace(/ignore previous/gi, ''); // Remove override attempts
// Validate against known attack patterns
if (await this.detectInjectionAttempt(sanitized)) {
throw new SecurityError('Potential injection detected');
}
return sanitized;
}
}3. Tenant Context Propagation
class TenantContextPropagator {
private contextStorage: AsyncLocalStorage<TenantContext>;
async propagateContext<T>(
tenantId: string,
operation: () => Promise<T>
): Promise<T> {
const context: TenantContext = {
tenantId,
userId: getCurrentUserId(),
permissions: await this.getUserPermissions(),
dataScope: await this.getTenantDataScope(tenantId),
auditInfo: {
requestId: generateRequestId(),
timestamp: Date.now(),
ip: getClientIP()
}
};
return this.contextStorage.run(context, async () => {
// Set context in all downstream services
await this.setDatabaseContext(context);
await this.setClaudeContext(context);
await this.setCacheContext(context);
// Execute operation with full context
return await operation();
});
}
getCurrentContext(): TenantContext {
const context = this.contextStorage.getStore();
if (!context) {
throw new Error('No tenant context available');
}
return context;
}
}Authentication & Authorization
1. Multi-Tenant OAuth Implementation
class MultiTenantOAuth {
async authenticate(request: AuthRequest): Promise<AuthResult> {
// Extract tenant from subdomain or path
const tenantId = this.extractTenantId(request);
// Get tenant-specific OAuth config
const oauthConfig = await this.getTenantOAuthConfig(tenantId);
// Validate OAuth token with tenant context
const token = await this.validateToken(request.token, oauthConfig);
// Create tenant-scoped session
return {
tenantId,
userId: token.sub,
permissions: await this.getTenantPermissions(tenantId, token.sub),
sessionId: generateSessionId(),
expiresAt: token.exp
};
}
private extractTenantId(request: AuthRequest): string {
// Option 1: Subdomain (tenant1.app.com)
const subdomain = request.hostname.split('.')[0];
if (subdomain !== 'www' && subdomain !== 'app') {
return subdomain;
}
// Option 2: Path prefix (/tenant/tenant1)
const pathMatch = request.path.match(/^\/tenant\/([^\/]+)/);
if (pathMatch) {
return pathMatch[1];
}
// Option 3: Custom header
if (request.headers['x-tenant-id']) {
return request.headers['x-tenant-id'];
}
throw new Error('Unable to determine tenant');
}
}2. Role-Based Access Control (RBAC)
class TenantRBACManager {
async checkPermission(
tenantId: string,
userId: string,
resource: string,
action: string
): Promise<boolean> {
// Get user's roles within tenant
const roles = await this.getUserTenantRoles(tenantId, userId);
// Check if any role grants the permission
for (const role of roles) {
const permissions = await this.getRolePermissions(tenantId, role);
if (this.hasPermission(permissions, resource, action)) {
// Log successful authorization
await this.auditLog.record({
tenantId,
userId,
resource,
action,
result: 'granted',
timestamp: Date.now()
});
return true;
}
}
// Log denied authorization
await this.auditLog.record({
tenantId,
userId,
resource,
action,
result: 'denied',
timestamp: Date.now()
});
return false;
}
async createTenantRole(
tenantId: string,
role: TenantRole
): Promise<void> {
// Ensure role is scoped to tenant
const scopedRole = {
...role,
tenantId,
permissions: role.permissions.map(p => ({
...p,
tenantId,
scope: 'tenant'
}))
};
await this.db.insert('tenant_roles', scopedRole);
}
}Compliance & Audit
1. Comprehensive Audit Logging
class TenantAuditLogger {
private auditStorage: AuditStorage;
private encryptionKey: string;
async logClaudeInteraction(
tenantId: string,
interaction: ClaudeInteraction
): Promise<void> {
const auditEntry: AuditEntry = {
id: generateAuditId(),
tenantId,
userId: interaction.userId,
timestamp: Date.now(),
action: 'claude_api_call',
resource: 'claude_code',
details: {
model: interaction.model,
tokenCount: interaction.tokenCount,
cost: interaction.estimatedCost,
// Don't log actual prompts/responses for privacy
promptHash: this.hashContent(interaction.prompt),
responseHash: this.hashContent(interaction.response),
toolsUsed: interaction.toolsUsed,
duration: interaction.duration
},
compliance: {
dataClassification: interaction.dataClassification,
retentionPolicy: this.getRetentionPolicy(tenantId),
encryption: 'AES-256-GCM'
}
};
// Encrypt sensitive data
const encryptedEntry = await this.encryptAuditEntry(auditEntry);
// Store with tenant partitioning
await this.auditStorage.store(tenantId, encryptedEntry);
// Real-time compliance monitoring
await this.checkComplianceRules(auditEntry);
}
private async checkComplianceRules(entry: AuditEntry): Promise<void> {
// Check for suspicious patterns
if (entry.details.tokenCount > 100000) {
await this.alertCompliance('high_token_usage', entry);
}
// Check for cross-tenant access attempts
if (await this.detectCrossTenantAccess(entry)) {
await this.alertSecurity('cross_tenant_attempt', entry);
}
}
}2. Data Residency & Sovereignty
class DataResidencyManager {
async routeClaudeRequest(
tenantId: string,
request: ClaudeRequest
): Promise<ClaudeEndpoint> {
// Get tenant's data residency requirements
const residency = await this.getTenantResidency(tenantId);
// Select appropriate Claude endpoint
const endpoint = this.selectEndpoint(residency);
// Ensure data doesn't leave required region
if (!this.isCompliantEndpoint(endpoint, residency)) {
throw new ComplianceError(
`Cannot process request: endpoint ${endpoint.region} violates residency requirement ${residency.region}`
);
}
return endpoint;
}
private selectEndpoint(residency: DataResidency): ClaudeEndpoint {
const endpoints = {
'us': { url: 'https://api.anthropic.com', region: 'us-east-1' },
'eu': { url: 'https://eu.api.anthropic.com', region: 'eu-west-1' },
'apac': { url: 'https://apac.api.anthropic.com', region: 'ap-southeast-1' }
};
return endpoints[residency.region] || endpoints['us'];
}
}3. GDPR & Privacy Compliance
class GDPRComplianceManager {
async handleDataRequest(
tenantId: string,
userId: string,
requestType: 'access' | 'deletion' | 'portability'
): Promise<ComplianceResponse> {
switch (requestType) {
case 'access':
return await this.handleAccessRequest(tenantId, userId);
case 'deletion':
return await this.handleDeletionRequest(tenantId, userId);
case 'portability':
return await this.handlePortabilityRequest(tenantId, userId);
}
}
private async handleDeletionRequest(
tenantId: string,
userId: string
): Promise<ComplianceResponse> {
// Delete user's Claude interaction history
await this.deleteClaudeHistory(tenantId, userId);
// Delete cached responses
await this.deleteCachedData(tenantId, userId);
// Delete from vector stores
await this.deleteVectorEmbeddings(tenantId, userId);
// Audit the deletion
await this.auditDeletion(tenantId, userId);
return {
status: 'completed',
deletedRecords: await this.getDeletedCount(),
timestamp: Date.now(),
certificate: await this.generateDeletionCertificate()
};
}
}Security Best Practices
1. Zero-Trust Architecture
class ZeroTrustSecurityManager {
async validateRequest(request: SecureRequest): Promise<ValidationResult> {
// Never trust, always verify
const validations = await Promise.all([
this.validateAuthentication(request),
this.validateAuthorization(request),
this.validateTenantContext(request),
this.validateDataAccess(request),
this.validateNetworkOrigin(request)
]);
// All validations must pass
const allValid = validations.every(v => v.valid);
if (!allValid) {
const failures = validations
.filter(v => !v.valid)
.map(v => v.reason);
throw new SecurityError(`Request validation failed: ${failures.join(', ')}`);
}
return { valid: true, context: this.buildSecurityContext(validations) };
}
}2. Encryption at Rest and in Transit
class EncryptionManager {
async encryptTenantData(
tenantId: string,
data: any
): Promise<EncryptedData> {
// Get tenant-specific encryption key
const tenantKey = await this.getTenantEncryptionKey(tenantId);
// Encrypt with AES-256-GCM
const encrypted = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: crypto.getRandomValues(new Uint8Array(12)),
tagLength: 128
},
tenantKey,
new TextEncoder().encode(JSON.stringify(data))
);
return {
ciphertext: encrypted,
algorithm: 'AES-256-GCM',
keyId: await this.getKeyId(tenantKey),
tenantId
};
}
}3. Security Monitoring & Threat Detection
class ThreatDetectionSystem {
async monitorClaudeUsage(
tenantId: string,
usage: ClaudeUsageMetrics
): Promise<ThreatAssessment> {
const anomalies = await this.detectAnomalies(tenantId, usage);
if (anomalies.length > 0) {
// Analyze threat level
const threatLevel = this.assessThreatLevel(anomalies);
if (threatLevel === 'high') {
// Immediate action
await this.blockTenant(tenantId);
await this.alertSecurityTeam(tenantId, anomalies);
} else if (threatLevel === 'medium') {
// Rate limit and monitor
await this.applyStrictRateLimits(tenantId);
await this.enableEnhancedMonitoring(tenantId);
}
return {
tenantId,
threatLevel,
anomalies,
recommendedActions: this.getRecommendedActions(threatLevel)
};
}
return { tenantId, threatLevel: 'low', anomalies: [] };
}
private async detectAnomalies(
tenantId: string,
usage: ClaudeUsageMetrics
): Promise<Anomaly[]> {
const anomalies: Anomaly[] = [];
// Check for unusual token usage
if (usage.tokensPerMinute > await this.getBaselineTPM(tenantId) * 10) {
anomalies.push({
type: 'excessive_token_usage',
severity: 'high',
details: `TPM: ${usage.tokensPerMinute}`
});
}
// Check for suspicious prompts
if (usage.suspiciousPromptCount > 0) {
anomalies.push({
type: 'suspicious_prompts',
severity: 'medium',
details: `Count: ${usage.suspiciousPromptCount}`
});
}
// Check for cross-tenant access attempts
if (usage.crossTenantAttempts > 0) {
anomalies.push({
type: 'cross_tenant_access',
severity: 'critical',
details: `Attempts: ${usage.crossTenantAttempts}`
});
}
return anomalies;
}
}Implementation Checklist
Security Requirements
- Implement tenant isolation at database level
- Set up per-tenant API key management
- Configure row-level security policies
- Implement comprehensive audit logging
- Set up encryption for data at rest
- Configure TLS for all communications
- Implement zero-trust validation
Compliance Requirements
- GDPR data handling procedures
- Data residency configuration
- Audit trail retention policies
- Privacy-preserving logging
- Right to deletion implementation
- Data portability APIs
- Compliance reporting dashboards
Monitoring Requirements
- Real-time security monitoring
- Anomaly detection system
- Usage analytics per tenant
- Cost tracking and allocation
- Performance monitoring
- Threat detection alerts
- Compliance violation alerts
Conclusion
Implementing secure multi-tenant SaaS applications with Claude Code requires careful attention to data isolation, authentication, and compliance. By following these patterns and best practices, you can build systems that maintain strong security boundaries while delivering powerful AI capabilities to each tenant.
Related Resources
Last verified: 2025-07-21
Source: Industry best practices and security research