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.
@@ -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.13",
4
- "herozion-linux-amd64": "a20617f88c2fe0221d08e5a5c7d5b1bfb281f8f98a9c7b027a88527cc2d3d420",
5
- "herozion-macos-amd64.tar.gz": "5c8dd9c0954a9c0447d40e07f1d83901e28a7c99d43c9afc3366dc7180cf177c",
6
- "herozion-macos-arm64.tar.gz": "b710499760b841adb20fc7f5d562b4c5a8b5c7a9d67579e7d8b801639abc149c",
7
- "herozion-windows-amd64.exe": "61a1b876158105a9b2c9592368bcfcbb92b5030b13d8acd21342ca320c95b65d"
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: <binary>.sha256 downloaded from GitHub Releases.
18
- * Both must agree; if either fails the binary is rejected.
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 === 301 || res.statusCode === 302) {
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 === 301 || res.statusCode === 302) {
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
- let bundledVerified = false;
280
- let remoteVerified = false;
281
-
282
- // ── Primary check: bundled checksum (trust anchor inside the npm package) ─
283
- if (bundledHash) {
284
- if (actualHash !== bundledHash) {
285
- fs.unlinkSync(tmpPath);
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
- main().catch((err) => {
371
- console.error("herozion install error:", err.message);
372
- process.exit(1);
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "herozion",
3
- "version": "2.0.13",
3
+ "version": "2.0.16",
4
4
  "description": "Security audit and performance analysis CLI tool for developers",
5
5
  "keywords": [
6
6
  "security",