The security posture of the LastHour backend. Production deployments should
treat the defaults in appsettings.json as development values and override
them per the checklist at the end.
| Layer | Mechanism |
|---|---|
| Authentication boundary | Not yet implemented (Epic 01). The ICurrentUser abstraction isolates the host from authentication; the API currently runs with an anonymous/default principal in non-request contexts. |
| Authorization | Out of scope until identity lands; the audit middleware already records rejected access attempts. |
| Rate limiting | Fixed/sliding window, token bucket, concurrency, or no-limit per policy; default global policy is a 120/min fixed window with PartitionBy=Global and no queuing (bursts are rejected with 429, not buffered). Policy configuration is validated at startup (unique names, per-algorithm limits, header-name requirement). |
| Request limits | Kestrel-level limits: max request body 10 MiB, multipart 128 MiB, header timeout 30s, keep-alive 2m10s, minimum data-rate enforcement to slow-loris. |
| CORS | Off by configuration risk: AllowAnyOrigin=false, AllowCredentials=false, explicit AllowedOrigins required. |
| Security headers | SecurityHeadersMiddleware applies the configured headers on every response. |
| Correlation ID | Every request carries/echoes X-Correlation-ID; it appears in logs and problem-details so incidents trace across layers. |
| Audit trail | Rejected access (401/403/429) and server errors (5xx) are written to a dedicated audit log, separate from operational logs. |
| Error contract | Failures return RFC 7807 problem details; unhandled exceptions are caught by the exception handler and their connection-string/secret references are redacted before logging. |
- Connection strings and other secrets are injected at startup from
LASTHOUR_SECRET_*environment variables (seedocs/CONFIGURATION.md), so credentials are never committed. - The audit logger and request logger never write request bodies, query strings, headers, passwords, tokens, or secrets.
- The problem-details exception handler redacts
Password=-style connection-string segments from any logged exception message. docker/.env.exampleis explicitly placeholders-only; never commit real secrets.
The CI pipeline runs dotnet list LastHour.Backend.sln package --vulnerable
after restore and fails the build when any known-vulnerable package is
reported. Keep the CPM versions in Directory.Packages.props current as part
of the regular maintenance cycle.
- Non-root
appuser; noroot-owned writable paths needed at runtime. DOTNET_EnableDiagnostics=0(no diagnostics IPC exposed).- Only port 8080 is exposed;
postgres,redis,seq, andpgadminhost ports are bound to127.0.0.1for local development. - The CI
docker-buildjob builds the image on every push/PR, so a broken or supply-chain-changed base image fails the pipeline.
AllowedHosts: "*"in the base config accepts any host header. Production must set it to the canonical hostnames — host-header validation rejects spoofedHostheaders used in cache-poisoning and link-injection attacks.- The ForwardedHeaders middleware is configured and validated by options;
place the API behind a reverse proxy that sets
X-Forwarded-Proto,X-Forwarded-Host, andX-Forwarded-Forso HTTPS detection and client IPs are correct and the forwarded headers themselves are stripped at the edge.
Identity, users, and access-control enforcement are explicitly out of scope
for the hardening sprint. The seams are ready: ICurrentUser/ICurrentTenant
abstractions, the audit trail recording access outcomes, and the
IsInRole-style checks on the default principal that currently deny
everything. Implement authentication before exposing any protected endpoint.