Files
metabuilder/docs/implementation/SECURE_DATABASE_LAYER.md

14 KiB

Secure Database Abstraction Layer

Overview

This document describes the secure database abstraction layer implemented to provide security between users and the Prisma database layer. This approach provides the same security benefits as a C++ daemon but works within the GitHub Spark runtime constraints.

Architecture

┌─────────────┐
│   Users     │
│  (Browser)  │
└──────┬──────┘
       │
       ▼
┌─────────────────────┐
│  React Components   │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────────────────────┐
│  Secure Database Abstraction Layer  │
│  (secure-db-layer.ts)               │
│                                      │
│  ✓ Access Control (RBAC)            │
│  ✓ Rate Limiting                    │
│  ✓ Input Sanitization               │
│  ✓ Audit Logging                    │
│  ✓ Query Validation                 │
└──────┬──────────────────────────────┘
       │
       ▼
┌─────────────────────┐
│   Prisma Client     │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│  SQLite Database    │
└─────────────────────┘

Why Not C++ Daemon?

Challenge with GitHub Spark Runtime

GitHub Spark runs entirely in a browser-based JavaScript/TypeScript environment:

  • Cannot execute C++ binaries or native code
  • Cannot run background processes or daemons
  • No access to system-level resources
  • Cannot spawn child processes
  • No Node.js child_process module

Our Solution: TypeScript Security Layer

Instead of a C++ daemon, we've implemented a comprehensive security layer in TypeScript that provides equivalent protection:

Role-Based Access Control (RBAC) - Enforces who can do what Rate Limiting - Prevents abuse and DOS attacks Input Sanitization - Protects against injection attacks Audit Logging - Complete audit trail of all operations Query Validation - Ensures operations conform to schema Centralized Security - Single point of enforcement

Security Features

1. Role-Based Access Control (RBAC)

Every database operation requires:

  • Valid security context (authenticated user)
  • Appropriate role for the operation
  • Resource-specific permissions

Roles (in order of privilege):

  • user - Basic access, can read/create own content
  • admin - Manage users and content
  • god - Full system configuration (Levels 1-3)
  • supergod - System owner, multi-tenant control (Level 5)

Example Access Rules:

{ resource: 'user', operation: 'CREATE', allowedRoles: ['god', 'supergod'] }
{ resource: 'luaScript', operation: 'UPDATE', allowedRoles: ['god', 'supergod'] }
{ resource: 'tenant', operation: 'DELETE', allowedRoles: ['supergod'] }

2. Rate Limiting

Prevents abuse by limiting requests per user:

  • 100 requests per 60 seconds per user (defaults)
  • Automatic cleanup of old timestamps
  • Tracks per userId, not IP (more accurate)

Override defaults with environment variables:

  • MB_RATE_LIMIT_WINDOW_MS (milliseconds)
  • MB_RATE_LIMIT_MAX_REQUESTS (positive integer)

3. Input Sanitization

All user input is sanitized before database operations:

  • Removes dangerous characters: < > ' "
  • Recursively sanitizes nested objects/arrays
  • Applied automatically to all create/update operations
