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
+26
View File
@@ -5,6 +5,8 @@ let logFilePath = path.resolve(process.cwd(), "rd_downloader.log");
let fallbackLogFilePath: string | null = null;
const LOG_FLUSH_INTERVAL_MS = 120;
const LOG_BUFFER_LIMIT_CHARS = 1_000_000;
const LOG_MAX_FILE_BYTES = 10 * 1024 * 1024;
let lastRotateCheckAt = 0;
let pendingLines: string[] = [];
let pendingChars = 0;
@@ -90,6 +92,29 @@ function scheduleFlush(immediate = false): void {
}, LOG_FLUSH_INTERVAL_MS);
}
function rotateIfNeeded(filePath: string): void {
try {
const now = Date.now();
if (now - lastRotateCheckAt < 60_000) {
return;
}
lastRotateCheckAt = now;
const stat = fs.statSync(filePath);
if (stat.size < LOG_MAX_FILE_BYTES) {
return;
}
const backup = `${filePath}.old`;
try {
fs.rmSync(backup, { force: true });
} catch {
// ignore
}
fs.renameSync(filePath, backup);
} catch {
// ignore - file may not exist yet
}
}
async function flushAsync(): Promise<void> {
if (flushInFlight || pendingLines.length === 0) {
return;
@@ -101,6 +126,7 @@ async function flushAsync(): Promise<void> {
pendingChars = 0;
try {
rotateIfNeeded(logFilePath);
const primary = await appendChunk(logFilePath, chunk);
if (fallbackLogFilePath) {
const fallback = await appendChunk(fallbackLogFilePath, chunk);