1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use ;
/// Defense-in-depth response headers applied to every HTTP response (UI + `/api/v1`).
///
/// The daemon serves a browser dashboard whose buttons drive an unauthenticated loopback API
/// (create / trigger / delete routines, `POST /shutdown`), so the served responses are hardened
/// against the cheap framing / sniffing / in-page-injection vectors:
///
/// - `X-Frame-Options: DENY` + CSP `frame-ancestors 'none'` — block clickjacking of the
/// dashboard's destructive controls via `<iframe src="http://localhost:5784/">`.
/// - `X-Content-Type-Options: nosniff` — stop browsers content-sniffing a response into an
/// unintended type.
/// - `Referrer-Policy: no-referrer` — never leak the loopback URL to third parties.
/// - A CSP that locks everything to `'self'` by default and blocks `object-src`, `base-uri`, and
/// `form-action` outright — so an injected `<script>`, `<base>`, or off-origin `<form>` cannot
/// exfiltrate to or act on behalf of the unauthenticated destructive API (issue #406's
/// in-code follow-up, #551).
///
/// Two directives stay loose, by necessity rather than oversight:
///
/// - `script-src` / `style-src` carry `'unsafe-inline'`. The bundled React SPA (`prebuilt.html`,
/// built by `src/build/client.rs`) is inlined by `vite-plugin-singlefile` into one inline
/// `<script type="module">` and one inline `<style>` block so the daemon ships as one file with
/// no separate static-asset serving; that payload's bytes change on every UI rebuild, so a
/// `sha256-…` hash would have to be regenerated and wired through the build script rather than
/// hardcoded, which is left as further follow-up. The embedded Swagger UI
/// (`utoipa-swagger-ui`) also sets inline `style="…"` attributes from its React components at
/// runtime. This is one blanket policy applied to every response; it can't be tightened
/// per-route without splitting the middleware, which isn't warranted for a single SPA.
/// - `style-src` and `font-src` allow `https://fonts.googleapis.com` / `https://fonts.gstatic.com`
/// respectively: the dashboard still loads its webfont from Google Fonts pending #467 (tracked
/// separately in open PR #519); once that self-hosts the font, these CDN allowances should be
/// dropped in favor of `'self'`.
///
/// `img-src` allows `data:` for an inline SVG noise-texture background used by the dashboard CSS.
const SECURITY_HEADERS: & = &;
/// Inject the [`SECURITY_HEADERS`] onto every response.
///
/// `Cache-Control: no-store` is also added as a fallback for responses that don't already carry a
/// `Cache-Control` header. Responses that set their own directive (e.g. the SPA's `index.html`
/// uses `no-cache` to enable ETag-based 304 revalidation, issue #401) are left untouched.
/// Without this fallback, browsers apply heuristic caching to API `GET` responses and may serve
/// stale JSON on page reload (issue #921).
pub async