theseus/addon-update-pubkeys.js

22 lines
945 B
JavaScript
Raw Permalink Normal View History

feat(theseus/addons): signed add-on update endpoint, à la Firefox XPI Decouples bundled-add-on updates from Theseus releases. An add-on whose addon.json declares an updateURL can be republished at any time without shipping a new Theseus installer; existing installs pick it up on the next boot's +30 s background check. Client flow (main-process only, no UI touchpoints in this commit): initAddons() ├── promoteStagedUpdates() # promote signed stage if newer ├── seedBundledAddons() # bundle wins over on-disk if newer └── AddonHost.discoverAndActivate() 30 s later: └── checkAndStageUpdates() # fetch, verify, download, stage Signature: Ed25519 over "silentmode.addon-update-v1|<id>|<version>|<tarball-sha256>", verified against a hardcoded set of operator pubkeys living in addon-update-pubkeys.js. Domain-separated so the operator key can't be tricked into signing a message with a different purpose. Empty pubkey array is the shipping default — checkAndStageUpdates() then short-circuits and no outbound requests are made, which is the safe posture until the operator ceremonies a key in. Payload: gzipped tar, extracted with the system tar (present on Win10 1803+, macOS, Linux). Path traversal defended by tar's default refusal of `..` entries; the extracted manifest's id + version are re-checked against the signed values before staging. Staged updates go to <userData>/addons-updates-staged/<id>-<version>/. Promotion into <userData>/addons/<id>/ reuses seedBundledAddons's backup dance: existing folder moves to <userData>/addons-backups/<id>-<oldver>-<timestamp>/ so any local edits survive. New files: - addon-updater.js — client - addon-update-pubkeys.js — hardcoded pubkeys (empty; edit + rebuild to rotate) - scripts/generate-update-keypair.mjs — one-time keygen - scripts/sign-addon-update.mjs — operator packager+signer - docs/ADDON-UPDATES.md — operator brief + threat model Wired into main.js at boot; screenshot add-on's addon.json advertises the reference updateURL for when the endpoint goes live.
2026-09-07 21:58:30 +02:00
// Ed25519 public keys authorized to sign bundled-addon updates.
//
// A pubkey listed here can, at runtime, cause Theseus to REPLACE any user's
// bundled add-on with a signed payload fetched over the network. Only add
// pubkeys the Silent Mode operator controls.
//
// Rotation:
// 1. Generate a new keypair with scripts/generate-update-keypair.mjs.
// 2. Sign next updates.json entries with BOTH old and new key.
// 3. Ship a Theseus release adding the new pubkey to this array (both live).
// 4. After users have updated past that release, ship a follow-up release
// removing the old pubkey; stop signing with it.
//
// Empty array means "no update endpoint" — the client short-circuits and
// makes no outbound requests. This is the safe default for a fresh build.
module.exports = {
PUBKEYS_HEX: [
"732b1263a236b0030383a2376597cfa43c3624b3ca2912a46134f8f2a06e6012", // Silent Mode ops, generated 2026-09-07
feat(theseus/addons): signed add-on update endpoint, à la Firefox XPI Decouples bundled-add-on updates from Theseus releases. An add-on whose addon.json declares an updateURL can be republished at any time without shipping a new Theseus installer; existing installs pick it up on the next boot's +30 s background check. Client flow (main-process only, no UI touchpoints in this commit): initAddons() ├── promoteStagedUpdates() # promote signed stage if newer ├── seedBundledAddons() # bundle wins over on-disk if newer └── AddonHost.discoverAndActivate() 30 s later: └── checkAndStageUpdates() # fetch, verify, download, stage Signature: Ed25519 over "silentmode.addon-update-v1|<id>|<version>|<tarball-sha256>", verified against a hardcoded set of operator pubkeys living in addon-update-pubkeys.js. Domain-separated so the operator key can't be tricked into signing a message with a different purpose. Empty pubkey array is the shipping default — checkAndStageUpdates() then short-circuits and no outbound requests are made, which is the safe posture until the operator ceremonies a key in. Payload: gzipped tar, extracted with the system tar (present on Win10 1803+, macOS, Linux). Path traversal defended by tar's default refusal of `..` entries; the extracted manifest's id + version are re-checked against the signed values before staging. Staged updates go to <userData>/addons-updates-staged/<id>-<version>/. Promotion into <userData>/addons/<id>/ reuses seedBundledAddons's backup dance: existing folder moves to <userData>/addons-backups/<id>-<oldver>-<timestamp>/ so any local edits survive. New files: - addon-updater.js — client - addon-update-pubkeys.js — hardcoded pubkeys (empty; edit + rebuild to rotate) - scripts/generate-update-keypair.mjs — one-time keygen - scripts/sign-addon-update.mjs — operator packager+signer - docs/ADDON-UPDATES.md — operator brief + threat model Wired into main.js at boot; screenshot add-on's addon.json advertises the reference updateURL for when the endpoint goes live.
2026-09-07 21:58:30 +02:00
],
};