Skip to main content

Security Overview

DgiDgi implements a defense-in-depth security model with multiple layers of protection for multi-tenant SaaS operations.

Security Architecture

Authentication & Authorization

Authentication Flow

Role-Based Access Control

RolePermissions
OwnerFull tenant access, billing, member management
AdminProject management, settings, integrations
DeveloperProject access, code execution, deployments
ViewerRead-only access to projects and runs

Permission Model

// Permission structure
{
"tenant": {
"manage_members": ["owner", "admin"],
"manage_billing": ["owner"],
"manage_settings": ["owner", "admin"]
},
"project": {
"create": ["owner", "admin", "developer"],
"read": ["owner", "admin", "developer", "viewer"],
"write": ["owner", "admin", "developer"],
"delete": ["owner", "admin"]
},
"agent": {
"execute": ["owner", "admin", "developer"],
"configure": ["owner", "admin"]
}
}

Tenant Isolation

Database Isolation

Row-Level Security (RLS) ensures tenants can only access their own data:

-- All queries automatically filtered by tenant
-- Enforced at PostgreSQL level, cannot be bypassed

CREATE POLICY tenant_isolation ON projects
FOR ALL
USING (tenant_id = current_setting('app.tenant_id')::uuid);

CREATE POLICY tenant_isolation ON chat_sessions
FOR ALL
USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- Applied to all tenant-scoped tables

Storage Isolation

Object storage uses tenant-prefixed keys:

tenants/
├── tenant-abc/ ← Tenant A can only access this
│ └── ...
└── tenant-xyz/ ← Tenant B can only access this
└── ...

// Key validation enforced at API layer
if (!key.startsWith(`tenants/${tenantId}/`)) {
throw new SecurityError("Access denied");
}

Network Isolation

  • Each tenant's runtime sandbox is isolated
  • No cross-tenant network communication
  • Egress filtering for external requests

Secrets Management

Tenant Secrets Storage

Platform Secrets

Platform-level secrets (LLM API keys, etc.) are stored in:

  • Fly.io Secrets (runtime)
  • GitHub Secrets (CI/CD)
  • Never committed to version control

Input Validation

All API inputs validated using Zod schemas:

// Example: Chat message validation
const sendMessageSchema = z.object({
sessionId: z.string().uuid(),
content: z.string().min(1).max(100000),
attachments: z.array(z.object({
key: z.string().regex(/^tenants\/[^/]+\//),
type: z.enum(["image", "file"]),
})).optional(),
});

Protection Against

  • SQL Injection: Parameterized queries via Drizzle ORM
  • XSS: Content sanitization, CSP headers
  • CSRF: SameSite cookies, origin validation
  • Path Traversal: Key validation, path normalization

Rate Limiting

TierRequests/minBurstScope
Anonymous1020IP address
Free60100User + Tenant
Pro300500User + Tenant
EnterpriseCustomCustomUser + Tenant

Implementation:

  • Cloudflare KV for distributed state
  • Sliding window algorithm
  • Separate limits for different endpoints

Audit Logging

All security-relevant events are logged:

// Audit log entry structure
{
"id": "...",
"tenant_id": "...",
"user_id": "...",
"action": "secret.created",
"resource_type": "tenant_secret",
"resource_id": "...",
"ip_address": "...",
"user_agent": "...",
"timestamp": "...",
"metadata": { /* action-specific data */ }
}

Logged Events

  • Authentication events (login, logout, failed attempts)
  • Authorization changes (role assignments)
  • Secret operations (create, update, delete, access)
  • Configuration changes
  • Data exports
  • API key management

Runtime Sandboxing

Agent code execution is isolated in sandboxes:

Security Headers

All responses include security headers:

Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'; ...
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin

Vulnerability Management

  • Regular dependency updates (Dependabot)
  • Security scanning in CI/CD (CodeQL, Semgrep)
  • Penetration testing (periodic)
  • Bug bounty program (planned)