Shipflash logoDocs

Production Safety

RLS, grants, webhook idempotency, cron auth, outbox workers, request ids, and retention jobs are the production guardrails to preserve.

On this page

Production safety source map

Database security baseline

The baseline migration owns grants, RLS enablement, policies, RPC functions, and service-role privileges for the Supabase schema.

  • supabase/migrations/20260426000000_init.sql

Webhook request guard

Webhook utilities centralize rate limiting, raw body reading, payload size limits, structured logging, and request id context.

  • src/lib/security/webhooks/request.ts
  • src/lib/security/webhooks/processing.ts

Cron guard

Cron services reject missing or placeholder secrets, compare authorization safely, rate-limit scheduler calls, and log job outcomes.

  • src/lib/security/cron-secret.ts
  • src/features/notifications/server/outbox/services/cron.ts

Outbox and retention operations

Background workers claim batches, retry safely, dead-letter exhausted work, and run retention cleanup behind protected cron routes.

  • src/features/notifications/server/outbox/services/worker.ts
  • src/features/compliance/server/retention/service.ts

Supabase server boundaries

Server helpers keep user-scoped Supabase access separate from service-role work and preserve request id propagation.

  • src/lib/supabase/server.ts
  • src/lib/supabase/proxy.ts

Production safety flow

  1. 1

    Define durable database invariants

    Create tables, constraints, grants, RLS policies, RPC functions, and service-role access in migrations instead of relying on runtime assumptions.

    • supabase/migrations/20260426000000_init.sql
  2. 2

    Preserve request context

    API and webhook routes carry request ids through logs so failures can be traced across rate limits, provider calls, database writes, and jobs.

    • src/lib/security/webhooks/request.ts
    • src/lib/supabase/proxy.ts
  3. 3

    Claim before processing external events

    Webhook handlers verify signatures, store event rows, reject duplicate processed events, and reclaim only stale processing leases.

    • src/lib/security/webhooks/request.ts
    • src/lib/security/webhooks/processing.ts
  4. 4

    Protect scheduled work

    Cron services validate CRON_SECRET, reject placeholder values, compare authorization without leaking timing, and rate-limit scheduler calls.

    • src/lib/security/cron-secret.ts
    • src/features/notifications/server/outbox/services/cron.ts
  5. 5

    Retry through outbox state

    Workers claim a bounded batch, record success or failure, back off transient errors, and dead-letter rows after repeated failures.

    • src/features/notifications/server/outbox/services/worker.ts

Cron secrets fail closed

Source: src/lib/security/cron-secret.ts

A configured but obvious placeholder secret is treated the same as a missing secret.

ts
export function readValidatedCronSecret(value: string | undefined = process.env.CRON_SECRET): string | null {
  const secret = value?.trim();
  if (!secret) {
    return null;
  }

  if (PLACEHOLDER_CRON_SECRETS.has(secret.toLowerCase())) {
    return null;
  }

  return secret;
}

Webhook stale lease

Source: src/lib/security/webhooks/processing.ts

A processing row can be retried only after the lease timeout, which protects against duplicate live workers and crashed workers.

ts
const WEBHOOK_PROCESSING_STALE_AFTER_MS = 15 * 60 * 1000;

export function isWebhookProcessingStale(
  processingStartedAt: string | null | undefined,
  nowMs: number = Date.now(),
  staleAfterMs: number = WEBHOOK_PROCESSING_STALE_AFTER_MS,
): boolean {
  if (!processingStartedAt) {
    return true;
  }

  const startedAtMs = Date.parse(processingStartedAt);
  if (!Number.isFinite(startedAtMs)) {
    return true;
  }

  return nowMs - startedAtMs >= staleAfterMs;
}

Recipe: add a production-safe table

Add a Supabase table that works in production without accidental public access, broken generated types, or missing operational docs.

Inspect

  • supabase/migrations/20260426000000_init.sql
  • src/lib/supabase/server.ts
  • src/lib/supabase/proxy.ts

Edit

  • supabase/migrations/<timestamp>_<change>.sql
  • src/lib/supabase/types.ts
  • src/features/<domain>/server/persistence/<table>.ts

Steps

  • Create the table, indexes, foreign keys, and durable constraints in a migration.
  • Enable RLS and write policies before exposing user-scoped reads or writes.
  • Grant only the roles that need access, and keep service-role writes server-only.
  • Regenerate Supabase types and route all access through feature-owned persistence helpers.
  • Document any env vars, cron routes, webhooks, or retention behavior introduced by the table.

Verify

  • pnpm run contract:migrations
  • pnpm run supabase:types
  • pnpm run test:features
  • pnpm run quality

Common mistakes

  • Do not add a table without RLS decisions.
  • Do not put service-role reads in client-reachable code.
  • Do not skip generated type refresh after schema changes.

AI prompt: production safety review

AI prompt
Review this change for production safety. Inspect migrations, RLS, grants, webhook/event idempotency, cron authentication, outbox retry behavior, request id logging, generated Supabase types, tests, and deployment checklist impact. Fail closed on auth, billing, admin, webhook, and cron boundaries.

Inspect first

  • supabase/migrations/20260426000000_init.sql
  • src/lib/security/webhooks/request.ts
  • src/lib/security/cron-secret.ts

Verify

  • pnpm run contract:migrations
  • pnpm run test:features
  • pnpm run quality