herozion 2.0.13 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/checksums.json +5 -5
- package/install.js +94 -59
- package/package.json +1 -1
package/bin/checksums.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "SHA-256 hashes for each platform binary. Auto-generated in CI from release CHECKSUMS.sha256. DO NOT edit manually.",
|
|
3
|
-
"_version": "2.0.
|
|
4
|
-
"herozion-linux-amd64": "
|
|
5
|
-
"herozion-macos-amd64.tar.gz": "
|
|
6
|
-
"herozion-macos-arm64.tar.gz": "
|
|
7
|
-
"herozion-windows-amd64.exe": "
|
|
3
|
+
"_version": "2.0.16",
|
|
4
|
+
"herozion-linux-amd64": "a6f14d840c17e335bb1d34edb88ee9aad688db73595de8a0573091538bfc8796",
|
|
5
|
+
"herozion-macos-amd64.tar.gz": "d2d99b9a51ccfcd533b8c7e62af924886ae5c090772a3ff43dbbed26ea3d9e02",
|
|
6
|
+
"herozion-macos-arm64.tar.gz": "c29772a2ef54953e5a86a5dafb2e06cf456e5cc1765ff506f243d4d171ffe499",
|
|
7
|
+
"herozion-windows-amd64.exe": "711328645a1e692e7f63e6eb86d77b0da8b4797c64e2ed743abf3fa7cc100d88"
|
|
8
8
|
}
|
package/install.js
CHANGED
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
* 1. PRIMARY trust anchor: bin/checksums.json bundled inside this npm package.
|
|
15
15
|
* These hashes are embedded at `npm publish` time and cannot be swapped by
|
|
16
16
|
* a GitHub Releases compromise alone.
|
|
17
|
-
* 2. SECONDARY verification:
|
|
18
|
-
*
|
|
17
|
+
* 2. SECONDARY verification: CHECKSUMS.sha256 from GitHub Releases.
|
|
18
|
+
* A remote hash mismatch rejects the binary even when the bundled hash matched.
|
|
19
|
+
* If that file cannot be fetched and the bundled hash matched, install continues
|
|
20
|
+
* with a warning.
|
|
19
21
|
*
|
|
20
22
|
* Supported platforms:
|
|
21
23
|
* Windows x64 → herozion-windows-amd64.exe
|
|
@@ -94,13 +96,23 @@ function _validateRedirectUrl(location) {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
function _isRedirect(statusCode) {
|
|
100
|
+
return (
|
|
101
|
+
statusCode === 301 ||
|
|
102
|
+
statusCode === 302 ||
|
|
103
|
+
statusCode === 303 ||
|
|
104
|
+
statusCode === 307 ||
|
|
105
|
+
statusCode === 308
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
97
109
|
function download(url, destPath, redirectCount = 0) {
|
|
98
110
|
return new Promise((resolve, reject) => {
|
|
99
111
|
if (redirectCount > 5) {
|
|
100
112
|
return reject(new Error("Too many redirects"));
|
|
101
113
|
}
|
|
102
114
|
https.get(url, { headers: { "User-Agent": "herozion-npm-installer" } }, (res) => {
|
|
103
|
-
if (res.statusCode
|
|
115
|
+
if (_isRedirect(res.statusCode)) {
|
|
104
116
|
try { _validateRedirectUrl(res.headers.location); } catch (e) { return reject(e); }
|
|
105
117
|
return resolve(download(res.headers.location, destPath, redirectCount + 1));
|
|
106
118
|
}
|
|
@@ -127,7 +139,7 @@ function downloadText(url, redirectCount = 0) {
|
|
|
127
139
|
return new Promise((resolve, reject) => {
|
|
128
140
|
if (redirectCount > 5) return reject(new Error("Too many redirects"));
|
|
129
141
|
https.get(url, { headers: { "User-Agent": "herozion-npm-installer" } }, (res) => {
|
|
130
|
-
if (res.statusCode
|
|
142
|
+
if (_isRedirect(res.statusCode)) {
|
|
131
143
|
try { _validateRedirectUrl(res.headers.location); } catch (e) { return reject(e); }
|
|
132
144
|
return resolve(downloadText(res.headers.location, redirectCount + 1));
|
|
133
145
|
}
|
|
@@ -276,57 +288,13 @@ async function main() {
|
|
|
276
288
|
await download(url, tmpPath);
|
|
277
289
|
|
|
278
290
|
const actualHash = sha256File(tmpPath);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
throw new Error(
|
|
287
|
-
`Bundled checksum mismatch for ${binaryName}.\n` +
|
|
288
|
-
` Expected (bundled) : ${bundledHash}\n` +
|
|
289
|
-
` Got : ${actualHash}\n` +
|
|
290
|
-
"The downloaded binary does not match the hash embedded in this npm package.\n" +
|
|
291
|
-
"This may indicate a supply-chain attack. Aborting installation."
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
bundledVerified = true;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// ── Secondary check: CHECKSUMS.sha256 from GitHub Releases ────────────────
|
|
298
|
-
try {
|
|
299
|
-
const checksumData = await downloadText(checksumUrl);
|
|
300
|
-
// Parse the combined checksum file: each line is "<hash> <filename>"
|
|
301
|
-
const remoteHash = (() => {
|
|
302
|
-
for (const line of checksumData.split(/\n/)) {
|
|
303
|
-
const parts = line.trim().split(/\s+/);
|
|
304
|
-
if (parts.length >= 2 && parts[1] === binaryName) {
|
|
305
|
-
return parts[0].toLowerCase();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return "";
|
|
309
|
-
})();
|
|
310
|
-
if (remoteHash) {
|
|
311
|
-
if (actualHash !== remoteHash) {
|
|
312
|
-
fs.unlinkSync(tmpPath);
|
|
313
|
-
throw new Error(
|
|
314
|
-
`Remote checksum mismatch for ${binaryName}.\n` +
|
|
315
|
-
` Expected (remote) : ${remoteHash}\n` +
|
|
316
|
-
` Got : ${actualHash}\n` +
|
|
317
|
-
"The downloaded binary does not match the GitHub Releases CHECKSUMS.sha256 file."
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
remoteVerified = true;
|
|
321
|
-
}
|
|
322
|
-
} catch (checksumErr) {
|
|
323
|
-
// If bundled verification already passed, remote failure is non-fatal.
|
|
324
|
-
if (!bundledVerified) {
|
|
325
|
-
throw checksumErr;
|
|
326
|
-
}
|
|
327
|
-
console.warn(`herozion: WARNING — could not verify remote checksum: ${checksumErr.message}`);
|
|
328
|
-
console.warn("Proceeding because bundled checksum verification passed.");
|
|
329
|
-
}
|
|
291
|
+
// A remote CHECKSUMS mismatch is rethrown even when the bundled hash matched.
|
|
292
|
+
const { bundledVerified, remoteVerified } = await verifyChecksums({
|
|
293
|
+
actualHash,
|
|
294
|
+
bundledHash,
|
|
295
|
+
binaryName,
|
|
296
|
+
readRemoteChecksums: () => downloadText(checksumUrl),
|
|
297
|
+
});
|
|
330
298
|
|
|
331
299
|
// Fail closed: require at least one independent verification source.
|
|
332
300
|
if (!bundledVerified && !remoteVerified) {
|
|
@@ -367,7 +335,74 @@ async function main() {
|
|
|
367
335
|
}
|
|
368
336
|
}
|
|
369
337
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
338
|
+
function parseRemoteChecksum(checksumData, binaryName) {
|
|
339
|
+
for (const line of String(checksumData).split(/\n/)) {
|
|
340
|
+
const parts = line.trim().split(/\s+/);
|
|
341
|
+
if (parts.length >= 2 && parts[1] === binaryName) {
|
|
342
|
+
return parts[0].toLowerCase();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return "";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Compare the downloaded bytes with the bundled hash and CHECKSUMS.sha256.
|
|
350
|
+
* A remote mismatch always rejects the binary. A failure to fetch the remote
|
|
351
|
+
* file is fatal only when the bundled hash did not already match.
|
|
352
|
+
*/
|
|
353
|
+
async function verifyChecksums({ actualHash, bundledHash, binaryName, readRemoteChecksums }) {
|
|
354
|
+
let bundledVerified = false;
|
|
355
|
+
let remoteVerified = false;
|
|
356
|
+
if (bundledHash) {
|
|
357
|
+
if (actualHash !== bundledHash) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Bundled checksum mismatch for ${binaryName}.\n` +
|
|
360
|
+
` Expected (bundled) : ${bundledHash}\n` +
|
|
361
|
+
` Got : ${actualHash}\n` +
|
|
362
|
+
"The downloaded binary does not match the hash embedded in this npm package.\n" +
|
|
363
|
+
"This may indicate a supply-chain attack. Aborting installation."
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
bundledVerified = true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let remoteMismatch = null;
|
|
370
|
+
try {
|
|
371
|
+
const remoteHash = parseRemoteChecksum(await readRemoteChecksums(), binaryName);
|
|
372
|
+
if (remoteHash && actualHash !== remoteHash) {
|
|
373
|
+
remoteMismatch = new Error(
|
|
374
|
+
`Remote checksum mismatch for ${binaryName}.\n` +
|
|
375
|
+
` Expected (remote) : ${remoteHash}\n` +
|
|
376
|
+
` Got : ${actualHash}\n` +
|
|
377
|
+
"The downloaded binary does not match the GitHub Releases CHECKSUMS.sha256 file."
|
|
378
|
+
);
|
|
379
|
+
} else if (remoteHash) {
|
|
380
|
+
remoteVerified = true;
|
|
381
|
+
}
|
|
382
|
+
} catch (checksumErr) {
|
|
383
|
+
if (!bundledVerified) {
|
|
384
|
+
throw checksumErr;
|
|
385
|
+
}
|
|
386
|
+
console.warn(`herozion: WARNING — could not verify remote checksum: ${checksumErr.message}`);
|
|
387
|
+
console.warn("Proceeding because bundled checksum verification passed.");
|
|
388
|
+
}
|
|
389
|
+
if (remoteMismatch) {
|
|
390
|
+
throw remoteMismatch;
|
|
391
|
+
}
|
|
392
|
+
return { bundledVerified, remoteVerified };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (require.main === module) {
|
|
396
|
+
main().catch((err) => {
|
|
397
|
+
console.error("herozion install error:", err.message);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
module.exports = {
|
|
403
|
+
_isRedirect,
|
|
404
|
+
download,
|
|
405
|
+
downloadText,
|
|
406
|
+
parseRemoteChecksum,
|
|
407
|
+
verifyChecksums,
|
|
408
|
+
};
|