herozion 1.1.68 → 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.
- package/install.js +37 -0
- 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) {
|