Skip to content

Security: WavestormSoftware/Elsie

docs/security.md

Security notes

Elsie is a small HTTP stack. Treat production like any other custom host.

Defaults that matter

Setting Default Production guidance
Cookie TicketKey Required (or dev flag) Long random secret; never commit
AllowInsecureDevelopmentKey off unless you set it Never in prod
Cookie Secure true (strict) Set false only for plain-HTTP local dev; __Host- prefix forbids it anyway
Cookie MaxAge 8 h Emitted in Set-Cookie; ExpireTimeSpan drives the actual ticket/session lifetime
Cookie CookiePrefix unset __Host- validated at startup (Secure + Path=/ + no Domain); name must start with the prefix
Server-side sessions off (client-side v1 ticket) Opt into SessionStore for logout/revocation; opaque ≥128-bit v2 ids
Cookie / antiforgery SameSite ElsieSameSite.Lax / Strict Use None + Secure only for cross-site HTTPS
UseForwardedHeaders false Enable only behind a trusted proxy
Rate-limit partition RemoteIp only Use ForwardedPartitionKey only with trusted XFF
MaxRequestBodyBytes 10 MiB Lower for APIs that don’t need big posts
RequestBodyIdleTimeout 30s Slow-loris guard on body reads
DisableContinue false Leave false so Expect: 100-continue works
AbortRequestsOnClientDisconnect true Cancels RequestAborted on peer close
ShutdownAbortConnections true Force-close sockets after drain timeout
Static files path-safe Keep roots outside secrets

HTTP/1.1 framing

  • Rejects Content-Length + Transfer-Encoding together (smuggling).
  • Rejects differing duplicate Content-Length values; equal duplicates accepted.
  • Transfer-Encoding must be chunked only (no coding lists).
  • Chunk-size / request lines capped (MaxRequestLineLength, default 8 KiB).
  • Request paths canonicalized at the host boundary (//, ./..); root-escaping .., \\, and NUL → 400.
  • Responses include RFC 7231 Date unless the app set one.
  • Compression honors Accept-Encoding q-values and sets Vary: Accept-Encoding.
  • Request bodies stream; idle timeout on body reads → 408 (ElsieRequestException).

Cookie sessions

  • Tickets are AES-GCM sealed claims + expiry.
  • TicketKeyFromString requires ≥ 16 characters (SHA-256 → 32-byte key).
  • Set Secure = true and SameSite = ElsieSameSite.Lax|Strict|None for HTTPS.

Headers / keys

  • ElsieAuth.RequireApiKey / RequireHeader use constant-time compare.
  • Prefer TLS (proxy or Elsie HTTPS) for anything sensitive.
  • Baseline browser headers: ElsieSecurityHeaders.DefaultAfter() (after-style middleware transform).

Reverse proxy

.Server(o =>
{
    o.UseForwardedHeaders = true; // only if proxy strips untrusted X-Forwarded-*
    o.MaxRequestBodyBytes = 1_000_000;
    o.MaxConcurrentConnections = 10_000;
    o.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
    o.RequestBodyIdleTimeout = TimeSpan.FromSeconds(30);
})

Recommended: terminate TLS on the proxy; Elsie listens on loopback HTTP/1.1.

Antiforgery

Browser cookie apps should register AddElsieAntiforgery and Use(ElsieAntiforgeryService.RequireAntiforgery()) on mutating routes. Double-submit cookie + X-CSRF-TOKEN header or form field __RequestVerificationToken (Base64Url tokens; see Dashboard sample).

What we test

Automated tests cover (Web + Auth suites):

  • Body over limit → 413; oversized headers → client error
  • Static path traversal / encoded .. does not leak files
  • Forwarded headers on/off + CRLF host rejection
  • Cookie ticket tamper / wrong key / expired / garbage
  • Short ticket secrets rejected; missing key without dev flag fails DI setup
  • API-key gate rejects wrong keys
  • HTTP/1.1 parser body/header limits + adversarial framing (CL+TE, dup CL, TE list, giant chunk line)
  • Expect: 100-continue, path canonicalization, body idle → 408, shutdown abort
  • 405 / 404 problem bodies do not leak handler data
  • Antiforgery header + form field paths
  • Static path directory-boundary (sibling root-prefix)
  • Response header / cookie attribute / download-name CR/LF rejection
  • Unsafe X-Request-Id values are not echoed

CI / supply chain

Repo CI runs dotnet list package --vulnerable and packs all package IDs. Dependabot watches NuGet + GitHub Actions weekly (.github/dependabot.yml).

Out of scope (for now)

  • Full HTTP/2 adversarial fuzzing / h2spec CI
  • Full OIDC middleware (helpers + PKCE only; PrincipalFromIdToken requires JWT validation unless allowUnvalidated: true)
  • WAF / rate limit at the edge (use proxy + Elsie rate-limit middleware)

See also

There aren't any published security advisories