CVE-2023-4897: Relative Path Traversal (CWE-23)
value: Relative Path Traversal in GitHub repository mintplex-labs/anything-llm prior to 0.0.1.
CWE description: The product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as ".." that can resolve to a location that is outside of that directory.
CVSS v3 base score: 9.8
Published: 2023-09-11T21:15Z
Repository: https://github.com/mintplex-labs/anything-llm
Language: JavaScript
Files changed: frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx, server/endpoints/system.js
Commit date: 2023-09-11 13:07:48 -0700
Commit message: prevent exports path traversal (#233)


Diff: diff --git a/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx b/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx
index 3dcfd33..628f22c 100644
--- a/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx
+++ b/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx
@@ -175,12 +175,13 @@ export default function DocumentSettings({ workspace }) {
             )}
           </div>
         </div>
       </div>
       <div
-        className={`flex items-center ${canDelete ? "justify-between" : "justify-end"
-          } p-4 md:p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600`}
+        className={`flex items-center ${
+          canDelete ? "justify-between" : "justify-end"
+        } p-4 md:p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600`}
       >
         <button
           hidden={!canDelete}
           onClick={deleteWorkspace}
           type="button"
diff --git a/server/endpoints/system.js b/server/endpoints/system.js
index 736ab14..d493a2c 100644
--- a/server/endpoints/system.js
+++ b/server/endpoints/system.js
@@ -22,10 +22,11 @@ const { v4 } = require("uuid");
 const { SystemSettings } = require("../models/systemSettings");
 const { User } = require("../models/user");
 const { validatedRequest } = require("../utils/middleware/validatedRequest");
 const { handleImports } = setupDataImports();
 const { handleLogoUploads } = setupLogoUploads();
+const fs = require("fs");
 const path = require("path");
 const {
   getDefaultFilename,
   determineLogoFilepath,
   fetchLogo,
@@ -313,13 +314,25 @@ function systemEndpoints(app) {
 
   app.get(
     "/system/data-exports/:filename",
     [validatedRequest],
     (request, response) => {
-      const filePath =
-        __dirname + "/../storage/exports/" + request.params.filename;
-      response.download(filePath, request.params.filename, (err) => {
+      const exportLocation = __dirname + "/../storage/exports/";
+      const sanitized = path
+        .normalize(request.params.filename)
+        .replace(/^(\.\.(\/|\\|$))+/, "");
+      const finalDestination = path.join(exportLocation, sanitized);
+
+      if (!fs.existsSync(finalDestination)) {
+        response.status(404).json({
+          error: 404,
+          msg: `File ${request.params.filename} does not exist in exports.`,
+        });
+        return;
+      }
+
+      response.download(finalDestination, request.params.filename, (err) => {
         if (err) {
           response.send({
             error: err,
             msg: "Problem downloading the file",
           });
@@ -446,17 +459,15 @@ function systemEndpoints(app) {
 
         const canDelete = await SystemSettings.canDeleteWorkspaces();
         response.status(200).json({ canDelete });
       } catch (error) {
         console.error("Error fetching can delete workspaces:", error);
-        response
-          .status(500)
-          .json({
-            success: false,
-            message: "Internal server error",
-            canDelete: false,
-          });
+        response.status(500).json({
+          success: false,
+          message: "Internal server error",
+          canDelete: false,
+        });
       }
     }
   );
 
   app.get("/system/welcome-messages", async function (request, response) {

Vulnerable code:         className={`flex items-center ${canDelete ? "justify-between" : "justify-end"
          } p-4 md:p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600`}
      const filePath =
        __dirname + "/../storage/exports/" + request.params.filename;
      response.download(filePath, request.params.filename, (err) => {
        response
          .status(500)
          .json({
            success: false,
            message: "Internal server error",
            canDelete: false,
          });
Fixed code:         className={`flex items-center ${
          canDelete ? "justify-between" : "justify-end"
        } p-4 md:p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600`}
const fs = require("fs");
      const exportLocation = __dirname + "/../storage/exports/";
      const sanitized = path
        .normalize(request.params.filename)
        .replace(/^(\.\.(\/|\\|$))+/, "");
      const finalDestination = path.join(exportLocation, sanitized);

      if (!fs.existsSync(finalDestination)) {
        response.status(404).json({
          error: 404,
          msg: `File ${request.params.filename} does not exist in exports.`,
        });
        return;
      }

      response.download(finalDestination, request.params.filename, (err) => {
        response.status(500).json({
          success: false,
          message: "Internal server error",
          canDelete: false,
        });