diff --git a/main.js b/main.js index f0077dcc..48b8ae95 100644 --- a/main.js +++ b/main.js @@ -1984,15 +1984,51 @@ function installDownloadTracker() { emitUpdateAvailable(); }); item.once("done", (_ev, state) => { - if (state === "completed") { - updateDownloadPath = item.getSavePath() || dst; - updateDownloadState = "ready"; - console.log(`[update] silent fetch complete: ${updateDownloadPath}`); - } else { + if (state !== "completed") { updateDownloadState = "failed"; console.warn(`[update] silent fetch ${state}`); + emitUpdateAvailable(); + return; } - emitUpdateAvailable(); + // NEVER mark "ready" without verifying the file hashes to what the + // manifest promised. Electron's DownloadItem has been observed to + // fire done/completed on truncated payloads (bad Content-Length, + // CDN cache truncation, mid-stream TLS reset the runtime swallowed), + // and 0.3.31's in-app updater then spawned a half-file as setup — + // NSIS integrity check failed silently and the browser was gone. + const savedPath = item.getSavePath() || dst; + const expected = String(updateAvailable && updateAvailable.setupHash || "").toLowerCase(); + if (!expected) { + updateDownloadState = "failed"; + console.warn(`[update] no manifest hash for ${savedPath} — refusing to arm install`); + try { fs.unlinkSync(savedPath); } catch {} + emitUpdateAvailable(); + return; + } + const crypto = require("node:crypto"); + const hash = crypto.createHash("sha256"); + const rs = fs.createReadStream(savedPath); + rs.on("data", (c) => hash.update(c)); + rs.once("error", (e) => { + updateDownloadState = "failed"; + console.warn(`[update] hash read failed: ${e.message}`); + try { fs.unlinkSync(savedPath); } catch {} + emitUpdateAvailable(); + }); + rs.once("end", () => { + const got = hash.digest("hex").toLowerCase(); + if (got !== expected) { + updateDownloadState = "failed"; + console.warn(`[update] SHA-256 mismatch: got ${got}, want ${expected} — refusing to arm install`); + try { fs.unlinkSync(savedPath); } catch {} + emitUpdateAvailable(); + return; + } + updateDownloadPath = savedPath; + updateDownloadState = "ready"; + console.log(`[update] silent fetch complete + verified: ${savedPath}`); + emitUpdateAvailable(); + }); }); return; } diff --git a/package.json b/package.json index e152868c..3e0a0dfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "theseus-navigator", - "version": "0.3.32", + "version": "0.3.33", "description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.", "author": "Silent Mode", "main": "main.js",