theseus/GOTCHAS.md
Local Dev 08beadcf8f Theseus: close the Settings-tab vault leak and the add-on update signer bypass
A preload belongs to the WebContents, not the page: a website loaded into
the Settings tab kept window.cfg and could read every vault password, flip
settings and install extensions without consent. Settings and add-on tabs
now never load web content, and the channels behind settings-preload check
their sender. `navigate` no longer accepts calls from web pages.

Add-on updates trusted any publisherSig, whatever name it carried, even for
bundled add-ons. The trust root is now the installed addon.json (publisher,
or the operator key when there is none); versions must be plain dotted
numbers; a community install can't take over a bundled or foreign id.

Also:
- autofill matches and fills against the live URL, not a stale prov.host
- bns:// forwards the raw request path (..%2F escaped the name's bucket)
- clipboard-read denied, openExternal asks; forged collision choices ignored
- web pages can't window.open file:/chrome:/theseus:; data:/blob: no longer
  go to the search engine; the quick-links panel loses home-preload
- clear-history-on-quit is awaited and removes history.json too
- update helper takes its paths from the environment (non-ASCII profiles)
- electrum poll has a deadline; misses wait at most 2.5 s
- p records go through Tor; add-on proxy credentials are actually used
- whole-folder require-cache bust on add-on version change; failed
  activate() no longer leaks its request filter
- approvals released when the window closes; web-app ids stay on-origin
2026-10-03 09:50:10 +02:00

6.2 KiB

Theseus gotchas

Non-obvious things sessions keep re-deriving. If you found this file because something broke in a way that felt spooky, the answer is probably here.

build.files in package.json is EXPLICIT

electron-builder ships only what's listed in build.files. Every runtime- loaded HTML/preload/module MUST be there. A missing entry causes the loadFile("foo.html") in a WebContentsView to silently open blank in the packaged app — visually indistinguishable from "the button does nothing".

npm start (dev) never catches this because it reads from the source tree directly. Only a real npm run dist + install exposes it.

Every time you add a new WebContentsView or loadFile, add its HTML and preload to the files array. Sanity check after building:

node -e "const b = require('fs').readFileSync('dist-public/win-unpacked/resources/app.asar'); for (const n of ['engine-picker.html','popover.html','downloads.html','engine-picker-preload.js','popover-preload.js','downloads-preload.js']) console.log(n, b.indexOf(Buffer.from(n)) >= 0 ? 'OK' : 'MISSING');"

This has bit us once (search-picker dropdown, shipped 6bbccf9c, blank picker). Do not let it bite twice.

Building — admin terminal the first time

npm run dist needs an admin terminal on the machine's first Theseus build so electron-builder's winCodeSign package can extract its darwin/ symlinks (requires SeCreateSymbolicLink). Subsequent builds reuse the cache under $LOCALAPPDATA/electron-builder/Cache/winCodeSign/ and don't need admin.

If DNS is misbehaving on the machine, pin these hosts in C:\Windows\System32\drivers\etc\hosts before building so electron-builder can fetch: github.com, codeload.github.com, objects.githubusercontent.com, release-assets.githubusercontent.com. Strip them after.

Reproducible builds

Set both before npm run dist:

  • SOURCE_DATE_EPOCH — freezes timestamps so a rebuild with no content changes produces the same hash. Bump per release, not per rebuild.
  • CSC_IDENTITY_AUTO_DISCOVERY=false — stops electron-builder searching for a signing cert (we don't sign; SECURITY.md rule 2).

Native <select> popup theme

Chromium's native <select> popup uses the page's color-scheme. When it's "light dark" (both accepted), Chromium picks by the OS scheme — so a dark-themed app on a light OS shows a light popup and vice versa.

Fix, currently in settings.html:

:root { color-scheme: dark; }
@media (prefers-color-scheme: light) { :root { color-scheme: light; } }

nativeTheme.themeSource (driven by settings.theme) sets prefers-color-scheme, so this stays in sync automatically. Do not remove.

Chromium also respects select option { background; color } in the popup on Windows — a belt-and-braces override alongside color-scheme.

A preload belongs to the WebContents, not the page

A view's preload runs for every document that view ever loads. Navigating a Settings tab to a website used to hand that site window.cfg, which covers the vault (pwGet), settings and extension installs. Two rules follow:

  • Settings and add-on-file tabs never load anything else. navigateTab and will-navigate open the target in a fresh tab instead.
  • A new IPC channel exposed through settings-preload.js must be added to SETTINGS_ONLY (or SETTINGS_SHARED if the toolbar's preload.js calls it too) in main.js. The wrapper around ipcMain.handle only checks the sender for the channels in those sets. Any other channel is callable by whatever page ends up in the view.

The same applies to home-preload.js, which is in every tab. Handlers behind it check isHomePageSender / isErrorPageSender, which compare the sender against the exact shipped file:// URL.

The resolver in Theseus is resolver-web.mjs, not .js

Packaged builds ship Argus/src/lib/resolver-web.js as resolver-web.mjs (see extraResources in package.json). Renamed because resources/ has no adjacent package.json so a .js gets treated as CommonJS and its exports fail. Dev reads the engine copy directly (Argus is type: module so .js works there).

The resolver's electrum WS directIP fallback

main.js passes directIP: true to resolveHost so the engine can dial the chipnet electrum servers by pinned IP if system DNS is dead. Do not remove — this is what keeps .bch resolution working when the adapter DNS is broken (a real failure mode we've hit).

Publishing new hashes end-to-end

  1. Build (see above).
  2. Update ../site/releases-manifest.json AND ../site/tools/index.html AND ../site/releases/index.html with the fresh SHA-256s + release date. All three must match.
  3. Deploy:
    scp dist-public/*.exe ../site/releases-manifest.json silentmode:/opt/silent-mode/dl/
    cd ../Argus && node src/lib/sia-upload.js ../site bns/silentmode
    
  4. On-chain (wallet spend, user's action): releases.silentmode.bch currently publishes {"u":"https://dl.silentmode.st/releases-manifest.json"} — a compact u record because on-chain hashes wouldn't fit the 200-byte OP_RETURN. Only re-publish if the URL changes or if we ever put full hashes on-chain.

Sia upload from this machine fails without a hosts pin

node src/lib/sia-upload.js fails with getaddrinfo EAI_FAIL coinspectrum.duckdns.org unless the host is pinned in C:\Windows\System32\drivers\etc\hosts: 195.184.247.106 coinspectrum.duckdns.org. This is a DNS quirk of the machine, not a code bug — related to the strict-resolver behavior the Ariadne daemon's EDNS fix addresses.

Deploy directory quirks

  • /opt/silent-mode/dl/ is served as dl.silentmode.st — installers + manifest live here.
  • silentmode.st proxies to Sia (bns/silentmode/) — the HTML pages live there.
  • The bns gateway does NOT auto-index directories. silentmode.st/apps/ is a 404 (NoSuchKey); users must hit /apps/index.html. Fix upstream in bnsd.js if it ever matters.

Firefox needs a restart after Ariadne CA install/rotation

Firefox only reads root CAs at startup. The Ariadne installer offers a -CloseFirefox task by default when Firefox is running so this doesn't bite. Chrome/Edge pick up the new root immediately.