From 455e6051624316856c8ccaf8bcdb83bd289e2112 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 17 Apr 2026 16:30:08 -0500 Subject: [PATCH 1/3] Update author & library item image endpoints to clamp width/height query params --- server/controllers/AuthorController.js | 6 +++--- server/controllers/LibraryItemController.js | 6 +++--- server/utils/index.js | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 82ed3e50a..80471ec47 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -10,7 +10,7 @@ const CacheManager = require('../managers/CacheManager') const CoverManager = require('../managers/CoverManager') const AuthorFinder = require('../finders/AuthorFinder') -const { reqSupportsWebp, isValidASIN } = require('../utils/index') +const { reqSupportsWebp, isValidASIN, clampPositiveInt } = require('../utils/index') const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare @@ -412,8 +412,8 @@ class AuthorController { const options = { format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'), - height: height ? parseInt(height) : null, - width: width ? parseInt(width) : null + height: clampPositiveInt(height ? parseInt(height) : null, 4096), + width: clampPositiveInt(width ? parseInt(width) : null, 4096) } return CacheManager.handleAuthorCache(res, authorId, options) } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 5f7bd9736..1a6b8ac11 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -7,7 +7,7 @@ const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') const zipHelpers = require('../utils/zipHelpers') -const { reqSupportsWebp } = require('../utils/index') +const { reqSupportsWebp, clampPositiveInt } = require('../utils/index') const { ScanResult, AudioMimeType } = require('../utils/constants') const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils') const LibraryItemScanner = require('../scanner/LibraryItemScanner') @@ -398,8 +398,8 @@ class LibraryItemController { const options = { format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'), - height: height ? parseInt(height) : null, - width: width ? parseInt(width) : null + height: clampPositiveInt(height ? parseInt(height) : null, 4096), + width: clampPositiveInt(width ? parseInt(width) : null, 4096) } return CacheManager.handleCoverCache(res, libraryItemId, options) } diff --git a/server/utils/index.js b/server/utils/index.js index c7700a783..49a7c8e67 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -54,6 +54,16 @@ module.exports.isNullOrNaN = (num) => { return num === null || isNaN(num) } +/** + * @param {number|null|undefined} value + * @param {number} max + * @returns {number|null} + */ +module.exports.clampPositiveInt = (value, max) => { + if (value == null || !Number.isFinite(value) || value <= 0) return null + return Math.min(Math.floor(value), max) +} + const xmlToJSON = (xml) => { return new Promise((resolve, reject) => { parseString(xml, (err, results) => { From 09fa0b38f5af5a9bc8d07b1541dc21718585e697 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 17 Apr 2026 16:51:22 -0500 Subject: [PATCH 2/3] Update podcast create path validation & fix relPath --- server/controllers/PodcastController.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index c70287600..f099d05ed 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -7,7 +7,7 @@ const Database = require('../Database') const fs = require('../libs/fsExtra') const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils') -const { getFileTimestampsWithIno, filePathToPOSIX } = require('../utils/fileUtils') +const { getFileTimestampsWithIno, filePathToPOSIX, isSameOrSubPath } = require('../utils/fileUtils') const { validateUrl } = require('../utils/index') const htmlSanitizer = require('../utils/htmlSanitizer') @@ -58,8 +58,18 @@ class PodcastController { return res.status(404).send('Folder not found') } + if (typeof payload.path !== 'string' || !payload.path.trim()) { + return res.status(400).send('Invalid request body. "path" must be a non-empty string') + } + + const libraryFolderPath = filePathToPOSIX(folder.path) const podcastPath = filePathToPOSIX(payload.path) + if (!isSameOrSubPath(libraryFolderPath, podcastPath)) { + Logger.error(`[PodcastController] Create: Podcast path is outside library folder "${libraryFolderPath}": "${podcastPath}"`) + return res.status(400).send('Podcast path must be inside the selected library folder') + } + // Check if a library item with this podcast folder exists already const existingLibraryItem = (await Database.libraryItemModel.count({ @@ -83,7 +93,7 @@ class PodcastController { const libraryItemFolderStats = await getFileTimestampsWithIno(podcastPath) - let relPath = payload.path.replace(folder.fullPath, '') + let relPath = podcastPath.replace(libraryFolderPath, '') if (relPath.startsWith('/')) relPath = relPath.slice(1) let newLibraryItem = null From b27f21fd95b413c5ecd74f5b14d8f2c0284ba6d7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 17 Apr 2026 16:59:22 -0500 Subject: [PATCH 3/3] Update podcastUtils to sanitize episode subtitle from rss feed --- server/utils/podcastUtils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js index 2042a8e39..1cb0c4cb4 100644 --- a/server/utils/podcastUtils.js +++ b/server/utils/podcastUtils.js @@ -217,6 +217,10 @@ function extractEpisodeData(item) { episode[cleanKey] = extractFirstArrayItemString(item, key) }) + if (episode.subtitle) { + episode.subtitle = htmlSanitizer.sanitize(episode.subtitle.trim()) + } + // Extract psc:chapters if duration is set episode.durationSeconds = episode.duration ? timestampToSeconds(episode.duration) : null