# Business Rules — U3 HTTP Security Headers & CSP
---
## Decision Logic
```mermaid
graph TD
start["Response starting"]
exists{"Header already set
by something else ?"}
leave["Leave it untouched"]
dev{"Environment is Development
and header is HSTS ?"}
skiphsts["Skip HSTS"]
always{"Header is nosniff or HSTS ?"}
apply["Apply"]
html{"Content type is HTML ?"}
skiphtml["Skip: not an HTML response"]
start --> exists
exists -->|yes| leave
exists -->|no| dev
dev -->|yes| skiphsts
dev -->|no| always
always -->|yes| apply
always -->|no| html
html -->|yes| apply
html -->|no| skiphtml
classDef entry fill:#f6e05e,stroke:#c05621,stroke-width:1px,color:#000;
classDef decision fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
classDef good fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef neutral fill:#e2e8f0,stroke:#4a5568,stroke-width:1px,color:#000;
class start entry;
class exists,dev,always,html decision;
class apply good;
class leave,skiphsts,skiphtml neutral;
```
Text alternative: an already-present header is never overwritten; HSTS is skipped in Development; `nosniff` and HSTS apply to every response while the remaining three apply only to HTML responses.
---
## Applicability Rules
| ID | Rule |
|---|---|
| **BR-U3-01** | `X-Content-Type-Options: nosniff` is applied to **every** response. |
| **BR-U3-02** | `Strict-Transport-Security` is applied to **every** response **except in Development**. |
| **BR-U3-03** | `Content-Security-Policy`, `X-Frame-Options` and `Referrer-Policy` are applied **only** to responses whose content type is HTML. |
| **BR-U3-04** | A header already present on the response is never overwritten. |
| **BR-U3-05** | Headers are written at response start, not before the pipeline continues, because the content type is unknown earlier. |
| **BR-U3-06** | The middleware is registered **before** static-file middleware, which short-circuits the pipeline. |
| **BR-U3-07** | Header application never throws into the response path. A configuration error is a startup failure, not a per-request one. |
| **BR-U3-08** | Every header except HSTS applies in Development, so a CSP violation surfaces during development. |
| **BR-U3-09** | Headers apply to error responses too — the middleware sits inside the exception handler. |
**Rationale for BR-U3-01**: `nosniff` exists to stop a browser guessing the type of a **non-HTML** resource. An uploaded `.txt` or `.svg` interpreted as HTML or JavaScript is the attack it prevents, so restricting it to HTML would remove it precisely where it works.
**Rationale for BR-U3-04**: a component that deliberately set a header — a download endpoint setting its own `Content-Disposition`-adjacent policy, for example — knows something this middleware does not. Overwriting would be silently destructive.
---
## Policy Content Rules
| ID | Rule |
|---|---|
| **BR-U3-10** | Exactly two policies exist, defined **in code**: `Strict` and `Relaxed`. |
| **BR-U3-11** | Path-to-policy assignment and allowed origins come from **configuration**, so a path or origin can be added without a code change. |
| **BR-U3-12** | `Strict` applies to `/admin`, `/api/v1` and `/health`. `Relaxed` is the default for everything else. |
| **BR-U3-13** | `Strict` sets `script-src 'self'` — **no `'unsafe-inline'` and no `'unsafe-eval'`**. This must not be relaxed. |
| **BR-U3-14** | `Strict` sets `style-src 'self' 'unsafe-inline'`. Documented exception, unavoidable — see below. |
| **BR-U3-15** | `Relaxed` is **enforcing**, not report-only, so SECURITY-04 is satisfied on every HTML-serving path. |
| **BR-U3-16** | `Relaxed` permits inline scripts and styles, and images, fonts and frames from any HTTPS origin — but **not** external script origins. |
| **BR-U3-17** | Both policies set `object-src 'none'` and `base-uri 'self'`. |
| **BR-U3-18** | Policy strings are composed once at startup and reused. |
| **BR-U3-19** | `X-Frame-Options` is `DENY` under `Strict` and `SAMEORIGIN` under `Relaxed`, with `frame-ancestors` set to match. |
**Rationale for BR-U3-14 — the one exception, and why it is not negotiable**: Radix UI positions dropdowns, dialogs and selects using inline `style` attributes whose values are recomputed per click, viewport and scroll position. CSP nonces apply only to `