Skip to content

security: honest E2EE messaging + CSP hardening (Levers 1 & 2)#35

Merged
jaschadub merged 1 commit into
masterfrom
security/honest-e2ee-messaging-and-csp
May 30, 2026
Merged

security: honest E2EE messaging + CSP hardening (Levers 1 & 2)#35
jaschadub merged 1 commit into
masterfrom
security/honest-e2ee-messaging-and-csp

Conversation

@jaschadub
Copy link
Copy Markdown
Member

Implements the two cheap "be honest about browser-E2EE" levers: disciplined messaging + low-cost CSP hardening.

Lever 1 — honest messaging

  • Drop "zero-knowledge" from the status bar. The web UI is end-to-end encrypted in your browser, but the same server ships the JS that does the encryption, so it isn't "zero-knowledge" in the strong sense. We shouldn't imply a guarantee we can't make.

  • New FAQ entry — "Is the web UI 'zero-knowledge'?" — with a per-client threat matrix lifted from docs/security.md, and a pointer to the sndr CLI for security-critical transfers:

    Threat Web UI sndr CLI
    Reading file contents after a passive server/storage breach
    Reading filenames / sizes / types after a passive breach
    Network / TLS eavesdropper reading contents
    An actively malicious server serving targeted JS to steal the key
    The operator seeing IPs, file sizes, timing, or denying service

    This is also the honest-positioning differentiator vs. tools that brand their web client "zero-knowledge" while sharing the identical operator-JS limitation.

Lever 2 — cheap CSP hardening (server/routes/index.js)

  • Remove a stray console.log that ran on every request inside the CSP connect-src callback.
  • worker-src 'self' — the download service worker is same-origin.
  • base-uri 'self' — blocks cross-origin <base> injection while still allowing the app's own same-origin base. (Caught and fixed a regression here: base-uri 'none' breaks the app, which sets its own base — see verification.)
  • frame-src 'none' — the app embeds no nested browsing contexts.

Also

Fixes a broken CLI repo link tarnover/sndertarnover/sndr in both the README reference and the new FAQ content.

Verification

Driven in production mode (NODE_ENV=production, CSP actually active — note config.env defaults to development, so this path isn't exercised by a plain dev run):

  • CSP response header is well-formed with the new directives.
  • App shell, nonce'd scripts/styles, the same-origin service worker (registers, scope /), and the How-it-works modal all load with zero CSP violations.
  • Status bar confirmed to no longer contain "zero-knowledge"; honest footnote intact.
  • npm test green (48 backend + 23 frontend).

Deliberately deferred (not cheap here — documented, not forgotten)

  • SRI on script/style tags. Assets are served from 'self' via server-rendered tags reading the webpack manifest; SRI would need a build plugin + manifest/layout plumbing for marginal value on same-origin assets. Worth revisiting if assets ever move to a separate CDN.
  • PWA service-worker bundle pinning.
  • Pre-existing finding (not fixed here): with the container's own CSP active in prod mode, the app triggers a data:-URL fetch that connect-src blocks. It reproduces identically on master (connect-src is unchanged in this PR) and only surfaces under the ancient bundled puppeteer's uploadFile; real uploads use FileReader, no data: fetch. Deployment note: environments that front the container with nginx security headers (e.g. snd.dx.pe) must keep the nginx and app CSPs consistent — browsers enforce the intersection of multiple CSP headers, so an nginx base-uri 'none'/restrictive connect-src would override what the container sends.

Lever 1 — honest messaging about the browser-E2EE threat model:
- Drop "zero-knowledge" from the status bar. The web UI is end-to-end
  encrypted in your browser, but the server also ships the JS, so it is
  not zero-knowledge in the strong sense; don't imply a guarantee we
  can't make.
- Add a FAQ entry ("Is the web UI 'zero-knowledge'?") with a per-client
  threat matrix lifted from docs/security.md, and point security-critical
  transfers at the sndr CLI.

Lever 2 — cheap, verified CSP hardening (server/routes/index.js):
- Remove a stray console.log that ran on every request inside the CSP
  connect-src callback.
- Add worker-src 'self' (the download service worker is same-origin),
  base-uri 'self' (block cross-origin <base> injection while still
  allowing the app's own same-origin base), and frame-src 'none'
  (the app embeds no nested browsing contexts).

Also fix a broken CLI repo link: tarnover/snder -> tarnover/sndr, in
both the README reference and the new FAQ content.

Verified in production mode (NODE_ENV=production, CSP active): the CSP
header is well-formed; the app shell, nonce'd scripts/styles, the
same-origin service worker, and the How-it-works modal all load with
zero CSP violations. npm test green (48 backend + 23 frontend).

Deliberately not included (documented as follow-ups, not cheap here):
- SRI on script/style tags. Assets are served from 'self' via
  server-rendered tags reading the webpack manifest; SRI would need a
  build-plugin + manifest/layout plumbing for marginal value on
  same-origin assets.
- PWA service-worker bundle pinning.
- A pre-existing finding surfaced while testing: with the container's
  own CSP active in prod mode, the app makes a data:-URL fetch that
  connect-src blocks. It reproduces identically on master (connect-src
  is unchanged here) and only appears under the ancient bundled
  puppeteer's uploadFile; real uploads use FileReader. Left for a
  separate fix, and relevant to deployments (e.g. snd.dx.pe) that front
  the container with nginx security headers: keep the nginx and app CSP
  consistent, since browsers enforce the intersection of both.
@jaschadub jaschadub merged commit d8cbf14 into master May 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant