diff --git a/collector/utils/files/index.js b/collector/utils/files/index.js index 3471654f..9b56bb5b 100644 --- a/collector/utils/files/index.js +++ b/collector/utils/files/index.js @@ -49,9 +49,9 @@ function writeToServerDocuments( const destination = destinationOverride ? path.resolve(destinationOverride) : path.resolve( - __dirname, - "../../../server/storage/documents/custom-documents" - ); + __dirname, + "../../../server/storage/documents/custom-documents" + ); if (!fs.existsSync(destination)) fs.mkdirSync(destination, { recursive: true }); const destinationFilePath = path.resolve(destination, filename) + ".json"; @@ -82,7 +82,7 @@ async function wipeCollectorStorage() { if (file === "__HOTDIR__.md") continue; try { fs.rmSync(path.join(directory, file)); - } catch { } + } catch {} } resolve(); }); @@ -97,7 +97,7 @@ async function wipeCollectorStorage() { if (file === ".placeholder") continue; try { fs.rmSync(path.join(directory, file)); - } catch { } + } catch {} } resolve(); }); diff --git a/server/endpoints/api/document/index.js b/server/endpoints/api/document/index.js index 158f4df6..50601c21 100644 --- a/server/endpoints/api/document/index.js +++ b/server/endpoints/api/document/index.js @@ -5,6 +5,7 @@ const { viewLocalFiles, findDocumentInDocuments, normalizePath, + isWithin, } = require("../../../utils/files"); const { reqBody } = require("../../../utils/http"); const { EventLogs } = require("../../../models/eventLogs"); @@ -603,6 +604,8 @@ function apiDocumentEndpoints(app) { try { const { name } = reqBody(request); const storagePath = path.join(documentsPath, normalizePath(name)); + if (!isWithin(path.resolve(documentsPath), path.resolve(storagePath))) + throw new Error("Invalid path name"); if (fs.existsSync(storagePath)) { response.status(500).json({ diff --git a/server/endpoints/document.js b/server/endpoints/document.js index 7d65b551..419e8d55 100644 --- a/server/endpoints/document.js +++ b/server/endpoints/document.js @@ -1,5 +1,5 @@ const { Document } = require("../models/documents"); -const { normalizePath, documentsPath } = require("../utils/files"); +const { normalizePath, documentsPath, isWithin } = require("../utils/files"); const { reqBody } = require("../utils/http"); const { flexUserRoleValid, @@ -18,6 +18,8 @@ function documentEndpoints(app) { try { const { name } = reqBody(request); const storagePath = path.join(documentsPath, normalizePath(name)); + if (!isWithin(path.resolve(documentsPath), path.resolve(storagePath))) + throw new Error("Invalid folder name."); if (fs.existsSync(storagePath)) { response.status(500).json({ diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 32715666..472e3aa7 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -1,7 +1,7 @@ process.env.NODE_ENV === "development" ? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` }) : require("dotenv").config(); -const { viewLocalFiles, normalizePath } = require("../utils/files"); +const { viewLocalFiles, normalizePath, isWithin } = require("../utils/files"); const { purgeDocument, purgeFolder } = require("../utils/files/purgeDocument"); const { getVectorDbClass } = require("../utils/helpers"); const { updateENV, dumpENV } = require("../utils/helpers/updateENV"); @@ -622,11 +622,13 @@ function systemEndpoints(app) { const userRecord = await User.get({ id: user.id }); const oldPfpFilename = userRecord.pfpFilename; if (oldPfpFilename) { + const storagePath = path.join(__dirname, "../storage/assets/pfp"); const oldPfpPath = path.join( - __dirname, - `../storage/assets/pfp/${normalizePath(userRecord.pfpFilename)}` + storagePath, + normalizePath(userRecord.pfpFilename) ); - + if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath))) + throw new Error("Invalid path name"); if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); } @@ -655,13 +657,14 @@ function systemEndpoints(app) { const userRecord = await User.get({ id: user.id }); const oldPfpFilename = userRecord.pfpFilename; - console.log("oldPfpFilename", oldPfpFilename); if (oldPfpFilename) { + const storagePath = path.join(__dirname, "../storage/assets/pfp"); const oldPfpPath = path.join( - __dirname, - `../storage/assets/pfp/${normalizePath(oldPfpFilename)}` + storagePath, + normalizePath(oldPfpFilename) ); - + if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath))) + throw new Error("Invalid path name"); if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); } diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index 61571659..2657eb97 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -6,7 +6,7 @@ const { userFromSession, safeJsonParse, } = require("../utils/http"); -const { normalizePath } = require("../utils/files"); +const { normalizePath, isWithin } = require("../utils/files"); const { Workspace } = require("../models/workspace"); const { Document } = require("../models/documents"); const { DocumentVectors } = require("../models/vectors"); @@ -629,13 +629,13 @@ function workspaceEndpoints(app) { const oldPfpFilename = workspaceRecord.pfpFilename; if (oldPfpFilename) { + const storagePath = path.join(__dirname, "../storage/assets/pfp"); const oldPfpPath = path.join( - __dirname, - `../storage/assets/pfp/${normalizePath( - workspaceRecord.pfpFilename - )}` + storagePath, + normalizePath(workspaceRecord.pfpFilename) ); - + if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath))) + throw new Error("Invalid path name"); if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); } @@ -670,11 +670,13 @@ function workspaceEndpoints(app) { const oldPfpFilename = workspaceRecord.pfpFilename; if (oldPfpFilename) { + const storagePath = path.join(__dirname, "../storage/assets/pfp"); const oldPfpPath = path.join( - __dirname, - `../storage/assets/pfp/${normalizePath(oldPfpFilename)}` + storagePath, + normalizePath(oldPfpFilename) ); - + if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath))) + throw new Error("Invalid path name"); if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); } diff --git a/server/utils/files/logo.js b/server/utils/files/logo.js index 68c56c21..3b861bc0 100644 --- a/server/utils/files/logo.js +++ b/server/utils/files/logo.js @@ -3,7 +3,7 @@ const fs = require("fs"); const { getType } = require("mime"); const { v4 } = require("uuid"); const { SystemSettings } = require("../../models/systemSettings"); -const { normalizePath } = require("."); +const { normalizePath, isWithin } = require("."); const LOGO_FILENAME = "anything-llm.png"; function validFilename(newFilename = "") { @@ -23,6 +23,8 @@ async function determineLogoFilepath(defaultFilename = LOGO_FILENAME) { if (currentLogoFilename && validFilename(currentLogoFilename)) { customLogoPath = path.join(basePath, normalizePath(currentLogoFilename)); + if (!isWithin(path.resolve(basePath), path.resolve(customLogoPath))) + return defaultFilepath; return fs.existsSync(customLogoPath) ? customLogoPath : defaultFilepath; } @@ -52,17 +54,17 @@ function fetchLogo(logoPath) { async function renameLogoFile(originalFilename = null) { const extname = path.extname(originalFilename) || ".png"; const newFilename = `${v4()}${extname}`; - const originalFilepath = process.env.STORAGE_DIR - ? path.join( - process.env.STORAGE_DIR, - "assets", - normalizePath(originalFilename) - ) - : path.join( - __dirname, - `../../storage/assets`, - normalizePath(originalFilename) - ); + const assetsDirectory = process.env.STORAGE_DIR + ? path.join(process.env.STORAGE_DIR, "assets") + : path.join(__dirname, `../../storage/assets`); + const originalFilepath = path.join( + assetsDirectory, + normalizePath(originalFilename) + ); + if (!isWithin(path.resolve(assetsDirectory), path.resolve(originalFilepath))) + throw new Error("Invalid file path."); + + // The output always uses a random filename. const outputFilepath = process.env.STORAGE_DIR ? path.join(process.env.STORAGE_DIR, "assets", normalizePath(newFilename)) : path.join(__dirname, `../../storage/assets`, normalizePath(newFilename)); @@ -73,9 +75,13 @@ async function renameLogoFile(originalFilename = null) { async function removeCustomLogo(logoFilename = LOGO_FILENAME) { if (!logoFilename || !validFilename(logoFilename)) return false; - const logoPath = process.env.STORAGE_DIR - ? path.join(process.env.STORAGE_DIR, `assets`, normalizePath(logoFilename)) - : path.join(__dirname, `../../storage/assets`, normalizePath(logoFilename)); + const assetsDirectory = process.env.STORAGE_DIR + ? path.join(process.env.STORAGE_DIR, "assets") + : path.join(__dirname, `../../storage/assets`); + + const logoPath = path.join(assetsDirectory, normalizePath(logoFilename)); + if (!isWithin(path.resolve(assetsDirectory), path.resolve(logoPath))) + throw new Error("Invalid file path."); if (fs.existsSync(logoPath)) fs.unlinkSync(logoPath); return true; } diff --git a/server/utils/files/pfp.js b/server/utils/files/pfp.js index 0d1dd9f8..2842614d 100644 --- a/server/utils/files/pfp.js +++ b/server/utils/files/pfp.js @@ -2,7 +2,7 @@ const path = require("path"); const fs = require("fs"); const { getType } = require("mime"); const { User } = require("../../models/user"); -const { normalizePath } = require("."); +const { normalizePath, isWithin } = require("."); const { Workspace } = require("../../models/workspace"); function fetchPfp(pfpPath) { @@ -35,6 +35,8 @@ async function determinePfpFilepath(id) { ? path.join(process.env.STORAGE_DIR, "assets/pfp") : path.join(__dirname, "../../storage/assets/pfp"); const pfpFilepath = path.join(basePath, normalizePath(pfpFilename)); + + if (!isWithin(path.resolve(basePath), path.resolve(pfpFilepath))) return null; if (!fs.existsSync(pfpFilepath)) return null; return pfpFilepath; } @@ -48,6 +50,8 @@ async function determineWorkspacePfpFilepath(slug) { ? path.join(process.env.STORAGE_DIR, "assets/pfp") : path.join(__dirname, "../../storage/assets/pfp"); const pfpFilepath = path.join(basePath, normalizePath(pfpFilename)); + + if (!isWithin(path.resolve(basePath), path.resolve(pfpFilepath))) return null; if (!fs.existsSync(pfpFilepath)) return null; return pfpFilepath; }