fix: enforce mandatory checksum verification and HTTPS-only registry access

Previously, checksum failures in install.js were silently downgraded to
warnings, allowing unverified binaries to be installed. Similarly,
update.js accepted HTTP registries and skipped verification when no
matching platform entry was found.

Now both scripts treat checksum verification as mandatory when configured:
all failure paths (download error, compute error, mismatch, missing
platform entry) abort the operation and clean up downloaded files.
update.js also drops the http module and silently skips version checks
for non-HTTPS registries.
This commit is contained in:
kite 2026-06-16 16:15:14 +08:00
parent 14d82ff0f0
commit e9bd334b7f
2 changed files with 67 additions and 47 deletions

View file

@ -189,37 +189,46 @@ async function main() {
}
if (config.checksumPattern) {
const checksumUrl = buildUrl(config.checksumPattern, vars);
info("Verifying checksum...");
let shaContent;
try {
const checksumUrl = buildUrl(config.checksumPattern, vars);
info("Verifying checksum...");
const shaContent = await downloadText(checksumUrl);
const actualSha = await computeChecksum(binaryDest);
shaContent = await downloadText(checksumUrl);
} catch (e) {
try { fs.unlinkSync(binaryDest); } catch (_) {}
throw new Error(`Failed to download checksum from ${checksumUrl}: ${e.message}`);
}
let actualSha;
try {
actualSha = await computeChecksum(binaryDest);
} catch (e) {
try { fs.unlinkSync(binaryDest); } catch (_) {}
throw new Error(`Failed to compute checksum for ${binaryDest}: ${e.message}`);
}
let verified = false;
for (const line of shaContent.split("\n")) {
const trimmed = line.trim();
if (trimmed.includes(`-${os}-${arch}`)) {
const expectedSha = trimmed.split(/\s+/)[0].toLowerCase();
if (expectedSha) {
if (actualSha !== expectedSha) {
throw new Error(
`Checksum mismatch! Expected: ${expectedSha}, Got: ${actualSha}`
);
}
info("Checksum verified.");
verified = true;
break;
let verified = false;
for (const line of shaContent.split("\n")) {
const trimmed = line.trim();
if (trimmed.includes(`-${os}-${arch}`)) {
const expectedSha = trimmed.split(/\s+/)[0].toLowerCase();
if (expectedSha) {
if (actualSha !== expectedSha) {
try { fs.unlinkSync(binaryDest); } catch (_) {}
throw new Error(
`Checksum mismatch! Expected: ${expectedSha}, Got: ${actualSha}`
);
}
info("Checksum verified.");
verified = true;
break;
}
}
if (!verified) {
warn("No matching checksum entry found; skipping verification.");
}
} catch (e) {
if (e.message.includes("mismatch")) {
throw e;
}
warn(`Could not verify checksum: ${e.message}`);
}
if (!verified) {
try { fs.unlinkSync(binaryDest); } catch (_) {}
throw new Error(
`No matching checksum entry for ${os}-${arch} in ${checksumUrl}`
);
}
}

View file

@ -4,7 +4,6 @@
const fs = require("fs");
const path = require("path");
const os = require("os");
const http = require("http");
const https = require("https");
const { spawnSync } = require("child_process");
@ -85,14 +84,14 @@ function fetchLatestVersion(pkg) {
if (!pkgName) return Promise.resolve(null);
const encodedName = pkgName.replace(/\//g, "%2F");
const url = `${registry.replace(/\/$/, "")}/${encodedName}/latest`;
const client = url.startsWith("https") ? https : http;
if (!url.startsWith("https://")) return Promise.resolve(null);
return new Promise((resolve) => {
const options = {
headers: { "User-Agent": "ocr-updater", Accept: "application/json" },
timeout: 15000,
};
const req = client
const req = https
.get(url, options, (res) => {
if (res.statusCode !== 200) {
res.resume();
@ -173,26 +172,38 @@ async function main() {
}
if (config.checksumPattern) {
const checksumUrl = buildUrl(config.checksumPattern, vars);
let shaContent;
try {
const checksumUrl = buildUrl(config.checksumPattern, vars);
const shaContent = await downloadText(checksumUrl);
const actualSha = await computeChecksum(tempPath);
let verified = false;
for (const line of shaContent.split("\n")) {
const trimmed = line.trim();
if (trimmed.includes(`-${platform}-${arch}`)) {
const expectedSha = trimmed.split(/\s+/)[0].toLowerCase();
if (expectedSha && actualSha !== expectedSha) {
fs.unlinkSync(tempPath);
return;
}
verified = true;
break;
}
}
shaContent = await downloadText(checksumUrl);
} catch (_) {
// checksum fetch failed, continue with the download
fs.unlinkSync(tempPath);
return;
}
let actualSha;
try {
actualSha = await computeChecksum(tempPath);
} catch (_) {
fs.unlinkSync(tempPath);
return;
}
let verified = false;
for (const line of shaContent.split("\n")) {
const trimmed = line.trim();
if (trimmed.includes(`-${platform}-${arch}`)) {
const expectedSha = trimmed.split(/\s+/)[0].toLowerCase();
if (expectedSha && actualSha !== expectedSha) {
fs.unlinkSync(tempPath);
return;
}
verified = true;
break;
}
}
if (!verified) {
fs.unlinkSync(tempPath);
return;
}
}