v1.1.0-beta.058 - fix(audit): deferred Low-severity follow-ups — GCM credential encryption, Internal DL stall-retry, schema indexes
🔐 Credential encryption → authenticated AES-256-GCM (audit Low #31)
- encryptSecret/decryptSecret moved from unauthenticated AES-256-CBC to AES-256-GCM, emitting a versioned enc:v2::: (12-byte IV + GCM auth tag) so at-rest tampering is now detectable. Exported signatures unchanged
- decrypt keeps a legacy enc:v1: CBC reader so already-stored secrets (download-client passwords, API keys, SMTP/OIDC secrets, TOTP seeds) still decrypt; every write emits v2, so values upgrade on next save
🩹 Internal DL mid-stream stall now retries instead of landing STALLED (audit Low #24)
- downloadDirectFile retried only stream ESTABLISHMENT; the pipeline transfer ran once outside the loop, so a 45s stall (stall-timer aborts the controller) rejected with no retry. Restructured so each attempt establishes the stream AND runs the transfer — a stall/abort (or a too-small file / HTML error page) now consumes a retry. 500KB-min check, stall timer, .part cleanup, and the Mega branch preserved, now per-attempt scoped
🛟 Parked batch siblings stranded by a failed lead (audit Low #24, part 2)
- A getcomics batch short-circuits duplicate requests, parking siblings as DOWNLOADING against one lead download; if the lead went terminally STALLED the siblings sat DOWNLOADING forever (the cron reconciler only matches active client torrents). The download checker now releases siblings of a terminal lead (STALLED, retryCount>=3) to STALLED. Gated on TERMINAL leads deliberately — gating on still-retryable ones would re-demote the actively-retrying lead each cycle (ping-pong)
🗃️ Schema indexes + dead-column drop (audit #38/#43/#41)
- Issue +@@index([releaseDate]) (global calendar range-scans it); Series +@@index([metadataId]) (library pendingOnly filters metadataId IN (...), which the composite unique can't serve); dropped the dead downloadId column (never read/written — grep-confirmed). Applied at next container start via the Dockerfile db push
- #37 (unique constraint on nullable (metadataSource, metadataId) + upsert) intentionally deferred: db push --accept-data-loss runs before app code, so CREATE UNIQUE INDEX would fail the boot on any duplicate/placeholder rows — needs verified-clean data first
🧪 Tests
- encryption suite rewritten: round-trip, fresh-IV, ciphertext/tag/truncation tamper-detection, legacy-v1 decrypt, idempotency, passthrough, env-fallback + insecure-secret guards
- cron: added request.updateMany mock + a parked-sibling-release test
✅ Verification
- tsc clean; eslint . 0 errors; vitest 281 passed (+8)