Elsie is a small HTTP stack. Treat production like any other custom host.
| 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 |
- Rejects Content-Length + Transfer-Encoding together (smuggling).
- Rejects differing duplicate Content-Length values; equal duplicates accepted.
Transfer-Encodingmust 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
Dateunless the app set one. - Compression honors
Accept-Encodingq-values and setsVary: Accept-Encoding. - Request bodies stream; idle timeout on body reads → 408 (
ElsieRequestException).
- Tickets are AES-GCM sealed claims + expiry.
TicketKeyFromStringrequires ≥ 16 characters (SHA-256 → 32-byte key).- Set
Secure = trueandSameSite = ElsieSameSite.Lax|Strict|Nonefor HTTPS.
ElsieAuth.RequireApiKey/RequireHeaderuse constant-time compare.- Prefer TLS (proxy or Elsie HTTPS) for anything sensitive.
- Baseline browser headers:
ElsieSecurityHeaders.DefaultAfter()(after-style middleware transform).
.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.
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).
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-Idvalues are not echoed
Repo CI runs dotnet list package --vulnerable and packs all package IDs. Dependabot watches NuGet + GitHub Actions weekly (.github/dependabot.yml).
- Full HTTP/2 adversarial fuzzing / h2spec CI
- Full OIDC middleware (helpers + PKCE only;
PrincipalFromIdTokenrequires JWT validation unlessallowUnvalidated: true) - WAF / rate limit at the edge (use proxy + Elsie rate-limit middleware)