# Security controls that start in the database and continue at the edge

> A SaaS security starter with RLS on 54 tables, route guards, scoped keys, audit logging, sanitizers, rate controls, and a candid validation gap.

Source: https://saascode.ai/inside/security-rls-validation-audit-logging · Published: 2026-08-21 · Section: fabric

---
A SaaS starter with RLS should make the database enforce the same boundaries the interface claims. The base does that across its current schema, then adds route guards, scoped API credentials, audit records, input controls, and rate limiting around the application.

Security here is not one middleware file. It is a set of overlapping controls. Row-level security limits what a signed-in database session can see. Role guards protect privileged routes. API keys carry organization identity and scope. Audit entries record sensitive changes. Sanitizers narrow risky text inputs. Rate limiters slow abusive request patterns.

No one layer proves the product is secure. Together, they give every product a stronger starting position and make the remaining limits visible.

## Row-level security covers the schema

A static scan of the current migration set found 54 application tables created and 54 tables with row-level security enabled. There were no created tables without a corresponding enable statement in that scan.

Tenant-owned policies use the current user's organization ID. User-owned policies can compare against the authenticated user ID. Admin policies use explicit role helpers. Public tables such as plans and platform-global tables such as translations use policies that match their intended visibility rather than pretending every row is tenant-private.

Later migrations harden the privilege layer around those policies. Authenticated table writes are explicitly revoked where clients should not mutate rows directly. API-key writes are restricted so a tenant session cannot create an admin-scoped credential. Security-definer database functions have execute grants revoked from public roles unless a function is meant to be callable.

The role model also has a self-escalation trigger. A non-admin cannot patch their own user row to become a top-level admin. The trigger runs as the caller and delegates the authorized role check to a controlled helper.

## Application guards sit above RLS

The admin route guard loads the authenticated user profile and accepts only the required owner role. The application proxy performs another role and account-status check before rendering the admin shell. Organization-owner and tenant-member helpers combine role and tenant identity for products that add those role tiers.

API keys use a separate credential path. The full secret is shown once; only its hash is stored. Validation checks the prefix, hash, active state, expiry, and read/write/admin scope. Request usage is logged against the key.

The MCP and Connect endpoints reuse that key identity. They check organization features and required scopes before calling a tool or business workflow. Connect validates its operation input against the operation's Zod schema before execution.

## Validation and sanitization are not the same thing

Zod is present in selected high-value routes and shared operations. Role changes, onboarding, analytics funnels, settings, device registration, and Connect workflows are among the paths that use schemas.

It is not universal, and the numbers say so plainly. A static scan found 174 API route files, 16 of them carrying a Zod import or a `z.object`, and 89 routes that read a JSON request body. Of those body-reading routes, 73 do not use Zod in the file. Many apply manual required-field checks or allowed-field lists instead. So “schema validation” here means selected paths, not every payload — worth knowing before assuming a new route inherits it.

The base also includes narrow sanitizers for specific risk classes. Rich HTML passes through an allowlist of elements, attributes, and protocols. Search input removes characters that could alter a PostgREST filter expression. CSV export prefixes spreadsheet-formula cells so opening an export does not execute them as formulas.

Those helpers only protect call sites that use them. A new route still has to choose a schema or sanitizer that matches its input.

## Audit and rate controls

The audit table records organization, actor, action, entity, metadata, and time. The actor can be a user, API key, or system process. Shared services write audit entries for role changes, API-key lifecycle, organization changes, plan changes, feature toggles, and other owner operations that call the helper. The admin audit endpoint can list and export the records.

Every API path crosses a coarse per-IP limiter in the application proxy. In production, Redis supplies shared counters when configured. A single-process deployment can use the in-memory fallback. Authentication and setup paths have stricter windows than the general API tier. Key-authenticated API and MCP calls can also use a 100-per-minute per-key window.

## The honest limit

RLS being enabled is a coverage fact, not a proof that every policy is correct. A permissive policy can still expose too much, and privileged server clients bypass user-session RLS by design. New tables need policies; new privileged routes need role guards and scoped queries.

Zod coverage is partial. The current numbers—16 of 174 route files, with 73 JSON-body routes lacking Zod in the route—do not support a blanket validation claim. Manual validation can be correct, but it is less uniform and has to be reviewed route by route.

Audit logging is an available, widely used helper, not a database-level guarantee that every mutation produces an entry. A new write path can omit it. Coverage should be tested for the actions a product considers security-relevant.

Rate limiting is best-effort when Redis is absent. The memory fallback is process-local, development is intentionally open, and limiter errors fail open. A distributed deployment that needs consistent enforcement should configure the shared backend.

This layer also makes no compliance claim. RLS, validation, logs, and limits are engineering controls. They do not by themselves certify a product for any legal or industry standard.

## Why security belongs in every product

Security retrofits fail at seams. The page looks protected but the database API is open. The admin route checks a role but the user can self-promote. The API key works but cannot be revoked. The audit page exists but important writes never call it.

The base closes many of those seams before product code arrives. It gives tables RLS, routes guard patterns, credentials scopes, privilege hardening, sanitizers, audit storage, and edge limits. It also leaves enough evidence to say where those controls stop.

That is more useful than a “secure by default” slogan. A buyer gets concrete controls and a concrete review list: policy correctness, privileged-route guards, schema coverage, audit coverage, and distributed rate-limit configuration.
