diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-18 10:30:59 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-18 10:30:59 +0200 |
| commit | 68bfe56a19aeb4c16f8e185fc85d8eee61aef78f (patch) | |
| tree | 2a9ae0352ec09aca413448c9b2e5e9a638b36471 /CLAUDE.md | |
| parent | 30e855f55f1d920b25da0bdd8e538c249d3c0c26 (diff) | |
| download | meshbay-68bfe56a19aeb4c16f8e185fc85d8eee61aef78f.tar.gz | |
fix(client): the desktop client runs, and running it corrected three things
Electron 42 / Chromium 148, launched under xvfb. The packaged interface mounts
over `app://` with a secure context, `crypto.subtle` present, Argon2 WASM
loaded, and no console errors. Three statements in the design were wrong, and
only launching it found them.
**A CSP in a `<meta>` tag silently drops `frame-ancestors`.** Chromium says so
in the console. A policy carrying a directive that does nothing is worse than
one without it, so the policy is sent as a header by the protocol handler —
which is also the only thing serving the interface, so one source instead of
two.
**`secure: true` is not what makes the service worker register.** Chromium
refuses a worker on a custom scheme whatever its privileges: "The URL protocol
of the current origin ('app://meshbay') is not supported". The application has
no service worker and needs none — it saves through a native dialog, which is
the better of the two paths. `sw.js` stays in the package because the same files
serve the browser, where it is one of only three ways to write a large file.
What `secure: true` is actually for was measured at the same time: without it
**the whole of `crypto.subtle` is undefined**. The first probe loaded a `data:`
URL and every algorithm failed with TypeError, AES-GCM included — which is why
the probe was rewritten before believing its answer. X25519 and Ed25519 are both
present on Chromium 148, settling the version floor left open as O6.
**The renderer cannot call the hub.** Its origin is `app://meshbay` and CORS
refuses it. The hub has *no CORS middleware at all* — its API is reachable from
no web origin whatever — and that is worth keeping. Widening it for
`app://meshbay` would be worse than it looks: that origin is not a credential,
since any Electron application can claim the same scheme and host name.
So every hub call leaves from the main process, exactly as saving a file does,
and it refuses any origin that is not the hub the user signed in to.
`platform.apiFetch()` is `fetch` in a browser and the bridge in the application,
so no caller has to know which it got. `transport.js` reaches it through a
global because it is a classic script, not a module — the alternative was a
second fetch path, which is how two callers of one hub start disagreeing about
how to reach it.
Verified from inside Electron: the main process gets 200 from
/v1/hub/version, the renderer is refused by CORS, and **a script served by the
hub is refused by the policy** — T3's mitigation demonstrated rather than
asserted.
Build note, written into the README because it will bite the next person:
**Ubuntu 24.04's nodejs 18 cannot install Electron at all** — the download
script `require()`s an ESM module, which Node gained in 22. Node 24 LTS,
checksum-verified against nodejs.org, is what this was built with.
package-lock.json is committed; builds use `npm ci`, not `npm install`.
799 tests pass, e2e.py still passes end to end. The session harness needed a
platform stub: it lifts `hubFetch` out of app.js as text and runs it, so the
adapter is now part of the environment it models.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 29 |
1 files changed, 21 insertions, 8 deletions
@@ -338,14 +338,27 @@ anything that assumes one key per person. key from the per-node identities. What it does cost is metadata: the hub now knows how many devices an account has and when each last signed in -- **The desktop client exists as source and has never been run.** There is no - npm on the development machine, so Electron was never installed and - `packages/meshbay-client/` has not been launched once. `test_desktop_shell.py` - pins its security contract by reading the source — sandbox, contextIsolation, - the privileged `app://` scheme, the CSP keeping `wasm-unsafe-eval`, the - traversal check, the bridge exposing no path. That is the same weak evidence - `test_downloads.py` gives the browser save paths, and for the same reason: it - catches a property being *removed*, and proves nothing about the thing running +- **The desktop client runs** (2026-08-18, Electron 42 / Chromium 148 under + xvfb). Build needs Node ≥ 22 — **Ubuntu 24.04's nodejs 18 cannot install + Electron at all**, its download script `require()`s an ESM module. Node 24 LTS + lives in `/opt/nodejs`, fetched and checksum-verified against nodejs.org. + `test_desktop_shell.py` pins the security contract by reading the source, and + that is still weak evidence — it catches a property being removed. Launching it + is what found the three things below + +- **Three things only launching it could find.** (1) A CSP in a `<meta>` tag + silently drops `frame-ancestors`; it is sent as a header by the protocol + handler now. (2) **Service workers do not work on a custom scheme** — Chromium + refuses whatever the privileges — so the app has none and uses the native save + dialog; `sw.js` stays for the browser. What `secure: true` actually buys was + measured at the same time: without it **all of `crypto.subtle` is undefined**, + AES-GCM included. X25519 and Ed25519 are present on Chromium 148. (3) **The + renderer cannot call the hub**: its `app://` origin is refused by CORS, and the + hub deliberately has no CORS middleware — its API is reachable from no web + origin. Every hub call therefore leaves from the main process, which also + refuses any origin that is not the signed-in hub. A script served by the hub is + refused by the policy, which is T3's mitigation demonstrated rather than + asserted - **A user unit cannot carry `User=`.** `meshbay-node.spec` installed the system template into `%{_userunitdir}`, where systemd refuses the file outright — the |