v1.1.0-beta.053 - fix(security): auth/access hardening from the full audit (High-severity batch)
๐ Admin authorization on destructive library routes (audit High #1)
- library/update, match-series, refresh-metadata, refresh-series did folder relocations / DB rewrites / Smart-Matcher re-matches with no role check โ middleware only gates /api/admin/*, so any authenticated non-admin reached them. Added role==='ADMIN' gates before any mutation; match-series reuses the session it already fetched for the audit log
๐๏ธ MAL/AniList import cross-user data loss (audit High #2)
- deleteMany trusted isGlobal from the request body and ran BEFORE the permission check, letting a non-privileged user delete (and cascade-delete the items of) global lists they don't own. Resolve canMakeGlobal first, derive effectiveGlobal, use it for both the delete scope and the create
๐ Single-use password-reset token (audit High #4)
- HMAC was bound only to id|expiration with no used-flag, so the link replayed for its full hour. Now binds the user's current sessionVersion into the HMAC (not the token plaintext) and consumes it with a conditional updateMany(where sessionVersion) โ the reset increments sessionVersion so any replay recomputes a different signature; the conditional also closes the concurrent double-submit race. Both minting routes updated
๐ฆ Rate-limit no longer trusts raw X-Forwarded-For (audit High #5)
- register / reset-request / reset-confirm keyed on the raw XFF header (wrong behind a proxy; freshly unique per request in direct-exposure โ lockout bypass). Added getClientIp (first hop, x-real-ip fallback) + an IP-independent checkGlobalRateLimit backstop (fixed-window) so header rotation can't defeat the cap
๐ Cover route path containment (audit Medium #8)
- Replaced the boundary-less startsWith check (sibling root "/data/comics-private" passed the "/data/comics" gate) with the existing separator-aware isPathWithinRoots
๐งช Tests
- match-series 403-for-non-admin; import-anilist non-admin isGlobal scoping; reset token replay + concurrent double-submit; getClientIp + global-cap unit tests
โ Verification
- tsc clean; eslint . 0 errors; vitest 255 passed (+8 new)