8.4 KiB
CORS Implementation - Security Enhancement
Overview
This document describes the implementation of a secure CORS (Cross-Origin Resource Sharing) configuration system that replaces the previous wildcard (*) configuration with an explicit origin whitelist to prevent CSRF attacks.
Implementation Summary
1. CORSConfig Structure (api/rest/router.go)
Created a comprehensive CORSConfig struct with the following fields:
type CORSConfig struct {
AllowedOrigins []string // Whitelist of allowed origins
AllowedMethods []string // Allowed HTTP methods
AllowedHeaders []string // Allowed HTTP headers
AllowCredentials bool // Whether to allow credentials
MaxAge int // Cache duration for preflight responses
}
Key Security Features:
- Wildcards (
*) are explicitly NOT supported - Origins must be explicitly whitelisted
- Credentials can only be enabled with specific origins
2. CORS Middleware Implementation
The newCORSMiddleware() function implements:
- Origin Validation: Checks incoming
Originheader against whitelist - Exact Match Required: Only sets CORS headers if origin is in whitelist
- Never Uses Wildcard: Always returns the exact origin, never
* - Preflight Handling: Properly handles OPTIONS requests
- Configurable Headers: All CORS headers are configurable
Code Location: api/rest/router.go:98-149
3. Configuration System
Config Structure (internal/config/config.go)
Added CORSConfig to the main application configuration:
type CORSConfig struct {
AllowedOrigins []string `mapstructure:"allowed_origins"`
AllowedMethods []string `mapstructure:"allowed_methods"`
AllowedHeaders []string `mapstructure:"allowed_headers"`
AllowCredentials bool `mapstructure:"allow_credentials"`
MaxAge int `mapstructure:"max_age"`
}
Default Values (internal/config/config.go:205-210)
AllowedOrigins: []string{} // Empty by default
AllowedMethods: []string{"GET", "POST", "OPTIONS", "DELETE"} // Standard REST methods
AllowedHeaders: []string{"Content-Type", "Authorization"} // Common headers
AllowCredentials: false // Disabled by default
MaxAge: 3600 // 1 hour
4. Configuration Validation (internal/config/config.go:263-282)
Implemented validateCORS() that enforces:
- Wildcard Rejection:
*is not allowed inallowed_origins - Origin Format Validation: Origins must start with
http://orhttps:// - Credentials Validation:
allow_credentialsrequires at least one origin
Example Error Messages:
"wildcard (*) is not allowed in CORS allowed_origins for security reasons - specify exact origins instead""invalid origin format: example.com - origins must start with http:// or https://""allow_credentials is enabled but no origins are allowed - this configuration is ineffective"
5. Updated Server Initialization (cmd/server/main.go:288-326)
The startRESTServer() function now:
- Converts config CORS to
rest.CORSConfig - Logs CORS configuration on startup
- Warns if no origins are configured
- Passes CORS config to router
Example Log Output:
CORS enabled for origins: [http://localhost:3000 http://localhost:8080]
Or:
CORS has no allowed origins configured - all cross-origin requests will be blocked
6. Configuration Example (config.yaml:112-146)
Development Configuration
cors:
allowed_origins:
- "http://localhost:3000" # React/Next.js dev port
- "http://localhost:8080" # Vue/Angular dev port
- "http://localhost:5173" # Vite dev server
allowed_methods:
- "GET"
- "POST"
- "OPTIONS"
- "DELETE"
allowed_headers:
- "Content-Type"
- "Authorization"
allow_credentials: false
max_age: 3600
Production Configuration (Example)
cors:
allowed_origins:
- "https://app.example.com"
- "https://dashboard.example.com"
- "https://api-docs.example.com"
allowed_methods:
- "GET"
- "POST"
- "OPTIONS"
- "DELETE"
allowed_headers:
- "Content-Type"
- "Authorization"
allow_credentials: true # Enable for auth tokens
max_age: 3600
Test Coverage
1. CORS Middleware Tests (api/rest/cors_test.go)
Test Cases:
- ✅
TestCORSMiddleware_AllowedOrigin: Verifies allowed origins are accepted - ✅
TestCORSMiddleware_BlockedOrigin: Verifies non-whitelisted origins are rejected - ✅
TestCORSMiddleware_PreflightRequest: Tests OPTIONS preflight handling - ✅
TestCORSMiddleware_Credentials: Verifies credential header handling - ✅
TestCORSMiddleware_NoWildcard: Ensures wildcard is never returned - ✅
TestCORSMiddleware_EmptyConfig: Tests secure default (no origins) - ✅
TestCORSMiddleware_MaxAge: Tests cache duration configuration - ✅
TestDefaultCORSConfig: Verifies default configuration values
Total: 8 test functions, 21 test cases
2. CORS Validation Tests (internal/config/cors_test.go)
Test Cases:
- ✅
TestValidateCORS_WildcardRejection: Wildcard origins are rejected - ✅
TestValidateCORS_InvalidOriginFormat: Invalid origin formats are rejected - ✅
TestValidateCORS_CredentialsWithoutOrigins: Credentials require origins - ✅
TestValidateCORS_ValidConfigurations: Valid configs are accepted - ✅
TestValidateCORS_MultipleOrigins: Multiple origins with wildcard rejected - ✅
TestValidateCORS_EdgeCases: Edge cases handled correctly
Total: 6 test functions, 16 test cases
Security Improvements
Before
w.Header().Set("Access-Control-Allow-Origin", "*") // ❌ CSRF Vulnerability
After
// Only set CORS headers if origin is in whitelist
if allowed {
w.Header().Set("Access-Control-Allow-Origin", origin) // ✅ Exact origin
}
// Otherwise, no CORS headers are set (browser blocks the response)
Security Guarantees
- No Wildcard: The system makes it impossible to configure wildcard CORS
- Explicit Whitelist: All allowed origins must be explicitly configured
- Validation at Startup: Invalid configurations are rejected before the server starts
- Default Secure: Empty origin list by default (most restrictive)
- Environment-Specific: Different configs for dev/staging/production
Migration Guide
For Development
Update your config.yaml to include localhost origins:
cors:
allowed_origins:
- "http://localhost:3000"
- "http://localhost:8080"
For Production
Configure your production origins:
cors:
allowed_origins:
- "https://yourdomain.com"
- "https://app.yourdomain.com"
allow_credentials: true
Environment Variables
You can also configure CORS via environment variables:
export NOTIFIER_CORS_ALLOWED_ORIGINS="https://app.example.com,https://dashboard.example.com"
export NOTIFIER_CORS_ALLOW_CREDENTIALS=true
Acceptance Criteria
✅ CORS whitelist fully configurable - via config.yaml or environment variables
✅ Wildcard configuration is impossible - validation rejects * at startup
✅ Security headers properly set - only for whitelisted origins
✅ Environment-specific configs work - examples provided for dev/prod
✅ No CSRF vulnerability - wildcard eliminated, exact origins only
✅ Comprehensive tests - 37 test cases covering all scenarios
Files Modified
api/rest/router.go- CORS config struct and middlewareapi/rest/cors_test.go- CORS middleware tests (NEW)internal/config/config.go- CORS configuration structureinternal/config/cors_test.go- CORS validation tests (NEW)cmd/server/main.go- Server initialization with CORS configconfig.yaml- Example CORS configurationinternal/auth/bootstrap.go- Fixed unrelated string formatting buginternal/auth/keystore_hybrid.go- Fixed unrelated rate limiter signature
Additional Fixes
While implementing CORS, also fixed pre-existing build errors:
- String multiplication in
bootstrap.go(changed tostrings.Repeat) - Rate limiter signature mismatch in
keystore_hybrid.go - Missing parameter in
service_retention_test.go
References
- OWASP CORS: https://owasp.org/www-community/attacks/csrf
- MDN CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- RFC 6454: The Web Origin Concept