security: honest E2EE messaging + CSP hardening (Levers 1 & 2)#35
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 thesndrCLI for security-critical transfers:sndrCLIThis 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)console.logthat ran on every request inside the CSPconnect-srccallback.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/snder→tarnover/sndrin both the README reference and the new FAQ content.Verification
Driven in production mode (
NODE_ENV=production, CSP actually active — noteconfig.envdefaults todevelopment, so this path isn't exercised by a plain dev run):/), and the How-it-works modal all load with zero CSP violations.npm testgreen (48 backend + 23 frontend).Deliberately deferred (not cheap here — documented, not forgotten)
'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.data:-URL fetch thatconnect-srcblocks. It reproduces identically on master (connect-srcis unchanged in this PR) and only surfaces under the ancient bundled puppeteer'suploadFile; real uploads useFileReader, nodata: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 nginxbase-uri 'none'/restrictiveconnect-srcwould override what the container sends.