Kit ships with a defense-in-depth security architecture that protects your application at multiple layers. Every API request passes through rate limiting, input validation, sanitization, security headers, and CORS — all pre-configured and production-ready out of the box.
This page provides the architectural overview. For implementation details, see Rate Limiting & Validation and Headers & CORS.
Defense-in-Depth Architecture
Security is implemented in five concentric layers. An attack must bypass all layers to succeed — if any single layer catches it, the request is blocked:
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: Middleware │
│ CORS preflight, Clerk authentication, security headers │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Layer 2: Rate Limiting │ │
│ │ Category-based limits (upload, email, payments) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Layer 3: Input Validation │ │ │
│ │ │ Zod schemas for every API endpoint │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ │ │ Layer 4: Sanitization │ │ │ │
│ │ │ │ XSS, SQL, path traversal, URLs │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ ┌─────────────────────────────┐ │ │ │ │
│ │ │ │ │ Layer 5: Error Handling │ │ │ │ │
│ │ │ │ │ Safe responses, no leaks │ │ │ │ │
│ │ │ │ └─────────────────────────────┘ │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Each layer operates independently — they do not depend on each other to function. This means disabling rate limiting (for example, in local development without Redis) does not weaken input validation or security headers.
Middleware Chain
Every request that reaches your application passes through the middleware chain defined in
middleware.ts. The chain executes in a specific order, with each step able to short-circuit the request:Incoming Request
|
v
1. Test Environment Check
|--- Test/CI? ──> NextResponse.next() (bypass all security)
|
v
2. Blog Feature Flag
|--- Blog disabled + /blog route? ──> Redirect to /
|
v
3. CORS Preflight
|--- OPTIONS request? ──> Return 204 with CORS headers (stop here)
|
v
4. Clerk Authentication
|--- Public route? ──> Continue (auth state available but not required)
|--- Protected route? ──> auth.protect() (redirect to /login if unauthenticated)
|
v
5. Security Headers
|--- Apply HSTS, X-Frame-Options, CSP, Permissions-Policy, etc.
|
v
6. CORS Response Headers
|--- Add Access-Control-Allow-Origin and related headers
|
v
Response
The middleware is designed for fail-safe operation. If any step throws an error, the catch block returns
NextResponse.next() to avoid blocking legitimate requests:src/middleware.ts — Fail-safe middleware entry
// Main middleware function with security enhancements
async function middleware(request: NextRequest) {
// Always bypass in test/CI environments
if (isTestEnvironment()) {
return NextResponse.next()
}
Clerk and security headers are loaded via dynamic
import() inside the middleware function. This avoids bundling heavy dependencies at the top level and allows the middleware to gracefully handle missing packages (e.g., during testing without Clerk installed).Public vs Protected Routes
The middleware distinguishes between public routes (accessible to anyone) and protected routes (requiring authentication). Public routes are defined as an explicit allowlist:
src/middleware.ts — Public routes configuration
// Define public routes that don't require authentication
const isPublicRoute = createRouteMatcher([
'/login(.*)',
'/register(.*)',
'/privacy(.*)',
'/terms(.*)',
'/imprint(.*)',
'/payment/(.*)',
'/email-preview(.*)',
'/logout(.*)',
'/api/health(.*)',
'/api/pricing(.*)',
'/api/webhooks/lemonsqueezy(.*)',
'/api/webhooks/resend(.*)',
'/api/webhooks/clerk(.*)',
'/robots.txt',
'/sitemap.xml',
])
Route Categories
| Category | Routes | Auth Required | Notes |
|---|---|---|---|
| Marketing | /, /about, /blog, /pricing, /contact | No | Public-facing pages |
| Auth | /login, /register, /logout | No | Clerk handles auth flow |
| Public API | /api/health, /api/pricing, /api/contact, /api/newsletter | No | Open endpoints |
| Webhooks | /api/webhooks/lemonsqueezy, /api/webhooks/resend, /api/webhooks/clerk | No | Signature-verified externally |
| SEO | /robots.txt, /sitemap.xml | No | Search engine files |
| Dashboard | /dashboard/** | Yes | All dashboard routes |
| Protected API | /api/** (not listed above) | Yes | All other API endpoints |
Any route not in the public list requires a valid Clerk session. Unauthenticated users are redirected to
/login.Fail-Open Philosophy
Kit's security system is designed to degrade gracefully rather than fail catastrophically. This is a deliberate architectural decision:
| Scenario | Behavior | Rationale |
|---|---|---|
| Redis unavailable | Rate limiting disabled, requests allowed | Don't block users due to infrastructure issues |
| Clerk fails to load | Middleware bypassed | Don't break the app if auth service is down |
| Security headers import fails | Request continues without headers | Headers are a defense layer, not a gatekeeper |
| Rate limit check throws | Request allowed through | Fail-open is safer than fail-closed for SaaS |
Error in any security layer?
|
├── Log warning (for monitoring)
|
└── Allow request to continue
(other layers still active)
Fail-open means your application stays available during partial outages, but it also means security layers can silently degrade. Always monitor for warning logs like
"Rate limiting disabled - Redis not available" in production. Set up alerts for these patterns so you can respond to infrastructure issues quickly.The rationale: for a SaaS application, blocking legitimate paying customers is a worse outcome than temporarily allowing a few extra requests. Each layer logs warnings when it degrades, so you can detect and fix the root cause.
Safe Error Handling
The fifth and innermost layer ensures that error responses never leak sensitive information. Kit includes
sanitizeErrorMessage() which strips stack traces, database connection strings, and internal details from API responses:Development (NODE_ENV=development):
Error: "Connection refused at postgres://user:pass@host:5432"
→ Returned as-is (helpful for debugging)
Production (NODE_ENV=production):
Error: "Connection refused at postgres://user:pass@host:5432"
→ Returned as: "An unexpected error occurred"
Known safe errors (any environment):
Error: "Validation failed: email is required"
→ Returned as-is (safe for users to see)
Errors starting with known safe prefixes (
Validation failed, Invalid, Required, Not found, Unauthorized, Forbidden) pass through unchanged. All other errors are replaced with a generic message in production.OWASP Coverage
Kit's security layers address the most common web application vulnerabilities:
| OWASP Category | Kit Protection | Layer |
|---|---|---|
| Injection (SQL, XSS, Command) | Zod validation + sanitization functions | 3 & 4 |
| Broken Authentication | Clerk middleware with route protection | 1 |
| Sensitive Data Exposure | Security headers (HSTS, no-sniff) + safe error messages | 1 & 5 |
| Broken Access Control | Public/protected route separation + Clerk auth.protect() | 1 |
| Security Misconfiguration | Pre-configured security headers + CORS | 1 |
| Cross-Site Scripting (XSS) | sanitizeHtml() + X-XSS-Protection header | 1 & 4 |
| Rate Limiting Abuse | Category-based rate limiting with Upstash Redis | 2 |
Key Files
| File | Purpose | Lines |
|---|---|---|
apps/boilerplate/src/middleware.ts | Middleware chain — CORS, Clerk auth, security headers | 172 |
apps/boilerplate/src/lib/security/api-rate-limiter.ts | Category-based rate limiting with Upstash Redis | 349 |
apps/boilerplate/src/lib/security/rate-limit-middleware.ts | withRateLimit() middleware factory for API routes | 231 |
apps/boilerplate/src/lib/security/sanitization.ts | XSS, filename, URL, email, SQL, JSON sanitization | 342 |
apps/boilerplate/src/lib/security/security-headers.ts | Security headers generation (HSTS, CSP, CORP, etc.) | 200 |
apps/boilerplate/src/lib/security/cors-middleware.ts | CORS configuration and middleware | 216 |
apps/boilerplate/src/lib/validations/api-schemas.ts | Centralized Zod schemas for all API endpoints | 208 |