application.yaml
CORS Configuration
Section titled “CORS Configuration”CORSConfig controls cross-origin resource sharing. Never use * in production with credentials.
web: cors: enabled: true allowed_origins: - "https://app.myapp.com" - "https://admin.myapp.com" allow_credentials: true allow_methods: - GET - POST - PUT - DELETE - PATCH expose_headers: - X-Request-Id max_age: 600The CORSConfig validator rejects allow_credentials=True combined with allowed_origins=['*'] — browsers block such responses. WebConfig construction enforces the same rule in any environment (staging included), so a wildcard-plus-credentials CORS config can never silently ship.
# ✅ Correct — explicit origins with credentialsconfig = CORSConfig(allowed_origins=["https://myapp.com"], allow_credentials=True)
# ❌ ValueError — browser-incompatibleconfig = CORSConfig(allowed_origins=["*"], allow_credentials=True)In production, WebConfig.validate_production_security() blocks wildcard CORS at boot even without credentials:
# Raises ValueError at boot:web: cors: allowed_origins: ["*"]Debug-permissive CORS (explicit opt-in)
Section titled “Debug-permissive CORS (explicit opt-in)”In debug mode (server.debug: true or DEBUG=1), CORS origins are not
widened automatically — an operator who intentionally wants any-origin CORS
must opt in explicitly:
web: cors: debug_permissive: true # wildcard ONLY while debug mode is activeThe wildcard widening applies only while debug_permissive: true and
debug mode is active at boot; the middleware logs a warning when it kicks in.
debug_permissive is ignored outside debug mode.
Breaking change: deployments that previously relied on the implicit debug widening (debug mode + untouched localhost defaults → wildcard) will start rejecting non-listed origins until they set
debug_permissive: true. This is intentional — the silent behaviour is now explicit.
CSRF Protection
Section titled “CSRF Protection”CSRF protection is opt-in: CSRFProtectionMiddleware is added only when
SecurityConfig.security.csrf.enabled is true (default false). When
enabled, csrf.secret_key must be set — token generation and validation fail
closed (ValueError) without it.
web: security: csrf: enabled: true cookie_name: csrf_token header_name: X-CSRF-Token cookie_secure: true # HTTPS only cookie_httponly: true cookie_samesite: Lax token_length: 32 token_ttl: 3600 # 1 hour excluded_paths: - /api/ - /health - /metricsThe CSRFProtectionMiddleware sets a signed cookie on GET requests and validates the X-CSRF-Token header on state-changing methods (POST, PUT, DELETE, PATCH). With a CacheBackendProtocol wired into the middleware, the synchronizer-token pattern is used instead (server-side comparison). The cookie-mode token is an HMAC-signed base64url("{iss}:{ts}:{nonce}").base64url(hmac) value that expires after token_ttl seconds, forcing rotation on stale tokens.
The middleware is wired automatically by WebProvider._setup_middleware when security.csrf.enabled is true (the default via WebConfig); no manual resolution is needed. On every safe method the current token is also exposed via the X-CSRF-Token response header for SPA clients.
Rate Limiting
Section titled “Rate Limiting”RateLimitConfig supports per-path rules with sliding window (Redis-backed) or fixed-window (cache-backed) algorithms:
web: rate_limit: enabled: true default_limit: 100 default_window: 60 storage_backend: redis # or "memory" (dev only) rules: "/api/auth/login": requests: 10 window: 60 burst: 5 "/api/webhook": requests: 200 window: 60The RateLimitMiddleware operates per-IP by default. Use the RateLimiter directly for per-user limits:
from oridecon.web.middleware.rate_limit import RateLimiter
limiter = RateLimiter(window=60, max_requests=30)if await limiter.try_acquire(client_id="user:42"): # proceed passInput Sanitization
Section titled “Input Sanitization”InputSanitizationMiddleware strips null bytes and rejects obvious script-injection patterns from query parameters before they reach controllers:
from oridecon.web.middleware.sanitization import InputSanitizationMiddleware
# Added automatically when WebProvider is configured# Sanitizes: null bytes, <script> tags, javascript: URIsThis is defense-in-depth — validate at the service layer too. The middleware does not inspect request bodies.
Security Headers
Section titled “Security Headers”SecurityHeadersMiddleware emits standard security headers with sensible defaults:
web: security: hsts: enabled: true max_age: 31536000 # 1 year include_subdomains: true preload: false csp: enabled: true directives: default-src: "'self'" script-src: "'self'" style-src: "'self' 'unsafe-inline'" img-src: "'self' data: https: blob:" frame-ancestors: "'none'" base-uri: "'self'" form-action: "'self'" cross_origin: enabled: true opener_policy: same-origin embedder_policy: require-corpUse create_production_config() for secure defaults or create_development_config() for local dev:
from oridecon.web.middleware.security import ( create_production_config, create_development_config,)
production = create_production_config() # strict HSTS, full CSPdevelopment = create_development_config() # relaxed CSP, no HSTSSession Management
Section titled “Session Management”SessionCookieBackend stores session data in signed cookies (default) or server-side via a SessionRepositoryProtocol backend:
web: security: session: cookie_name: session cookie_secure: true cookie_httponly: true cookie_samesite: Lax max_age: 86400 # 24 hoursError Response Hardening
Section titled “Error Response Hardening”oridecon-web never leaks internal tracebacks in production. The DefaultExceptionFilter returns sanitized error bodies:
from oridecon.web.filters import DefaultExceptionFilter
# Debug mode includes stack traces; production strips themfilter = DefaultExceptionFilter(debug=False)Custom error bodies use ProblemDetail (RFC 9457):
from oridecon.web.errors.problem_detail import ProblemDetail
class MyError(HTTPError): def problem_detail(self) -> ProblemDetail: return ProblemDetail( type="https://docs.myapp.com/errors/rate-limit", title="Rate Limit Exceeded", status=429, detail="Too many requests. Retry after 30 seconds.", )