Security Architecture
Seed implements defense-in-depth security using multiple layers of protection to guard against common web vulnerabilities and attacks.
Security Layers Overview
HTTP Security Headers
Seed uses Helmet to set security headers that protect against common web vulnerabilities.
Configuration
File: src/config/helmet.ts
Two configurations are provided:
helmetConfig- Full security headers for HTML responses (documentation)helmetApiConfig- Relaxed configuration for API routes (no CSP)
Content Security Policy (CSP)
Purpose: Prevents XSS attacks by restricting content sources
Configuration (HTML responses only):
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"], // VitePress requires inline styles
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"], // Prevent clickjacking
},
}Protection Against:
- XSS via inline scripts (only allow 'self')
- Data exfiltration via unauthorized connections
- Clickjacking via frame embedding
- Content injection attacks
Note: CSP is disabled for API routes (/mcp, /oauth/*) since they return JSON, not HTML.
Strict Transport Security (HSTS)
Purpose: Enforces HTTPS connections and prevents protocol downgrade attacks
Configuration (production only):
strictTransportSecurity: process.env.NODE_ENV === "production"
? {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true,
}
: falseWhy Production Only?
- Requires valid HTTPS certificate
- Development environments often use HTTP
- Can cause issues with self-signed certificates
Protection Against:
- Man-in-the-Middle attacks
- Protocol downgrade attacks
- SSL stripping
Frame Protection
Purpose: Prevents clickjacking attacks
Headers:
frameguard: {
action: "deny", // X-Frame-Options: DENY
}
// Also enforced via CSP
frameAncestors: ["'none'"]Protection Against:
- Clickjacking via iframe embedding
- UI redressing attacks
MIME Sniffing Protection
Purpose: Prevents browsers from MIME-sniffing responses
Configuration:
noSniff: true // X-Content-Type-Options: nosniffProtection Against:
- MIME confusion attacks
- Content-Type spoofing
Referrer Policy
Purpose: Controls information sent in Referer header
Configuration:
referrerPolicy: {
policy: "strict-origin-when-cross-origin",
}Behavior:
- Same-origin: Send full URL
- Cross-origin (HTTPS → HTTPS): Send origin only
- Cross-origin (HTTPS → HTTP): Send nothing
Protection Against:
- Information leakage via Referer header
- Privacy violations
Additional Headers
DNS Prefetch Control:
dnsPrefetchControl: {
allow: false, // X-DNS-Prefetch-Control: off
}Cross-Origin Policies:
crossOriginOpenerPolicy: {
policy: "same-origin", // COOP: same-origin
}
crossOriginResourcePolicy: {
policy: "cross-origin", // CORP: cross-origin (allows CORS)
}
crossOriginEmbedderPolicy: false // Disabled to allow cross-origin resourcesOrigin Agent Cluster:
originAgentCluster: true // Origin-Agent-Cluster: ?1Hide Server Info:
hidePoweredBy: true // Remove X-Powered-By headerPermissions-Policy Header
Purpose: Restricts browser features to prevent unauthorized access to sensitive APIs
Implementation: Custom middleware at src/middleware/permissions-policy.ts
Since Seed is an MCP server/OAuth proxy, it doesn't need access to any browser features like camera, microphone, geolocation, etc. The Permissions-Policy header (formerly Feature-Policy) disables all unnecessary browser APIs as a defense-in-depth measure.
Configuration:
// All features disabled with () = empty allowlist
const policies = [
"camera=()", // No camera access
"microphone=()", // No microphone access
"geolocation=()", // No location access
"payment=()", // No payment APIs
"usb=()", // No USB device access
"magnetometer=()", // No magnetometer access
"gyroscope=()", // No gyroscope access
"accelerometer=()", // No accelerometer access
"ambient-light-sensor=()", // No ambient light sensor
"autoplay=()", // No autoplay
"encrypted-media=()", // No encrypted media (DRM)
"fullscreen=()", // No fullscreen API
"midi=()", // No MIDI device access
"picture-in-picture=()", // No picture-in-picture
"speaker-selection=()", // No speaker selection
"sync-xhr=()", // No synchronous XHR
"vr=()", // No VR APIs (deprecated)
"xr-spatial-tracking=()", // No XR spatial tracking
"display-capture=()", // No screen capture
];Applied to: All routes via middleware in src/app.ts
Protection Against:
- Unauthorized access to device hardware (camera, microphone, USB)
- Location tracking via geolocation API
- Side-channel attacks via sensors (gyroscope, accelerometer, magnetometer)
- Unauthorized screen capture or recording
- Payment API abuse
Format: feature=(allowlist) where:
()= empty allowlist (feature disabled for all origins)'self'= feature allowed for same origin only*= feature allowed for all origins (not used in Seed)
References:
CORS Policy
Cross-Origin Resource Sharing (CORS) controls which origins can access the API.
Default Allowed Origins
File: src/config/cors.ts
const defaultOrigins = [
"http://localhost",
"http://localhost:3000",
"http://localhost:5173",
"https://claude.ai",
"https://claude.anthropic.com",
];Environment-Based Additions:
CORS_EXTRA_ORIGINS=https://example.com,https://app.example.comConfiguration
export const corsConfig = {
origin: [...defaultOrigins, ...extraOrigins],
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: [
"Content-Type",
"Authorization",
"mcp-session-id",
],
exposedHeaders: [
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-RateLimit-Reset",
],
maxAge: 86400, // 24 hours
};Key Features:
- Credentials Support: Allows cookies and Authorization headers
- Session Headers: Exposes
mcp-session-idfor MCP session tracking - Rate Limit Headers: Exposes rate limit info to clients
- Preflight Caching: 24-hour cache for OPTIONS requests
Protection Against
- Unauthorized cross-origin access
- CSRF attacks (with proper origin validation)
- Data exfiltration from untrusted origins
Origin Validation
Origin validation provides an additional layer of protection against DNS rebinding attacks.
DNS Rebinding Attack Overview
Attack Scenario:
- Attacker controls domain
evil.com - User visits
evil.comwhich loads malicious JavaScript - DNS for
evil.comresolves to127.0.0.1(localhost) - JavaScript makes requests to
http://localhost:3000/mcp - Requests bypass CORS because they appear to come from localhost
How Origin Validation Prevents This:
- Validates
Originheader against whitelist - Rejects requests from non-whitelisted origins
- Works in conjunction with CORS
Implementation
File: src/middleware/origin-validation.ts
Middleware Function:
export function validateOrigin(req: Request, res: Response, next: NextFunction): void {
// Skip if origin validation is disabled
if (!config.security.originValidation.enabled) {
return next();
}
const origin = req.headers.origin;
// Allow requests without Origin header (CLI tools, native apps)
if (!origin) {
return next();
}
// Check against allowed origins
if (isAllowedOrigin(origin)) {
return next();
}
// Log potential attack
logSecurityEvent("origin_blocked", "medium", {
origin,
path: req.path,
ip: req.ip,
});
res.status(403).json({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Forbidden",
data: {
reason: "invalid_origin",
details: "Origin not allowed by server policy",
},
},
id: null,
});
}Pattern Matching
The origin validation supports wildcard patterns for flexible configuration:
Exact Match:
pattern: "https://claude.ai"
// Matches: https://claude.ai
// Rejects: https://api.claude.aiWildcard Subdomain:
pattern: "*.anthropic.com"
// Matches: https://api.anthropic.com
// Matches: https://app.anthropic.com
// Matches: https://anthropic.com
// Rejects: https://fake-anthropic.comWildcard Port:
pattern: "http://localhost:*"
// Matches: http://localhost:3000
// Matches: http://localhost:5173
// Rejects: http://localhost (no port)Implementation:
export function matchOriginPattern(origin: string, pattern: string): boolean {
// Exact match
if (origin === pattern) {
return true;
}
// Wildcard subdomain match (e.g., "*.anthropic.com")
if (pattern.startsWith("*.")) {
const domain = pattern.slice(2);
const url = new URL(origin);
return url.hostname === domain || url.hostname.endsWith("." + domain);
}
// Wildcard port match (e.g., "http://localhost:*")
if (pattern.endsWith(":*")) {
const basePattern = pattern.slice(0, -2);
const url = new URL(origin);
if (!url.port) return false;
const baseUrl = `${url.protocol}//${url.hostname}`;
return baseUrl === basePattern;
}
return false;
}Configuration
File: src/config/security.ts
export const securityConfig = {
originValidation: {
enabled: process.env.ORIGIN_VALIDATION_ENABLED !== "false",
},
allowedOrigins: [
"http://localhost",
"http://localhost:*",
"https://claude.ai",
"*.anthropic.com",
...(process.env.ALLOWED_ORIGINS?.split(",").map((o) => o.trim()) ?? []),
],
};Environment Variables:
ORIGIN_VALIDATION_ENABLED=true
ALLOWED_ORIGINS=https://example.com,https://app.example.comBehavior
Requests WITHOUT Origin Header:
- Allowed through (CLI tools, native apps, Postman)
- Browser requests always include Origin header
Requests WITH Origin Header:
- Must match allowed origins list
- Pattern matching applied (wildcards supported)
- Blocked requests return 403 Forbidden
Observability
Security Event Logging:
logSecurityEvent("origin_blocked", "medium", {
origin: "https://evil.com",
path: "/mcp",
ip: "192.168.1.100",
});Log Format:
{
"timestamp": "2026-01-06T12:00:00Z",
"level": "warn",
"event": "origin_blocked",
"severity": "medium",
"origin": "https://evil.com",
"path": "/mcp",
"ip": "192.168.1.100",
"service": "seed"
}JWT Authentication
JWT validation ensures only authenticated users with valid tokens can access protected endpoints.
Key Security Features
Signature Verification:
- Uses JWKS (JSON Web Key Set) from OIDC provider
- Supports RSA and ECDSA signature algorithms
- Automatic key rotation via cache refresh
Claims Validation:
- Issuer (
iss): Must matchOIDC_ISSUER - Audience (
aud): Must matchOIDC_AUDIENCE - Expiration (
exp): Token must not be expired - Not Before (
nbf): Token must be valid at current time (if present) - Subject (
sub): User ID must be present
Implementation: See Authentication Flow for detailed documentation.
Protection Against
- Unauthorized access (missing/invalid tokens)
- Token forgery (signature verification)
- Token replay attacks (expiration validation)
- Cross-tenant attacks (issuer/audience validation)
OAuth Security
The OAuth 2.1 implementation includes several security features per RFC standards.
PKCE (Proof Key for Code Exchange)
Purpose: Prevents authorization code interception attacks
How It Works:
- Client generates
code_verifier(random string) - Client creates
code_challenge(SHA256 hash of verifier) - Authorization request includes
code_challenge - Token request includes original
code_verifier - Server verifies that SHA256(code_verifier) matches stored challenge
Validation:
// In token endpoint
const storedCodeChallenge = await getStoredChallenge(code);
const computedChallenge = base64UrlEncode(sha256(code_verifier));
if (computedChallenge !== storedCodeChallenge) {
throw new Error("Invalid code_verifier");
}Protection Against:
- Authorization code interception
- Public client attacks
Redirect URI Validation
Purpose: Prevents open redirect vulnerabilities
Validation Rules (RFC 7591):
function validateRedirectUri(uri: string): string | null {
const url = new URL(uri);
// Must use HTTPS except localhost
const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
if (url.protocol !== "https:" && !isLocalhost) {
return "redirect_uri must use https";
}
// Must not contain fragment (per RFC 6749)
if (url.hash) {
return "redirect_uri must not contain a fragment";
}
return null; // Valid
}Enforcement:
- During Dynamic Client Registration (DCR)
- During authorization flow
- Exact match required (no partial matching)
Configuration Limits:
// Maximum redirect URIs per client
maxRedirectUris: 10Protection Against:
- Open redirect attacks
- Phishing via redirect manipulation
- Token theft via malicious redirects
Client Storage Security
Redis-Backed Storage:
- Clients stored with automatic expiration (default: 30 days)
- No sensitive credentials stored (public clients only)
- Client IDs prefixed for namespacing
Client ID Generation:
function generateClientId(): string {
const prefix = "seed-";
// 12-character alphanumeric random string
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
const randomPart = Array.from({ length: 12 }, () =>
chars[Math.floor(Math.random() * chars.length)]
).join("");
return prefix + randomPart;
}TTL Management:
- Automatic expiration prevents stale clients
- Configurable via
DCR_CLIENT_TTL(default: 2592000 seconds = 30 days)
Implementation: See OAuth 2.1 Implementation for detailed documentation.
Rate Limiting
Rate limiting protects against abuse and ensures fair resource usage.
Key Security Features
Distributed Limits:
- Redis-backed sliding window algorithm
- Works across multiple server instances
- Per-IP and global limits
Endpoint-Specific Limits:
- MCP Endpoints: 100 requests/minute per IP
- DCR Endpoint: 10 registrations/hour per IP
Graceful Degradation:
- Falls back to allowing requests if Redis unavailable
- Prevents rate limiting from causing downtime
Implementation: See Rate Limiting for detailed documentation.
Protection Against
- Brute force attacks
- Distributed denial of service (DDoS)
- Resource exhaustion
- API abuse
Security Best Practices
Environment-Based Configuration
Development:
- HSTS disabled (HTTP allowed)
- More verbose logging
- Less strict rate limits
Production:
- HSTS enabled (HTTPS enforced)
- Structured JSON logging
- Strict rate limits
Configuration Pattern:
strictTransportSecurity: process.env.NODE_ENV === "production"
? { maxAge: 31536000, includeSubDomains: true, preload: true }
: falseDefense in Depth
Multiple security layers provide redundancy:
- If origin validation fails, CORS still protects
- If CORS is bypassed, JWT auth still protects
- If auth is somehow bypassed, rate limiting still protects
Fail-Secure Defaults
Examples:
- Rate limiting enabled by default
- Origin validation enabled by default
- Authentication required by default
- Helmet headers applied by default
Disabling Security (not recommended for production):
RATE_LIMIT_ENABLED=false
ORIGIN_VALIDATION_ENABLED=false
AUTH_REQUIRED=falseObservability
All security events are logged with appropriate severity:
- Info: Successful authentication
- Warn: Rate limit exceeded, origin blocked
- Error: JWT validation failures, Redis errors
Structured Logging enables security monitoring and incident response:
{
"timestamp": "2026-01-06T12:00:00Z",
"level": "warn",
"event": "origin_blocked",
"severity": "medium",
"origin": "https://evil.com",
"path": "/mcp",
"ip": "192.168.1.100"
}Configuration Reference
Environment Variables
# Helmet Security Headers
NODE_ENV=production # Enable HSTS in production
# CORS
CORS_EXTRA_ORIGINS=https://example.com,https://app.example.com
# Origin Validation
ORIGIN_VALIDATION_ENABLED=true
ALLOWED_ORIGINS=https://example.com,https://app.example.com
# JWT Authentication
AUTH_REQUIRED=true # Require authentication (default: true)
OIDC_ISSUER=https://auth.example.com/application/o/my-app/
OIDC_AUDIENCE=my-client-id
OIDC_JWKS_URL=https://auth.example.com/application/o/my-app/jwks/
# Rate Limiting
RATE_LIMIT_ENABLED=true # Enable rate limiting (default: true)
MCP_RATE_LIMIT_WINDOW_MS=60000 # 1 minute
MCP_RATE_LIMIT_MAX=100 # 100 requests per IP per minute
DCR_RATE_LIMIT_WINDOW_MS=3600000 # 1 hour
DCR_RATE_LIMIT_MAX=10 # 10 registrations per IP per hour
# OAuth Security
DCR_CLIENT_TTL=2592000 # 30 daysImplementation Files
- Helmet Config:
src/config/helmet.ts- Security header configuration - Security Config:
src/config/security.ts- Origin validation configuration - CORS Config:
src/config/cors.ts- CORS policy configuration - Origin Validation:
src/middleware/origin-validation.ts- DNS rebinding protection - Auth Middleware:
src/middleware/auth.ts- JWT validation - Rate Limiter:
src/middleware/distributed-rate-limit.ts- Rate limiting implementation - OAuth DCR:
src/routes/oauth-register.ts- Redirect URI validation
Related Documentation
- Authentication Flow - JWT validation details
- Rate Limiting - Rate limiting implementation
- Middleware Architecture - Middleware execution order
- OAuth 2.1 Implementation - OAuth security features
- Configuration System - Security configuration
- Observability - Security event logging