v1.1.0-beta.054 - fix(security): XSS + SSRF + path-traversal hardening from the full audit
๐งผ Stored XSS in the series synopsis (audit High #3)
- The series page renders provider descriptions via dangerouslySetInnerHTML, but /api/library/issue and /api/library/series returned raw ComicVine/Metron HTML โ a poisoned description executed in the viewer's session. Both now run descriptions through the existing sanitizeDescription() (sanitize-html) on the way out, cleaning fresh AND already-stored data
๐ง sanitizeFilename path traversal (audit Medium #12)
- sanitizeFilename stripped <>:"/|?* but not dots, so an untrusted ComicInfo field of ".." became a real parent-dir segment once joined into a folder path (arbitrary write location). Now strips leading/trailing dots and collapses an all-dots value to "_"; interior dots (Vol.1, "Mr. Robot") are preserved
๐ SSRF guards on server-side fetches of untrusted URLs (audit Medium #9, #10)
- New src/lib/utils/ssrf.ts: assertSafeFetchUrl rejects non-http(s) schemes and loopback/private/link-local hosts (169.254.169.254, localhost, RFC1918, IPv6 ULA/link-local, obfuscated decimal IPs); assertSafeRedirect re-validates each axios redirect hop
- CBL import: guards the user-supplied URL, blocks redirects, adds a timeout + body-size cap
- Download pipeline (addDownload + downloadDirectFile): guards scraped/indexer URLs before fetch and constrains maxRedirects with per-hop re-validation
๐งช Tests
- ssrf host/url blocking (incl. decimal-IP + 172.16/12 boundaries); sanitizeFilename traversal; CBL refuses an internal SSRF target without fetching
โ Verification
- tsc clean; eslint . 0 errors; vitest 264 passed (+9)