Skip to content

feat: comprehensive security hardening for Nova#367

Open
Taure wants to merge 11 commits intomasterfrom
feat/session-cookie-hardening
Open

feat: comprehensive security hardening for Nova#367
Taure wants to merge 11 commits intomasterfrom
feat/session-cookie-hardening

Conversation

@Taure
Copy link
Collaborator

@Taure Taure commented Mar 12, 2026

Summary

Comprehensive security hardening based on ERLEF Web App Security Best Practices and Phoenix security recommendations.

Commits

  1. Session cookie hardeningHttpOnly, Secure, SameSite=Lax, Path=/ on all session cookies; replace rand:uniform with crypto:strong_rand_bytes for session IDs; configurable via session_cookie_opts app env
  2. Secure headers plugin (nova_secure_headers_plugin) — Sets X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, Permissions-Policy by default; optional HSTS and CSP support
  3. Rate limiting plugin (nova_rate_limit_plugin) — Sliding window ETS-based rate limiter per client IP; configurable paths, limits, windows, and custom key functions; returns 429 with Retry-After
  4. Session lifecycle management — Session rotation (nova_session:rotate/1) for fixation prevention; session timestamps with configurable max age (24h) and idle timeout (1h); periodic cleanup of expired ETS entries
  5. CORS defaults tightened — Changed wildcard * for headers/methods to explicit Content-Type, Authorization and GET, POST, PUT, DELETE, OPTIONS; both now configurable
  6. Force SSL plugin (nova_force_ssl_plugin) — HTTP→HTTPS redirect with 301; supports custom host/port, query string preservation, and path exclusions
  7. Log parameter filtering (nova_log_filter) — OTP logger filter for redacting sensitive params (password, secret, token, etc.); configurable filter list
  8. XSS template fix — Remove |safe filter from nova_error.dtl so erlydtl auto-escaping is not bypassed
  9. Path traversal fix — Reject .. and . segments in nova_file_controller wildcard pathinfo
  10. gitignore — Add erl_crash.dump

Security audit findings addressed

Category Before After
Session cookie security No flags HttpOnly + Secure + SameSite + Path
Session ID generation rand:uniform crypto:strong_rand_bytes
HTTP security headers None 5 default + optional HSTS/CSP
Rate limiting None Per-IP sliding window
Session timeout None Max age + idle timeout + cleanup
Session fixation No rotation rotate/1 on privilege change
CORS Wildcard everything Explicit defaults
TLS enforcement None HTTP→HTTPS redirect plugin
Log filtering None Sensitive param redaction
XSS in templates |safe bypassing Auto-escaped
Path traversal No check .. and . rejected

Test plan

  • All 179 tests pass (up from 141)
  • New tests for every feature: secure headers, rate limiting, session rotate, CORS, force SSL, log filter
  • Existing tests updated and passing
  • Manual: verify Set-Cookie headers in browser dev tools
  • Manual: verify security headers with securityheaders.com
  • Manual: verify rate limiting returns 429

🤖 Generated with Claude Code

Taure and others added 10 commits March 12, 2026 09:00
Set HttpOnly, Secure, SameSite=Lax, and Path=/ on all session cookies
to mitigate XSS cookie theft, man-in-the-middle attacks, and CSRF.
Replace rand:uniform/1 with crypto:strong_rand_bytes/1 for
cryptographically secure session ID generation. Cookie options are
configurable via the session_cookie_opts application environment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a configurable plugin that sets security headers on every response:
- X-Frame-Options: DENY (clickjacking protection)
- X-Content-Type-Options: nosniff (MIME sniffing protection)
- X-XSS-Protection: 1; mode=block
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: restrictive defaults

Optional HSTS and CSP support via plugin options.
All headers are overridable per-application.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sliding window rate limiter using ETS counters. Tracks requests per
client IP within configurable time windows. Returns 429 with
Retry-After header when limit exceeded.

Supports path-prefix filtering and custom key functions for
flexible rate limiting (e.g. per API key, per user).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add nova_session:rotate/1 for session fixation prevention — generates
  new session ID and migrates data on privilege changes (login/logout)
- Add rotate_session/2 callback to nova_session behaviour (optional)
- Add session timestamps (created_at, last_accessed) to ETS backend
- Add periodic cleanup of expired sessions (configurable intervals)
- session_max_age (default 24h) and session_idle_timeout (default 1h)
  configurable via application environment
- Backward-compatible with legacy {SessionId, Data} tuple format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change default Access-Control-Allow-Headers from "*" to
"Content-Type, Authorization" and Access-Control-Allow-Methods from
"*" to "GET, POST, PUT, DELETE, OPTIONS". Both are now configurable
via allow_headers and allow_methods plugin options.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redirects HTTP requests to HTTPS with 301 status. Supports custom
host/port override, query string preservation, and path exclusions
for health checks. Use alongside nova_secure_headers_plugin's HSTS
option for complete TLS enforcement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OTP logger filter that redacts sensitive parameters from log output.
Default filtered keys: password, secret, token, api_key, authorization,
passwd, credit_card. Configurable via filter_parameters app env.

Supports case-insensitive partial matching, nested maps, and both
atom and binary keys. Can be used as a logger filter or called
directly via redact_params/1,2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The extra_msg variable in nova_error.dtl was rendered with |safe,
bypassing erlydtl's auto-escaping. While the content is from internal
crash info, this is a defense-in-depth fix. Changed the error
controller to use newlines instead of <br /> HTML tags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reject path segments containing ".." or "." in wildcard pathinfo
before joining with the static directory path. Without this check,
a request like /static/../../../etc/passwd could escape the
configured static directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Taure Taure changed the title fix: harden session cookie security attributes feat: comprehensive security hardening for Nova Mar 12, 2026
Covers sessions, CSRF, HTTP headers, TLS, rate limiting, CORS, XSS,
SQL injection, authorization, logging, and BEAM-specific concerns.
Includes recommended plugin stack for production deployments and
links to ERLEF, Phoenix, and OWASP references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant