This document focuses on architecture-level security patterns. For implementation details, see:
- Authentication - User authentication flows
- Authorization - Permission system
- RLS Policies - Row-level security implementation
1. Consistent Security Enforcement at the API Boundary
Architectural Choice
Rather than a literal standalone API gateway, Tuturuuu enforces security consistently at the API route boundary through a shared higher-order wrapper. Every public SDK route underapps/web/src/app/api/v1/* is wrapped with withApiAuth, which centralizes the security logic (API-key authentication, rate limiting, permission checks) in one tested implementation instead of re-deriving it per route.
Conceptual vs. real. “API gateway” describes the pattern — a single, uniform enforcement point — not a literal gateway process in front of microservices. In
apps/web there is no separate gateway tier; security is enforced per route. The uniformity comes from sharing the withApiAuth wrapper (apps/web/src/lib/api-middleware.ts), Next.js middleware.ts, and Vercel’s edge. In the TanStack + Rust migration, the Rust backend (apps/backend) becomes the primary boundary that validates Supabase sessions.Impact and Justification
By funneling authentication and authorization through one shared wrapper, we create a single security control plane in code: policies are defined once and applied consistently before any business logic runs. Routes do not re-implement token parsing, key validation, or rate-limiting individually, which removes the drift and duplication that cause inconsistent security postures. Conceptual enforcement flow (illustrative, not a literal gateway process):apps/web/src/app/api/v1/storage/list/route.ts):
rateLimit is optional: by default withApiAuth leaves GET/HEAD reads open and applies a per-key mutation limit (about 100 requests/minute), with workspace-specific overrides read from workspace_secrets. Pass a rateLimit object only to tighten or relax those defaults.
Clarifying Additions
Shared enforcement ensures uniform security behavior across routes. Instead of each route implementing authentication differently,withApiAuth provides a single, tested implementation every endpoint benefits from.
Policies update consistently without touching each route. When a new requirement emerges (e.g., a stricter default rate limit), it is implemented once in the wrapper and immediately applies everywhere it is used.
This strengthens protection by eliminating inconsistent security implementations. Human error is reduced because developers do not re-implement the same security logic across many routes.
2. Zero Trust Principles Applied at the Architectural Level
Architectural Choice
The architecture implements Zero Trust security principles, where no service or request is inherently trusted based on its network location or source. Every interaction, whether from external clients or between internal services, must be explicitly authenticated and authorized.Impact and Justification
Traditional “castle-and-moat” security assumes that anything inside the network perimeter is trusted. This is a dangerous assumption because once an attacker breaches the perimeter, they have unrestricted access to internal systems. Zero Trust architecture eliminates implicit trust. Every request is re-validated for identity and permissions at the boundary it crosses, and the database independently enforces access rules regardless of which app issued the query. How identity is established in practice. Every request that reaches data must carry a verifiable identity, and that identity is re-checked at the boundary:- User requests carry a Supabase session (cookie or
Authorization: Beareraccess token). The Rust backend validates these inapps/backend/src/supabase_auth.rs, which extracts the access token from thesb-*-auth-tokencookie (including base64-prefixed, chunked cookies) or a non-app Bearer token, then calls the Supabase Auth/userendpoint with the service-role key to resolve the user. - SDK / machine requests carry a workspace-scoped API key, authenticated and rate-limited by
withApiAuthinapps/webbefore any handler runs.
Clarifying Additions
Identity is established from verifiable tokens, not network location. A request is trusted because it carries a valid Supabase session or workspace API key that is re-validated at the boundary — not because it originated inside the deployment. The database has the final say. Even if an API layer were bypassed or compromised, RLS policies independently enforce that a user can only read or write rows for workspaces they belong to. Application bugs cannot silently defeat data-access rules. This reduces risks associated with implicit trust. Because authorization is enforced both at the boundary and in the database, a single compromised layer does not grant unrestricted data access.3. Security Boundaries Through RLS and Permission Scoping
Architectural Choice
Isolation between domains (finance, AI chat, drive, tasks) is enforced primarily by per-table Row-Level Security policies and scoped permissions, not by giving each app its own Supabase project or key. The apps share one Supabase database; the boundary lives in the policies and in the permission set each API key or user role is granted.Impact and Justification
In a single shared database, a vulnerability that bypasses one route must still defeat the per-table RLS policy to reach data. By scoping each domain’s tables with dedicated policies and permission requirements, we contain the blast radius: a bug in a drive route cannot read finance rows, because the finance policy requires finance-specific roles and the request’s identity does not satisfy them. Concretely, SDK routes declare the permissions they need (permissions: ['manage_drive']), and the database enforces an orthogonal check at query time. An attacker who somehow reaches a query path is still bounded by both the granted permission scope and the row-level policy.
Data isolation through RLS policy design:
Clarifying Additions
A bug in one domain’s routes does not expose another domain’s data. RLS policies and permission scoping bound each domain independently, so reaching a finance table requires finance-grade identity even from an unrelated route. Policies act as an independent barrier alongside application checks. Defense in depth is achieved because the database re-validates access regardless of what the application layer believed. This scoping limits the damage in security incidents. Incident response is more manageable because the data an attacker could reach is bounded by the policies and permissions that gate each table.4. Defense-in-Depth via Layered Architecture
Architectural Choice
The architecture implements multiple layers of security controls, from the network edge through the API Gateway, into the application services, down to the database layer. Each layer provides independent security validation.Impact and Justification
Relying on a single security layer is fragile. If that layer is bypassed or fails, the entire system is exposed. The defense-in-depth approach ensures that even if an attacker successfully bypasses one security control, they encounter additional independent security barriers at deeper layers. For example, even if an attacker bypassed the API boundary’s authentication (highly unlikely but theoretically possible), they would still encounter:- Route-level authorization checks (declared permissions) in the application code
- Database Row-Level Security policies that enforce data access rules
- Workspace scoping that bounds every query to the caller’s workspace
createTuturuuuNextConfig receives Content-Security-Policy: frame-ancestors 'none' and X-Frame-Options: DENY on its responses. This prevents an
attacker-controlled page from rendering Tuturuuu interfaces inside an iframe
for UI redressing. App-specific headers() entries are composed after the
shared security rule, so route-specific cache and indexing headers remain
intact.
The only shared exception is the CMS WebGL asset route
/api/v1/workspaces/:wsId/external-projects/assets/:assetId/webgl/*. The CMS
player intentionally renders that document in a sandboxed iframe without
allow-same-origin; the asset route keeps its own restrictive CSP sandbox
policy. New framing exceptions must be path-specific, justified by a documented
product requirement, and preserve an equivalent sandbox boundary.
Layer 2: Application-Level Security
Clarifying Additions
Multiple layers ensure no single point of failure exposes sensitive logic. An attacker must defeat multiple independent security controls to compromise the system, making successful attacks exponentially harder. Higher layers validate and protect the domain before requests reach it. The most critical business logic (the domain core) is protected by multiple security barriers, ensuring it only processes validated, authorized requests. The system becomes safer by distributing security responsibility across layers. Security is not the responsibility of a single component but is woven throughout the architecture, creating a more resilient security posture.5. Auditability and Background Security Workflows
Architectural Choice
Security-relevant actions should be traceable, and heavier security workflows (anomaly scans, notifications, periodic reviews) should run as durable background jobs. Tuturuuu uses Trigger.dev v4task() for background work and native server console logs plus retained platform observability data for server-side diagnostics.
Impact and Justification
Security is not just about prevention; it’s also about detection and accountability. A durable, append-only audit trail would provide:- Forensic Analysis: After an incident, history lets investigators reconstruct what happened, when, and by whom
- Compliance: Regulations (GDPR, SOX, HIPAA) require audit trails of data access and modifications
- Anomaly Detection: Patterns can be analyzed to detect suspicious behavior (e.g., unusual access patterns, privilege escalation)
- Non-Repudiation: Append-only, tamper-evident records provide strong proof of actions
task() API):
Trigger.dev v4 uses the task() builder (@trigger.dev/sdk ^4.4.5). The v2 API (client.defineJob, eventTrigger, io.*) does not exist in this repo. A security-scan task looks like the calendar/schedule tasks under packages/trigger/src:
securityScanTask.trigger({ userId }) (or batchTrigger for many). See the Event-Driven Architecture page for how Tuturuuu actually uses Trigger.dev v4 and native server console logs.
Clarifying Additions
Durable background tasks keep heavy security work off the request path. Anomaly scans and notifications run as Trigger.dev v4 tasks rather than blocking the user’s request. An append-only audit trail would support accountability and forensics. This is the target; today, rely on retained platform observability data, native server console logs, and table-level history for traceability. Compliance benefits from consistent logging. Use the native console method matching severity and retain structured metadata where platform observability wrappers are wired.Security Architecture Summary
Security Best Practices in Tuturuuu
1. Never Trust, Always Verify
- Every request validated at multiple layers
- No assumptions about request origin or authenticity
- All user input sanitized and validated
2. Principle of Least Privilege
- Services granted minimum permissions needed
- Users granted minimum roles required
- API keys scoped to specific operations
3. Fail Securely
- Default deny for authorization decisions
- Explicit permission grants required
- Errors don’t leak sensitive information
4. Defense in Depth
- Multiple layers of security controls
- Redundant security checks
- No single point of failure
5. Audit Everything
- All security-relevant events logged
- Immutable audit trail
- Regular security audits and reviews
Security Under the TanStack + Rust Migration
apps/web (Next.js, port 7803) is being replaced by apps/tanstack-web (TanStack Start) plus apps/backend (Rust, port 7820). Several security responsibilities move accordingly:
- Boundary authentication shifts into the Rust backend (
apps/backend/src/supabase_auth.rs), which validates Supabase access tokens from cookies or Bearer headers against Supabase Auth using the service-role key. - TanStack web must not call
/trpcor raw app APIs directly (a guard,check-tanstack-api-access, enforces this); product data flows throughpackages/internal-apihelpers and REST/api/v1routes. - RLS remains the durable backstop across both stacks because both share the same Supabase project.
Related Documentation
- Authentication - User authentication implementation
- Authorization - Permission system and role-based access
- RLS Policies - Database-level security
- TanStack Start And Rust Migration - How the boundary moves into the Rust backend
- Encapsulation Patterns - Service boundary enforcement
- Event-Driven Architecture - Trigger.dev v4 tasks and background workflows