Architecture
Tenant Context
interface TenantContext {
tenantId: string;
userId: string;
permissions: string[];
quotas: {
maxExecutions: number;
maxToolCalls: number;
maxDurationMs: number;
};
}
// Extract from request
function getTenantContext(req: Request): TenantContext {
const token = req.headers.authorization?.replace('Bearer ', '');
const decoded = verifyToken(token);
return {
tenantId: decoded.tenantId,
userId: decoded.userId,
permissions: decoded.permissions,
quotas: getTenantQuotas(decoded.tenantId),
};
}
Tenant-Isolated Enclave
import { Enclave } from '@enclave-vm/core';
class TenantEnclaveService {
private enclave: Enclave;
constructor() {
this.enclave = new Enclave({
securityLevel: 'STRICT',
toolHandler: this.createTenantToolHandler.bind(this),
});
}
async execute(code: string, context: TenantContext) {
// Apply tenant-specific limits
const result = await this.enclave.run(code, {
timeout: context.quotas.maxDurationMs,
maxToolCalls: context.quotas.maxToolCalls,
context, // Pass to tool handler
});
// Log execution for audit
await this.logExecution(context, code, result);
return result;
}
private createTenantToolHandler(context: TenantContext) {
return async (name: string, args: Record<string, unknown>) => {
// Check permission
const permission = `tool:${name}`;
if (!context.permissions.includes(permission)) {
throw new Error(`Permission denied: ${permission}`);
}
// Execute with tenant isolation
return this.executeTenantTool(context, name, args);
};
}
private async executeTenantTool(
context: TenantContext,
name: string,
args: Record<string, unknown>
) {
// Add tenant filter to all queries
const tenantArgs = {
...args,
_tenantId: context.tenantId,
};
switch (name) {
case 'data:query':
return db.collection('data')
.find({ ...args.filter, tenantId: context.tenantId })
.limit(args.limit || 100)
.toArray();
case 'data:insert':
return db.collection('data').insertOne({
...args.document,
tenantId: context.tenantId,
createdBy: context.userId,
createdAt: new Date(),
});
default:
throw new Error(`Unknown tool: ${name}`);
}
}
private async logExecution(
context: TenantContext,
code: string,
result: ExecutionResult
) {
await auditLog.insert({
tenantId: context.tenantId,
userId: context.userId,
action: 'code_execution',
codeHash: hash(code),
success: result.success,
duration: result.stats?.duration,
timestamp: new Date(),
});
}
}
API Endpoints
import express from 'express';
import { TenantEnclaveService } from './services/enclave';
const app = express();
const enclaveService = new TenantEnclaveService();
// Middleware
app.use(authMiddleware);
app.use(tenantMiddleware);
app.use(rateLimitMiddleware);
// Execute endpoint
app.post('/api/execute', async (req, res) => {
const context = req.tenantContext as TenantContext;
const { code } = req.body;
// Check execution quota
const executions = await getExecutionCount(context.tenantId, 'day');
if (executions >= context.quotas.maxExecutions) {
return res.status(429).json({
error: 'Execution quota exceeded',
limit: context.quotas.maxExecutions,
reset: getNextResetTime(),
});
}
try {
const result = await enclaveService.execute(code, context);
res.json(result);
} catch (error) {
res.status(500).json({
error: error.message,
});
}
});
// Get tenant usage
app.get('/api/usage', async (req, res) => {
const context = req.tenantContext as TenantContext;
const usage = await getUsageStats(context.tenantId);
res.json({
executions: usage.executionCount,
toolCalls: usage.toolCallCount,
quotas: context.quotas,
periodEnd: getNextResetTime(),
});
});
Per-Tenant Tool Registration
interface TenantToolConfig {
tenantId: string;
tools: Array<{
name: string;
enabled: boolean;
rateLimit?: { calls: number; windowMs: number };
config?: Record<string, unknown>;
}>;
}
class TenantToolRegistry {
private configs = new Map<string, TenantToolConfig>();
async loadTenantConfig(tenantId: string): Promise<TenantToolConfig> {
if (this.configs.has(tenantId)) {
return this.configs.get(tenantId)!;
}
const config = await db.collection('tenant_tools').findOne({ tenantId });
this.configs.set(tenantId, config);
return config;
}
async isToolEnabled(tenantId: string, toolName: string): Promise<boolean> {
const config = await this.loadTenantConfig(tenantId);
const tool = config.tools.find(t => t.name === toolName);
return tool?.enabled ?? false;
}
async getToolConfig(
tenantId: string,
toolName: string
): Promise<Record<string, unknown> | undefined> {
const config = await this.loadTenantConfig(tenantId);
const tool = config.tools.find(t => t.name === toolName);
return tool?.config;
}
}
Data Isolation
// Ensure all database queries include tenant filter
class TenantDataAccess {
constructor(private tenantId: string) {}
async find(collection: string, filter: object) {
return db.collection(collection).find({
...filter,
tenantId: this.tenantId, // Always filter by tenant
});
}
async insert(collection: string, document: object) {
return db.collection(collection).insertOne({
...document,
tenantId: this.tenantId,
});
}
async update(collection: string, filter: object, update: object) {
return db.collection(collection).updateMany(
{ ...filter, tenantId: this.tenantId },
update
);
}
async delete(collection: string, filter: object) {
return db.collection(collection).deleteMany({
...filter,
tenantId: this.tenantId,
});
}
}
Resource Quotas
interface TenantQuotas {
// Execution limits
maxExecutionsPerDay: number;
maxExecutionDurationMs: number;
maxConcurrentExecutions: number;
// Tool limits
maxToolCallsPerExecution: number;
maxToolCallsPerDay: number;
// Resource limits
maxMemoryMB: number;
maxCodeSizeKB: number;
maxOutputSizeKB: number;
}
const TIER_QUOTAS: Record<string, TenantQuotas> = {
free: {
maxExecutionsPerDay: 100,
maxExecutionDurationMs: 5000,
maxConcurrentExecutions: 1,
maxToolCallsPerExecution: 10,
maxToolCallsPerDay: 500,
maxMemoryMB: 32,
maxCodeSizeKB: 50,
maxOutputSizeKB: 100,
},
starter: {
maxExecutionsPerDay: 1000,
maxExecutionDurationMs: 30000,
maxConcurrentExecutions: 5,
maxToolCallsPerExecution: 50,
maxToolCallsPerDay: 10000,
maxMemoryMB: 64,
maxCodeSizeKB: 100,
maxOutputSizeKB: 500,
},
enterprise: {
maxExecutionsPerDay: 100000,
maxExecutionDurationMs: 300000,
maxConcurrentExecutions: 100,
maxToolCallsPerExecution: 500,
maxToolCallsPerDay: 1000000,
maxMemoryMB: 256,
maxCodeSizeKB: 500,
maxOutputSizeKB: 5000,
},
};
async function getTenantQuotas(tenantId: string): Promise<TenantQuotas> {
const tenant = await db.collection('tenants').findOne({ id: tenantId });
const tier = tenant?.tier || 'free';
return TIER_QUOTAS[tier];
}
Rate Limiting per Tenant
import { RateLimiterRedis } from 'rate-limiter-flexible';
const rateLimiters = {
execution: new RateLimiterRedis({
storeClient: redis,
keyPrefix: 'rl:exec',
points: 100,
duration: 86400, // per day
}),
toolCall: new RateLimiterRedis({
storeClient: redis,
keyPrefix: 'rl:tool',
points: 10000,
duration: 86400,
}),
};
async function checkRateLimit(
limiter: string,
tenantId: string,
points = 1
): Promise<void> {
const rl = rateLimiters[limiter];
if (!rl) throw new Error(`Unknown limiter: ${limiter}`);
try {
await rl.consume(tenantId, points);
} catch (error) {
throw new Error(`Rate limit exceeded: ${limiter}`);
}
}
Tenant Isolation Testing
import { describe, it, expect } from 'vitest';
import { TenantEnclaveService } from './services/enclave';
describe('Tenant Isolation', () => {
const service = new TenantEnclaveService();
const tenant1 = {
tenantId: 'tenant-1',
userId: 'user-1',
permissions: ['tool:data:query', 'tool:data:insert'],
quotas: { maxExecutions: 100, maxToolCalls: 50, maxDurationMs: 5000 },
};
const tenant2 = {
tenantId: 'tenant-2',
userId: 'user-2',
permissions: ['tool:data:query'],
quotas: { maxExecutions: 100, maxToolCalls: 50, maxDurationMs: 5000 },
};
it('isolates data between tenants', async () => {
// Tenant 1 inserts data
await service.execute(`
await callTool('data:insert', { document: { name: 'secret' } });
`, tenant1);
// Tenant 2 queries - should not see tenant 1 data
const result = await service.execute(`
const items = await callTool('data:query', { filter: {} });
return items;
`, tenant2);
expect(result.value).toEqual([]);
});
it('enforces tool permissions', async () => {
// Tenant 2 doesn't have insert permission
const result = await service.execute(`
await callTool('data:insert', { document: { name: 'test' } });
`, tenant2);
expect(result.success).toBe(false);
expect(result.error?.message).toContain('Permission denied');
});
it('enforces quotas', async () => {
const limitedContext = {
...tenant1,
quotas: { ...tenant1.quotas, maxToolCalls: 2 },
};
const result = await service.execute(`
await callTool('data:query', {});
await callTool('data:query', {});
await callTool('data:query', {}); // Should fail
`, limitedContext);
expect(result.success).toBe(false);
expect(result.error?.code).toBe('MAX_TOOL_CALLS');
});
});
Complete Example
// server.ts
import express from 'express';
import { Enclave } from '@enclave-vm/core';
const app = express();
app.use(express.json());
// Tenant context middleware
app.use(async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const decoded = verifyToken(token);
req.tenant = {
tenantId: decoded.tenantId,
userId: decoded.userId,
permissions: decoded.permissions,
quotas: await getTenantQuotas(decoded.tenantId),
};
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
});
// Execute endpoint
app.post('/execute', async (req, res) => {
const { code } = req.body;
const tenant = req.tenant;
// Check execution quota
await checkRateLimit('execution', tenant.tenantId);
// Create tenant-specific enclave
const enclave = new Enclave({
securityLevel: 'STRICT',
timeout: tenant.quotas.maxDurationMs,
maxToolCalls: tenant.quotas.maxToolCalls,
memoryLimit: tenant.quotas.maxMemoryMB * 1024 * 1024,
toolHandler: async (name, args) => {
// Check permission
if (!tenant.permissions.includes(`tool:${name}`)) {
throw new Error(`Permission denied: tool:${name}`);
}
// Check tool rate limit
await checkRateLimit('toolCall', tenant.tenantId);
// Execute with tenant isolation
return executeTenantTool(tenant, name, args);
},
});
try {
const result = await enclave.run(code);
// Audit log
await logExecution(tenant, code, result);
res.json(result);
} finally {
enclave.dispose();
}
});
app.listen(3000);
Next Steps
- Encrypted Sessions - E2E encryption
- Security Hardening - Security best practices
- Scaling - Scale multi-tenant deployments