herozion 1.1.67 → 1.1.71

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.
Files changed (2) hide show
  1. package/install.js +37 -0
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -54,6 +54,29 @@ function getBinaryName() {
54
54
 
55
55
  // ── Download helper ───────────────────────────────────────────────────────────
56
56
 
57
+ const _ALLOWED_REDIRECT_HOSTS = new Set([
58
+ "github.com",
59
+ "objects.githubusercontent.com",
60
+ "codeload.github.com",
61
+ "releases.githubusercontent.com",
62
+ ]);
63
+
64
+ function _validateRedirectUrl(location) {
65
+ if (!location || !location.startsWith("https://")) {
66
+ throw new Error(`Redirect to non-HTTPS URL rejected: ${location}`);
67
+ }
68
+ let host;
69
+ try { host = new URL(location).hostname; } catch (_) {
70
+ throw new Error(`Invalid redirect URL: ${location}`);
71
+ }
72
+ if (!_ALLOWED_REDIRECT_HOSTS.has(host)) {
73
+ throw new Error(
74
+ `Redirect to untrusted host rejected: ${host}\n` +
75
+ "Only github.com and githubusercontent.com are allowed."
76
+ );
77
+ }
78
+ }
79
+
57
80
  function download(url, destPath, redirectCount = 0) {
58
81
  return new Promise((resolve, reject) => {
59
82
  if (redirectCount > 5) {
@@ -61,6 +84,7 @@ function download(url, destPath, redirectCount = 0) {
61
84
  }
62
85
  https.get(url, { headers: { "User-Agent": "herozion-npm-installer" } }, (res) => {
63
86
  if (res.statusCode === 301 || res.statusCode === 302) {
87
+ try { _validateRedirectUrl(res.headers.location); } catch (e) { return reject(e); }
64
88
  return resolve(download(res.headers.location, destPath, redirectCount + 1));
65
89
  }
66
90
  if (res.statusCode !== 200) {
@@ -87,6 +111,7 @@ function downloadText(url, redirectCount = 0) {
87
111
  if (redirectCount > 5) return reject(new Error("Too many redirects"));
88
112
  https.get(url, { headers: { "User-Agent": "herozion-npm-installer" } }, (res) => {
89
113
  if (res.statusCode === 301 || res.statusCode === 302) {
114
+ try { _validateRedirectUrl(res.headers.location); } catch (e) { return reject(e); }
90
115
  return resolve(downloadText(res.headers.location, redirectCount + 1));
91
116
  }
92
117
  if (res.statusCode !== 200) {
@@ -191,6 +216,18 @@ async function main() {
191
216
  console.warn("Proceeding because bundled checksum verification passed.");
192
217
  }
193
218
 
219
+ // Fail closed: refuse to install if neither hash source could be verified.
220
+ if (!bundledHash) {
221
+ // We reach here only when bundledHash is empty AND remote check was skipped/failed.
222
+ // Secondary catch block re-throws when !bundledHash, so this path is belt-and-suspenders.
223
+ fs.unlinkSync(tmpPath);
224
+ throw new Error(
225
+ `Cannot verify ${binaryName}: no bundled hash found and remote checksum unavailable.\n` +
226
+ "Installation aborted to prevent supply-chain risk.\n" +
227
+ `Download manually from: ${BASE_URL}/${binaryName}`
228
+ );
229
+ }
230
+
194
231
  // Both checks passed — move binary into place
195
232
  fs.renameSync(tmpPath, destPath);
196
233
  if (!isWindows) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "herozion",
3
- "version": "1.1.67",
3
+ "version": "1.1.71",
4
4
  "description": "Security audit and performance analysis CLI tool for developers",
5
5
  "keywords": [
6
6
  "security",