superapp
AdvancedSecurity

Overview

Defense-in-depth security architecture.

@superapp/backend is built on the principle that no query executes without passing through a permission check. The client sends parameterized SQL via Drizzle Proxy, and the server validates, applies permissions, and executes. There is no god mode.

Client Request (Drizzle Proxy: SQL + params + JWT)

  ├─ 1. Rate Limiting ─────── per-user and per-IP throttle
  ├─ 2. JWT Validation ────── algorithm allowlist, issuer, audience, expiry
  ├─ 3. Permission Check ──── validate SQL, table, operation, columns, row-level filters
  ├─ 4. DuckDB Isolation ──── ephemeral per-session instance, sandboxed
  └─ 5. Audit Log ─────────── query, params, duration, user, IP

Defense Layers

Parameterized SQL Only

Clients send parameterized SQL built by Drizzle ORM (via Drizzle Proxy), never raw interpolated strings. All user values are passed as parameters ($1, $2, ...), never concatenated into the query string. SQL injection is structurally impossible because user input never interpolates into query strings.

No God Mode

Every query must match at least one permission. If no permission grants access to a table, the request is rejected with 403 Forbidden. There is no superuser bypass in the data path -- even admin operations go through the admin API with master key authentication.

JWT Validation

Tokens are validated against an algorithm allowlist. Weak algorithms (HS256, none) are rejected by default. Claims are verified for issuer, audience, and expiry with configurable clock skew tolerance.

Permission Enforcement

Permissions define which tables, operations, and columns a role can access. Row-level filters are injected into every query automatically -- users cannot see or modify rows they are not authorized to access.

DuckDB Session Isolation

Each user session gets an ephemeral DuckDB instance with restricted capabilities. Filesystem access, COPY statements, and dangerous functions are blocked. Sessions are pooled and recycled with resource limits.

Encryption at Rest

Connection secrets (database URLs, API keys) are encrypted with AES-256-GCM using per-project keys derived from your master key via HKDF. Secrets are displayed once at creation time and never again.

Security Configuration

All security settings are configured through createEngine:

const engine = createEngine({
  connections: { /* ... */ },
  jwt: {
    algorithms: ['RS256', 'ES256'],
    issuer: 'https://auth.myapp.com',
    audience: 'https://api.myapp.com',
    clockSkewSeconds: 30,
  },
  limits: {
    maxLimit: 10_000,
    maxIncludeDepth: 3,
    maxFilterDepth: 5,
    maxFilterConditions: 20,
    maxRequestBodySize: '1mb',
    queryTimeout: 30_000,
    rateLimitPerUser: 200,
    rateLimitPerIP: 500,
  },
  duckdb: {
    maxMemory: '256MB',
    threads: 2,
    queryTimeout: 30_000,
  },
  masterKey: process.env.SUPERAPP_MASTER_KEY!,
})

Next Steps

On this page