v1.1.0-beta.057 - fix(audit): Low-severity cleanup batch — security, correctness, dead code
🔐 Security
- #19 reading-lists/items PUT scopes item updates to the verified listId (updateMany) — closes a cross-list reorder IDOR
- #20 api-auth rejects ADMIN/LEGACY keys supplied via ?apiKey= (URL secrets leak to logs/history/Referer); OPDS reader keys may still use the query
- #21 reset-password validates email format early and fires the notifier async so response time can't reveal whether an account exists
- #22 a wrong TOTP after a correct password throws a distinct 2FA_INVALID (login UI shows it on the 2FA screen) instead of bumping the password lockout + "invalid username or password"
- #30 standardized bcrypt cost to 12 at the two sites that had drifted to 10 (admin user-create, user/security)
- #32 in-handler role==='ADMIN' on admin/active-downloads, search-prowlarr, logs (GET+DELETE), job-logs (GET+DELETE) — defense-in-depth behind the middleware gate
- #18/#42 match-series + cover-upload path containment use separator-safe isPathWithinRoots
- #28 reviews POST validates rating in [1,5] (raw requests bypassed the star UI; no DB CHECK)
🩹 Correctness
- #26 metron cover-fetch rate-limit breaker checked for 'RATE_LIMIT' (never thrown) — now 'FATAL_RATE_LIMIT'/429; it was dead
- #27 KOReader progress binds to an EXACT filename (unambiguous) with full-path disambiguation — no more wrong-issue binding from duplicate basenames or '1.cbz' matching '001.cbz'
- #29 CBL & CSV series matching: exact normalized match first, ambiguous prefix only when a single candidate — 'Batman' no longer links to 'Batman Beyond'
- #33 weekly-digest history writes via createMany so a partial failure can't re-email next run
- #34 metadata-fetcher warns when a 2000+ issue volume is clipped by the pagination cap (was silent)
- #35 writeComicInfo only splits a well-formed date into Year/Month/Day; a slash/text date no longer corrupts
- #36 getFolderSize 'invalid or missing' log moved inside the guard (it fired only on the success path)
- #23/#25 surface swallowed errors: getAllActiveDownloads logs a broken/mis-authed client; getcomics logs a failed FlareSolverr-settings read
🧹 Dead code
- #40 deleted unused src/lib/auth.ts (empty providers:[] scaffold, zero importers) + its two stale test mocks
- #39 manual-upload-dialog uses the shared formatBytes (utils/format) instead of a divergent local copy
🧪 Tests
- Updated existing tests for the new prisma calls (cover-upload paths mock, queue createMany, koreader findMany)
✅ Verification
- tsc clean; eslint . 0 errors; vitest 273 passed