ADR-019: Security Request-Edge Middleware¶
Status: Accepted
Date: 2026-06-04
Backfilled from: tldw_Server_API/app/core/Security/README.md
Decision owner: TASK-2247 confirmation and TASK-2248 backfill scope
Related task: TASK-2248
Related spec/plan: Docs/ADR/inventory/2026-06-04-security-confirmation-audit.md
Decision¶
Request-edge Security middleware is owned by the Security module: normal startup installs setup access guard, setup CSP, and security headers; request ID and drain-gate middleware are always installed; CSP behavior is path-scoped; and production keeps security headers enabled by default unless explicitly disabled.
Context¶
The project has several request-edge controls that need consistent startup wiring and path-sensitive behavior: request correlation, shutdown draining, setup UI access, setup-specific CSP, documentation/API CSP differences, and HTTP hardening headers. Keeping these concerns in one Security module boundary gives endpoint and feature owners a clear place to add tests and prevents each router from inventing its own request-edge policy.
The TASK-2247 confirmation audit verified the current behavior that bounds this ADR:
app/main.pyimports and wires Security middlewares during startup.- Test startup still installs setup CSP and setup access guard, while explicitly skipping nonessential security headers in test mode.
- Normal startup installs setup CSP and setup access guard, computes
ENABLE_SECURITY_HEADERS, defaults production security headers on when the variable is absent, and installsSecurityHeadersMiddlewarewhen enabled. DrainGateMiddlewareandRequestIDMiddlewareare always installed.SecurityHeadersMiddlewaresets response hardening headers, removesServer, applies path-scoped CSP behavior for setup/docs/API surfaces, and only enables HSTS whenSECURITY_ENABLE_HSTSis true and the request is HTTPS orX-Forwarded-Proto: https.RequestIDMiddlewaresanitizes or generates request and session identifiers, stores them on request state, propagates response headers, and mirrors request IDs into tracing baggage.SetupAccessGuardMiddlewaregates/setup, permits loopback access, and applies denylist, allowlist, and explicit remote setup settings.SetupCSPMiddlewareapplies only to/setup, permits inline scripts, permitsevalby default, and removesevalwhenTLDW_SETUP_NO_EVALis set.
This ADR deliberately covers request-edge middleware only. It does not accept a project-wide outbound egress/SSRF decision, a universal secret-management adoption claim, or a serialization policy decision.
Alternatives considered¶
| Option | Why rejected |
|---|---|
| Scatter request-edge behavior across feature routers | Makes setup access, CSP, request IDs, drain handling, and HTTP hardening dependent on local router choices instead of one startup-owned Security boundary. |
| Treat setup, docs, and API CSP as one global policy | Current behavior is intentionally path-sensitive, and setup has relaxed script needs that should not leak into normal API or docs responses. |
| Force HSTS for every response independent of deployment protocol | HSTS can break deployments behind proxies or local HTTP setups when not coordinated with HTTPS ingress, so the middleware keeps HSTS opt-in and HTTPS-aware. |
| Install request IDs and drain gate only in production | Test and development requests still need correlation IDs and deterministic drain behavior, so these middlewares should stay always installed. |
| Backfill one broad Security ADR for request edge, egress, secrets, and serialization | TASK-2247 found different caveats for those areas. Combining them would overclaim adoption and make future review harder. |
Consequences¶
New request-edge middleware behavior belongs in tldw_Server_API/app/core/Security/ and startup wiring in app/main.py, with path-specific tests under tldw_Server_API/tests/Security/.
Production deployments keep Security headers enabled by default unless ENABLE_SECURITY_HEADERS explicitly disables them. Test mode and explicit disablement remain recognized caveats and must be called out when verifying behavior.
HSTS remains opt-in and HTTPS-aware. Setup CSP remains intentionally more relaxed than normal API/docs CSP, including the default eval allowance unless TLDW_SETUP_NO_EVAL disables it.
Outbound egress/SSRF policy, secret-management adoption, and serialization safety remain separate Security decisions. They should be confirmed and backfilled in separate bounded ADRs if they become priority work.
Follow-up¶
- Use this ADR as the covering record for the request-edge middleware portion of INV-029.
- Keep
Docs/ADR/inventory/2026-06-04-security-confirmation-audit.mdas the evidence record and scope boundary for this backfill. - Consider separate ADRs for outbound egress/SSRF policy and for secret/serialization adoption after focused confirmation work.