superapp
AdvancedSecurity

Overview

Defense-in-depth security architecture.

Chat in Claude

@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. Query Isolation ───── per-request connection from pool, 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.

Query Isolation

Each request gets an isolated database connection from the pool. The engine validates and rewrites all SQL before execution. Dangerous operations (filesystem access, schema modification, system functions) are blocked at the permission layer.

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'],        // allowed signing algorithms
    issuer: 'https://auth.myapp.com',      // reject other issuers
    audience: 'https://api.myapp.com',     // reject other audiences
    clockSkewSeconds: 30,                  // clock drift tolerance
  },
  limits: {
    maxRows: 10_000,              // max rows a query can return
    maxRelationDepth: 3,          // orders → items → product = depth 3
    maxFilterNesting: 5,          // nested $and/$or/$not levels
    maxFilterConditions: 20,      // total conditions per query
    maxRequestBodySize: '1mb',    // max JSON body size
    queryTimeout: 30_000,         // kill slow queries (ms)
    rateLimitPerUser: 200,        // req/min per user
    rateLimitPerIP: 500,          // req/min per IP
  },
  masterKey: process.env.SUPERAPP_MASTER_KEY!, // admin API key
})

Next Steps

On this page