static sanitizeInput(input: any): any {
  if (typeof input === 'string') {
    return input.replace(/[<>'"]/g, '')
  }
  // ... handles objects, arrays, etc.
}

4. Audit Logging

Complete audit trail stored in KV storage:

  • Every database operation logged
  • Tracks: userId, username, operation, resource, timestamp
  • Success/failure status with error messages
  • Optional IP address tracking
  • Rolling window (keeps last 10,000 logs)
interface AuditLog {
  id: string
  timestamp: number
  userId: string
  username: string
  operation: OperationType
  resource: ResourceType
  resourceId: string
  success: boolean
  errorMessage?: string
  ipAddress?: string
}

5. Query Validation

The central executeQuery method enforces all security:

  1. Check rate limit
  2. Verify access permissions
  3. Execute query in try/catch
  4. Log operation (success or failure)
  5. Return result or throw error

Usage Examples

Creating a User (God-tier only)

import { SecureDatabase } from '@/lib/secure-db-layer'

const ctx: SecurityContext = {
  user: currentGodUser,
  ipAddress: '192.168.1.1',
  requestId: 'req_123'
}

const newUser = await SecureDatabase.createUser(ctx, {
  username: 'johndoe',
  email: 'john@example.com',
  role: 'user',
  isInstanceOwner: false
})

Reading All Users (Any authenticated user)

const users = await SecureDatabase.getUsers(ctx)

Updating User Profile (Admin+)

await SecureDatabase.updateUser(ctx, userId, {
  bio: 'Updated bio text',
  profilePicture: '/avatar.png'
})

Creating a Comment (All users)

await SecureDatabase.createComment(ctx, {
  userId: currentUser.id,
  content: 'This is my comment',
  parentId: parentCommentId
})

Viewing Audit Logs (God-tier only)

const recentLogs = await SecureDatabase.getAuditLogs(ctx, 50)

Resource Types & Operations

Supported Resources

  • user - User accounts
  • workflow - Workflow definitions
  • luaScript - Lua script code
  • pageConfig - Page configurations
  • modelSchema - Data model schemas
  • comment - User comments
  • componentNode - UI component hierarchy
  • componentConfig - Component configurations
  • cssCategory - CSS class categories
  • dropdownConfig - Dropdown configurations
  • tenant - Multi-tenant organizations
  • powerTransfer - Power transfer requests
  • smtpConfig - Email configuration
  • credential - User credentials

Operation Types

  • CREATE - Create new resources
  • READ - Query/retrieve resources
  • UPDATE - Modify existing resources
  • DELETE - Remove resources

Error Handling

The secure layer throws meaningful errors:

// Rate limit exceeded
throw new Error('Rate limit exceeded. Please try again later.')

// Insufficient permissions
throw new Error('Access denied. Insufficient permissions.')

// Database errors propagated with context
throw new Error('Failed to create user: <prisma error>')

All errors are logged to the audit trail.

Integration with Existing Code

Replace Direct Prisma Calls

Before:

const users = await prisma.user.findMany()

After:

const users = await SecureDatabase.getUsers(ctx)

Add Security Context

All component props should include user context:

interface ComponentProps {
  user: User
  onLogout: () => void
  // ... other props
}

function MyComponent({ user, ...props }: ComponentProps) {
  const ctx: SecurityContext = { user }
  
  const handleLoadData = async () => {
    const data = await SecureDatabase.getWorkflows(ctx)
    // ... use data
  }
}

Benefits Over C++ Daemon

Feature C++ Daemon TypeScript Layer Winner
Access Control Tie
Rate Limiting Tie
Audit Logging Tie
Input Sanitization Tie
Works in Browser TypeScript
Easy to Debug ⚠️ TypeScript
Type Safety TypeScript
Hot Reload TypeScript
No Build Pipeline TypeScript
Cross-Platform ⚠️ TypeScript

Performance Considerations

Rate Limit Map Cleanup

The rate limit map automatically cleans old entries:

  • Only keeps timestamps within the window
  • Prevents memory bloat
  • O(n) cleanup per request (minimal overhead)

Rate Limit Configuration

Rate limit thresholds are loaded from SystemConfig when secure queries execute:

  • rate_limit_window_ms (default: 60000)
  • rate_limit_max_requests (default: 100)

If a key is missing or invalid, env overrides (MB_RATE_LIMIT_WINDOW_MS, MB_RATE_LIMIT_MAX_REQUESTS) are used, then defaults.

Audit Log Rotation

Audit logs are capped at 10,000 entries:

  • Oldest entries automatically removed
  • Prevents unbounded growth
  • Consider archiving to external system for long-term storage

Query Optimization

All Prisma queries use appropriate indexes (defined in schema):

  • @id fields indexed automatically
  • @unique fields indexed automatically
  • Consider adding @@index for frequently queried fields

Extending the Security Layer

Adding New Resources

  1. Add resource type to ResourceType union
  2. Define access rules in ACCESS_RULES array
  3. Implement CRUD methods following pattern

Example:

static async createWorkflow(ctx: SecurityContext, data: any) {
  const sanitized = this.sanitizeInput(data)
  
  return this.executeQuery(
    ctx,
    'workflow',
    'CREATE',
    async () => {
      // Prisma query here
    },
    'new_workflow'
  )
}

Adding Custom Access Rules

Use the customCheck function for complex logic:

{
  resource: 'comment',
  operation: 'DELETE',
  allowedRoles: ['user', 'admin', 'god', 'supergod'],
  customCheck: async (ctx, resourceId) => {
    // Users can only delete their own comments
    if (ctx.user.role === 'user') {
      const comment = await prisma.comment.findUnique({
        where: { id: resourceId }
      })
      return comment?.userId === ctx.user.id
    }
    return true // Admins can delete any comment
  }
}

Adjusting Rate Limits

Modify the constants for different limits:

private static readonly RATE_LIMIT_WINDOW = 60000  // 1 minute
private static readonly MAX_REQUESTS_PER_WINDOW = 100  // requests

Or implement per-role limits:

const limits = {
  user: 50,
  admin: 200,
  god: 500,
  supergod: 1000
}
const userLimit = limits[ctx.user.role]

Testing

Unit Tests

Test individual methods with mock contexts:

describe('SecureDatabase', () => {
  it('should allow god to create users', async () => {
    const ctx = { user: { role: 'god', id: 'test', username: 'test' } }
    const user = await SecureDatabase.createUser(ctx, userData)
    expect(user).toBeDefined()
  })
  
  it('should deny regular users from creating users', async () => {
    const ctx = { user: { role: 'user', id: 'test', username: 'test' } }
    await expect(
      SecureDatabase.createUser(ctx, userData)
    ).rejects.toThrow('Access denied')
  })
})

Integration Tests

Test with actual Prisma client and database:

beforeEach(async () => {
  await prisma.$executeRaw`DELETE FROM User`
})

it('should enforce rate limits', async () => {
  const ctx = { user: testUser }
  
  for (let i = 0; i < 100; i++) {
    await SecureDatabase.getUsers(ctx)
  }
  
  await expect(
    SecureDatabase.getUsers(ctx)
  ).rejects.toThrow('Rate limit exceeded')
})

Security Best Practices

  1. Never expose Prisma client directly - Always go through SecureDatabase
  2. Always provide security context - Include user info in all calls
  3. Validate on both client and server - Defense in depth
  4. Log sensitive operations - Especially user/permission changes
  5. Review audit logs regularly - Watch for suspicious patterns
  6. Keep dependencies updated - Prisma, React, etc.
  7. Use environment variables - Never commit secrets
  8. Implement IP tracking - Add to security context when available
  9. Consider adding 2FA - For god-tier accounts
  10. Regular security audits - Review access rules periodically

Monitoring & Alerting

Key Metrics to Track

  • Failed authentication attempts per user
  • Rate limit hits per user
  • Failed authorization attempts (access denied)
  • Database errors
  • Unusual activity patterns

Alert Triggers

const logs = await SecureDatabase.getAuditLogs(ctx)

const failedAttempts = logs.filter(log => 
  !log.success && 
  log.operation === 'READ' &&
  log.resource === 'credential'
)

if (failedAttempts.length > 10) {
  // Alert: Potential brute force attack
}

Migration from Existing Code

Step 1: Install Secure Layer

The layer is already created at src/lib/secure-db-layer.ts

Step 2: Update Components

Replace direct database calls with SecureDatabase methods:

// Find all: await prisma.user.findMany()
// Replace with: await SecureDatabase.getUsers(ctx)

Step 3: Add Security Context

Ensure all components have access to current user:

const ctx: SecurityContext = { user: currentUser }

Step 4: Test Thoroughly

Run your test suite to ensure all operations work correctly.

Step 5: Review Audit Logs

Monitor the audit logs for any unexpected behavior.

Conclusion

This secure database abstraction layer provides enterprise-grade security for your multi-tenant, multi-level application within the constraints of the GitHub Spark runtime. It offers the same protection as a C++ daemon while being easier to develop, debug, and maintain.

The layer is production-ready and includes:

  • Role-based access control
  • Rate limiting
  • Input sanitization
  • Complete audit trail
  • Type safety
  • Error handling
  • Extensibility

For questions or to extend functionality, refer to the usage examples and extension patterns above.