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
| Role | Permissions |
|---|---|
| Owner | Full tenant access, billing, member management |
| Admin | Project management, settings, integrations |
| Developer | Project access, code execution, deployments |
| Viewer | Read-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
| Tier | Requests/min | Burst | Scope |
|---|---|---|---|
| Anonymous | 10 | 20 | IP address |
| Free | 60 | 100 | User + Tenant |
| Pro | 300 | 500 | User + Tenant |
| Enterprise | Custom | Custom | User + 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)