Context
#313 loosened the viewer's CSP from img-src 'self' to img-src 'self' data: so the inline-SVG favicon could load:
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg ...%">
Bare data: allows ANY data-URI image type — data:image/png;base64,..., data:text/html;base64,... (some browsers still load), etc. The viewer doesn't need that.
Proposal
Tighten to one of:
img-src 'self' data:image/svg+xml — only allow SVG data URIs (what the favicon actually is)
- Or self-host the favicon at
/favicon.svg and revert CSP to img-src 'self'
The self-host option is cleaner — drops the data-URI surface entirely. The trade is a 6th asset (small SVG) in the viewer bundle vs an inline blob.
Files
src/auth.ts — buildViewerCsp()
src/viewer/index.html — favicon <link>
src/viewer/server.ts — add /favicon.svg route if self-hosting
test/viewer-security.test.ts — assert CSP doesn't allow arbitrary data URIs
Why this matters
Defence in depth. The viewer is bound to loopback by default + has the Host-header allowlist from #294 + has nonce-only script-src. Tightening img-src is the next-easiest layer to harden in case a future viewer change introduces server-side HTML injection (escape miss in tooltip / activity feed text).
Context
#313 loosened the viewer's CSP from
img-src 'self'toimg-src 'self' data:so the inline-SVG favicon could load:Bare
data:allows ANY data-URI image type —data:image/png;base64,...,data:text/html;base64,...(some browsers still load), etc. The viewer doesn't need that.Proposal
Tighten to one of:
img-src 'self' data:image/svg+xml— only allow SVG data URIs (what the favicon actually is)/favicon.svgand revert CSP toimg-src 'self'The self-host option is cleaner — drops the data-URI surface entirely. The trade is a 6th asset (small SVG) in the viewer bundle vs an inline blob.
Files
src/auth.ts—buildViewerCsp()src/viewer/index.html— favicon<link>src/viewer/server.ts— add/favicon.svgroute if self-hostingtest/viewer-security.test.ts— assert CSP doesn't allow arbitrary data URIsWhy this matters
Defence in depth. The viewer is bound to loopback by default + has the Host-header allowlist from #294 + has nonce-only
script-src. Tighteningimg-srcis the next-easiest layer to harden in case a future viewer change introduces server-side HTML injection (escape miss in tooltip / activity feed text).