molt-cli 1.0.1 → 1.0.3
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/cli.js +43 -3
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -120,10 +120,16 @@ function signMessage(message, privateKeyBase64) {
|
|
|
120
120
|
|
|
121
121
|
function computeProofOfWork(publicKey) {
|
|
122
122
|
let nonce = 0;
|
|
123
|
+
const startTime = Date.now();
|
|
123
124
|
while (true) {
|
|
124
125
|
const hash = crypto.createHash('sha256').update(publicKey + nonce).digest('hex');
|
|
125
|
-
if (hash.startsWith('
|
|
126
|
+
if (hash.startsWith('000000')) return nonce; // 6 zeros ≈ 5-15 seconds
|
|
126
127
|
nonce++;
|
|
128
|
+
// Safety: log progress every 5M hashes
|
|
129
|
+
if (nonce % 5000000 === 0) {
|
|
130
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
131
|
+
process.stdout.write(`\r Mining proof-of-work... ${(nonce / 1000000).toFixed(0)}M hashes (${elapsed}s)`);
|
|
132
|
+
}
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
|
|
@@ -332,13 +338,45 @@ function extractTarGz(tarGzPath, destDir) {
|
|
|
332
338
|
const extract = tar.extract();
|
|
333
339
|
const gunzip = zlib.createGunzip();
|
|
334
340
|
|
|
341
|
+
const MAX_EXTRACTED_SIZE = 50 * 1024 * 1024; // 50MB max decompressed
|
|
342
|
+
const MAX_FILES = 500; // max files in package
|
|
343
|
+
let totalSize = 0;
|
|
344
|
+
let fileCount = 0;
|
|
345
|
+
const resolvedDest = path.resolve(destDir);
|
|
346
|
+
|
|
335
347
|
extract.on('entry', (header, stream, next) => {
|
|
336
|
-
|
|
348
|
+
fileCount++;
|
|
349
|
+
if (fileCount > MAX_FILES) {
|
|
350
|
+
stream.destroy();
|
|
351
|
+
return reject(new Error(`Too many files in package (max ${MAX_FILES}). Aborting.`));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Path traversal protection: resolve and verify within destDir
|
|
355
|
+
const filePath = path.resolve(destDir, header.name);
|
|
356
|
+
if (!filePath.startsWith(resolvedDest + path.sep) && filePath !== resolvedDest) {
|
|
357
|
+
stream.resume(); // skip malicious entry
|
|
358
|
+
console.log(` ${C.red}⚠ Skipping suspicious path: ${header.name}${C.reset}`);
|
|
359
|
+
return next();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Skip symlinks (potential attack vector)
|
|
363
|
+
if (header.type === 'symlink' || header.type === 'link') {
|
|
364
|
+
stream.resume();
|
|
365
|
+
return next();
|
|
366
|
+
}
|
|
367
|
+
|
|
337
368
|
if (header.type === 'directory') {
|
|
338
369
|
fs.mkdirSync(filePath, { recursive: true });
|
|
339
370
|
stream.resume();
|
|
340
371
|
next();
|
|
341
372
|
} else {
|
|
373
|
+
// Tar bomb protection: track decompressed size
|
|
374
|
+
totalSize += header.size || 0;
|
|
375
|
+
if (totalSize > MAX_EXTRACTED_SIZE) {
|
|
376
|
+
stream.destroy();
|
|
377
|
+
return reject(new Error(`Package too large when extracted (>${MAX_EXTRACTED_SIZE / 1024 / 1024}MB). Possible tar bomb.`));
|
|
378
|
+
}
|
|
379
|
+
|
|
342
380
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
343
381
|
const out = fs.createWriteStream(filePath);
|
|
344
382
|
stream.pipe(out);
|
|
@@ -858,7 +896,9 @@ async function cmdInstall(skillName) {
|
|
|
858
896
|
console.log(` ${C.dim}Installed to: ${C.cyan}${path.relative(process.cwd(), destDir) || destDir}${C.reset}`);
|
|
859
897
|
console.log();
|
|
860
898
|
} catch (err) {
|
|
861
|
-
|
|
899
|
+
// Clear any active spinners before showing error
|
|
900
|
+
process.stdout.write('\r\x1b[K');
|
|
901
|
+
errorMsg(err.error || err.message || 'Install failed', err.hint);
|
|
862
902
|
}
|
|
863
903
|
}
|
|
864
904
|
|