Release v1.4.17 with security fixes, stability hardening and retry improvements

- Fix ZIP path traversal vulnerability (reject entries escaping target dir)
- Add single instance lock (prevent data corruption from multiple instances)
- Add unhandled exception/rejection handlers (prevent silent crashes)
- Fix mainWindow reference cleanup on close
- Add second-instance handler to focus existing window
- Fix claimTargetPath infinite loop (add 10k iteration bound)
- Add duplicate startItem guard (prevent concurrent downloads of same item)
- Clone session in getSnapshot to prevent live-reference mutation bugs
- Clear stateEmitTimer on clearAll to prevent dangling timer emissions
- Add extraction timeout safety (4h deadline with logging)
- Add dedicated unrestrict retry system with longer backoff for Mega-Debrid errors
- Add log rotation (10MB max, keeps one .old backup)
- Fix writeExtractResumeState missing mkdir (prevents crash on deleted dirs)
- Fix saveSessionAsync EXDEV cross-device rename with copy fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe
2026-02-28 05:04:21 +01:00
co-authored by Claude Opus 4.6
parent ea6301d326
commit d4dd266f6b
6 changed files with 127 additions and 12 deletions
+16 -6
View File
@@ -232,11 +232,16 @@ function readExtractResumeState(packageDir: string): Set<string> {
}
function writeExtractResumeState(packageDir: string, completedArchives: Set<string>): void {
const progressPath = extractProgressFilePath(packageDir);
const payload: ExtractResumeState = {
completedArchives: Array.from(completedArchives).sort((a, b) => a.localeCompare(b))
};
fs.writeFileSync(progressPath, JSON.stringify(payload, null, 2), "utf8");
try {
fs.mkdirSync(packageDir, { recursive: true });
const progressPath = extractProgressFilePath(packageDir);
const payload: ExtractResumeState = {
completedArchives: Array.from(completedArchives).sort((a, b) => a.localeCompare(b))
};
fs.writeFileSync(progressPath, JSON.stringify(payload, null, 2), "utf8");
} catch (error) {
logger.warn(`ExtractResumeState schreiben fehlgeschlagen: ${String(error)}`);
}
}
function clearExtractResumeState(packageDir: string): void {
@@ -582,8 +587,13 @@ function extractZipArchive(archivePath: string, targetDir: string, conflictMode:
const mode = effectiveConflictMode(conflictMode);
const zip = new AdmZip(archivePath);
const entries = zip.getEntries();
const resolvedTarget = path.resolve(targetDir);
for (const entry of entries) {
const outputPath = path.join(targetDir, entry.entryName);
const outputPath = path.resolve(targetDir, entry.entryName);
if (!outputPath.startsWith(resolvedTarget + path.sep) && outputPath !== resolvedTarget) {
logger.warn(`ZIP-Eintrag übersprungen (Path Traversal): ${entry.entryName}`);
continue;
}
if (entry.isDirectory) {
fs.mkdirSync(outputPath, { recursive: true });
continue;