VYPR
High severityNVD Advisory· Published Feb 24, 2026· Updated Feb 27, 2026

MindsDB has Path Traversal in /api/files Leading to Remote Code Execution

CVE-2026-27483

Description

MindsDB is a platform for building artificial intelligence from enterprise data. Prior to version 25.9.1.1, there is a path traversal vulnerability in Mindsdb's /api/files interface, which an authenticated attacker can exploit to achieve remote command execution. The vulnerability exists in the "Upload File" module, which corresponds to the API endpoint /api/files. Since the multipart file upload does not perform security checks on the uploaded file path, an attacker can perform path traversal by using ../ sequences in the filename field. The file write operation occurs before calling clear_filename and save_file, meaning there is no filtering of filenames or file types, allowing arbitrary content to be written to any path on the server. Version 25.9.1.1 patches the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mindsdbPyPI
< 25.9.1.125.9.1.1

Affected products

1

Patches

1
87a44bdb2b97

Better handling of uploaded file names (#11507)

https://github.com/mindsdb/mindsdbMax StepanovSep 4, 2025via ghsa
3 files changed · +87 91
  • mindsdb/api/http/namespaces/file.py+9 3 modified
    @@ -3,6 +3,7 @@
     import tarfile
     import tempfile
     import zipfile
    +from pathlib import Path
     from urllib.parse import urlparse
     
     import multipart
    @@ -60,7 +61,10 @@ def on_field(field):
     
             def on_file(file):
                 nonlocal file_object
    -            data["file"] = file.file_name.decode()
    +            file_name = file.file_name.decode()
    +            data["file"] = file_name
    +            if Path(file_name).name != file_name:
    +                raise ValueError(f"Wrong file name: {file_name}")
                 file_object = file.file_object
     
             temp_dir_path = tempfile.mkdtemp(prefix="mindsdb_file_")
    @@ -72,8 +76,9 @@ def on_file(file):
                     on_file=on_file,
                     config={
                         "UPLOAD_DIR": temp_dir_path.encode(),  # bytes required
    -                    "UPLOAD_KEEP_FILENAME": True,
    +                    "UPLOAD_KEEP_FILENAME": False,
                         "UPLOAD_KEEP_EXTENSIONS": True,
    +                    "UPLOAD_DELETE_TMP": False,
                         "MAX_MEMORY_FILE_SIZE": 0,
                     },
                 )
    @@ -93,6 +98,7 @@ def on_file(file):
                         except (AttributeError, ValueError, OSError):
                             logger.debug("Failed to flush file_object before closing.", exc_info=True)
                         file_object.close()
    +                Path(file_object.name).rename(Path(file_object.name).parent / data["file"])
                     file_object = None
             else:
                 data = request.json
    @@ -101,7 +107,7 @@ def on_file(file):
                 return http_error(
                     400,
                     "File already exists",
    -                f"File with name '{data['file']}' already exists",
    +                f"File with name '{mindsdb_file_name}' already exists",
                 )
     
             if data.get("source_type") == "url":
    
  • mindsdb/api/http/namespaces/handlers.py+77 87 modified
    @@ -19,96 +19,94 @@
     from mindsdb.api.http.namespaces.configs.handlers import ns_conf
     from mindsdb.api.executor.controllers.session_controller import SessionController
     from mindsdb.api.executor.command_executor import ExecuteCommands
    -from mindsdb.utilities.config import Config
     
     
    -@ns_conf.route('/')
    +@ns_conf.route("/")
     class HandlersList(Resource):
    -    @ns_conf.doc('handlers_list')
    -    @api_endpoint_metrics('GET', '/handlers')
    +    @ns_conf.doc("handlers_list")
    +    @api_endpoint_metrics("GET", "/handlers")
         def get(self):
    -        '''List all db handlers'''
    +        """List all db handlers"""
     
    -        if request.args.get('lazy') == '1':
    +        if request.args.get("lazy") == "1":
                 handlers = ca.integration_controller.get_handlers_metadata()
             else:
                 handlers = ca.integration_controller.get_handlers_import_status()
             result = []
             for handler_type, handler_meta in handlers.items():
                 # remove non-integration handlers
    -            if handler_type not in ['utilities', 'dummy_data']:
    -                row = {'name': handler_type}
    +            if handler_type not in ["utilities", "dummy_data"]:
    +                row = {"name": handler_type}
                     row.update(handler_meta)
    -                del row['path']
    +                del row["path"]
                     result.append(row)
             return result
     
     
    -@ns_conf.route('/<handler_name>/icon')
    +@ns_conf.route("/<handler_name>/icon")
     class HandlerIcon(Resource):
    -    @ns_conf.param('handler_name', 'Handler name')
    -    @api_endpoint_metrics('GET', '/handlers/handler/icon')
    +    @ns_conf.param("handler_name", "Handler name")
    +    @api_endpoint_metrics("GET", "/handlers/handler/icon")
         def get(self, handler_name):
             try:
                 handler_meta = ca.integration_controller.get_handlers_metadata().get(handler_name)
                 if handler_meta is None:
    -                return http_error(HTTPStatus.NOT_FOUND, 'Icon not found', f'Icon for {handler_name} not found')
    -            icon_name = handler_meta['icon']['name']
    -            handler_folder = handler_meta['import']['folder']
    -            mindsdb_path = Path(importlib.util.find_spec('mindsdb').origin).parent
    -            icon_path = mindsdb_path.joinpath('integrations/handlers').joinpath(handler_folder).joinpath(icon_name)
    +                return http_error(HTTPStatus.NOT_FOUND, "Icon not found", f"Icon for {handler_name} not found")
    +            icon_name = handler_meta["icon"]["name"]
    +            handler_folder = handler_meta["import"]["folder"]
    +            mindsdb_path = Path(importlib.util.find_spec("mindsdb").origin).parent
    +            icon_path = mindsdb_path.joinpath("integrations/handlers").joinpath(handler_folder).joinpath(icon_name)
                 if icon_path.is_absolute() is False:
                     icon_path = Path(os.getcwd()).joinpath(icon_path)
             except Exception:
    -            return http_error(HTTPStatus.NOT_FOUND, 'Icon not found', f'Icon for {handler_name} not found')
    +            return http_error(HTTPStatus.NOT_FOUND, "Icon not found", f"Icon for {handler_name} not found")
             else:
                 return send_file(icon_path)
     
     
    -@ns_conf.route('/<handler_name>')
    +@ns_conf.route("/<handler_name>")
     class HandlerInfo(Resource):
    -    @ns_conf.param('handler_name', 'Handler name')
    -    @api_endpoint_metrics('GET', '/handlers/handler')
    +    @ns_conf.param("handler_name", "Handler name")
    +    @api_endpoint_metrics("GET", "/handlers/handler")
         def get(self, handler_name):
    -
             handler_meta = ca.integration_controller.get_handler_meta(handler_name)
    -        row = {'name': handler_name}
    +        row = {"name": handler_name}
             row.update(handler_meta)
    -        del row['path']
    -        del row['icon']
    +        del row["path"]
    +        del row["icon"]
             return row
     
     
    -@ns_conf.route('/<handler_name>/install')
    +@ns_conf.route("/<handler_name>/install")
     class InstallDependencies(Resource):
    -    @ns_conf.param('handler_name', 'Handler name')
    -    @api_endpoint_metrics('POST', '/handlers/handler/install')
    +    @ns_conf.param("handler_name", "Handler name")
    +    @api_endpoint_metrics("POST", "/handlers/handler/install")
         def post(self, handler_name):
             handler_meta = ca.integration_controller.get_handler_meta(handler_name)
     
             if handler_meta is None:
    -            return f'Unknown handler: {handler_name}', 400
    +            return f"Unknown handler: {handler_name}", 400
     
    -        if handler_meta.get('import', {}).get('success', False) is True:
    -            return 'Installed', 200
    +        if handler_meta.get("import", {}).get("success", False) is True:
    +            return "Installed", 200
     
    -        dependencies = handler_meta['import']['dependencies']
    +        dependencies = handler_meta["import"]["dependencies"]
             if len(dependencies) == 0:
    -            return 'Installed', 200
    +            return "Installed", 200
     
             result = install_dependencies(dependencies)
     
             # reload it if any result, so we can get new error message
             ca.integration_controller.reload_handler_module(handler_name)
    -        if result.get('success') is True:
    +        if result.get("success") is True:
                 # If warm processes are available in the cache, remove them.
                 # This will force a new process to be created with the installed dependencies.
                 process_cache.remove_processes_for_handler(handler_name)
    -            return '', 200
    +            return "", 200
             return http_error(
                 500,
    -            f'Failed to install dependencies for {handler_meta.get("title", handler_name)}',
    -            result.get('error_message', 'unknown error')
    +            f"Failed to install dependencies for {handler_meta.get('title', handler_name)}",
    +            result.get("error_message", "unknown error"),
             )
     
     
    @@ -122,24 +120,24 @@ def on_field(field):
             params[name] = value
     
         def on_file(file):
    -        params[file.field_name.decode()] = file.file_object
    -        file_names.append(file.field_name.decode())
    +        file_name = file.field_name.decode()
    +        if file_name not in ("code", "modules"):
    +            raise ValueError(f"Wrong field name: {file_name}")
    +        params[file_name] = file.file_object
    +        file_names.append(file_name)
     
    -    temp_dir_path = temp_dir_path = tempfile.mkdtemp(
    -        prefix='mindsdb_byom_file_',
    -        dir=Config().paths['tmp']
    -    )
    +    temp_dir_path = tempfile.mkdtemp(prefix="mindsdb_file_")
     
         parser = multipart.create_form_parser(
             headers=request.headers,
             on_field=on_field,
             on_file=on_file,
             config={
    -            'UPLOAD_DIR': temp_dir_path.encode(),  # bytes required
    -            'UPLOAD_KEEP_FILENAME': True,
    -            'UPLOAD_KEEP_EXTENSIONS': True,
    -            'MAX_MEMORY_FILE_SIZE': 0
    -        }
    +            "UPLOAD_DIR": temp_dir_path.encode(),  # bytes required
    +            "UPLOAD_KEEP_FILENAME": True,
    +            "UPLOAD_KEEP_EXTENSIONS": True,
    +            "MAX_MEMORY_FILE_SIZE": float("inf"),
    +        },
         )
     
         while True:
    @@ -151,32 +149,33 @@ def on_file(file):
         parser.close()
     
         for file_name in file_names:
    +        file_path = os.path.join(temp_dir_path, file_name)
    +        with open(file_path, "wb") as f:
    +            params[file_name].seek(0)
    +            f.write(params[file_name].read())
             params[file_name].close()
    +        params[file_name] = file_path
     
         return params
     
     
    -@ns_conf.route('/byom/<name>')
    -@ns_conf.param('name', "Name of the model")
    +@ns_conf.route("/byom/<name>")
    +@ns_conf.param("name", "Name of the model")
     class BYOMUpload(Resource):
    -    @ns_conf.doc('post_file')
    -    @api_endpoint_metrics('POST', '/handlers/byom/handler')
    +    @ns_conf.doc("post_file")
    +    @api_endpoint_metrics("POST", "/handlers/byom/handler")
         def post(self, name):
             params = prepare_formdata()
     
    -        code_file_path = params['code'].name.decode()
    +        code_file_path = params["code"]
             try:
    -            module_file_path = params['modules'].name.decode()
    +            module_file_path = params["modules"]
             except AttributeError:
    -            module_file_path = Path(code_file_path).parent / 'requirements.txt'
    +            module_file_path = Path(code_file_path).parent / "requirements.txt"
                 module_file_path.touch()
                 module_file_path = str(module_file_path)
     
    -        connection_args = {
    -            'code': code_file_path,
    -            'modules': module_file_path,
    -            'type': params.get('type')
    -        }
    +        connection_args = {"code": code_file_path, "modules": module_file_path, "type": params.get("type")}
     
             session = SessionController()
     
    @@ -185,48 +184,39 @@ def post(self, name):
     
             engine_storage = HandlerStorage(base_ml_handler.integration_id)
     
    -        engine_versions = [
    -            int(x) for x in engine_storage.get_connection_args()['versions'].keys()
    -        ]
    +        engine_versions = [int(x) for x in engine_storage.get_connection_args()["versions"].keys()]
     
    -        return {
    -            'last_engine_version': max(engine_versions),
    -            'engine_versions': engine_versions
    -        }
    +        return {"last_engine_version": max(engine_versions), "engine_versions": engine_versions}
     
    -    @ns_conf.doc('put_file')
    -    @api_endpoint_metrics('PUT', '/handlers/byom/handler')
    +    @ns_conf.doc("put_file")
    +    @api_endpoint_metrics("PUT", "/handlers/byom/handler")
         def put(self, name):
    -        ''' upload new model
    -            params in FormData:
    -                - code
    -                - modules
    -        '''
    +        """upload new model
    +        params in FormData:
    +            - code
    +            - modules
    +        """
     
             params = prepare_formdata()
     
    -        code_file_path = params['code'].name.decode()
    +        code_file_path = params["code"]
             try:
    -            module_file_path = params['modules'].name.decode()
    +            module_file_path = params["modules"]
             except KeyError:
    -            module_file_path = Path(code_file_path).parent / 'requirements.txt'
    +            module_file_path = Path(code_file_path).parent / "requirements.txt"
                 module_file_path.touch()
                 module_file_path = str(module_file_path)
     
             connection_args = {
    -            'code': code_file_path,
    -            'modules': module_file_path,
    -            'mode': params.get('mode'),
    -            'type': params.get('type')
    +            "code": code_file_path,
    +            "modules": module_file_path,
    +            "mode": params.get("mode"),
    +            "type": params.get("type"),
             }
     
    -        ast_query = CreateMLEngine(
    -            name=Identifier(name),
    -            handler='byom',
    -            params=connection_args
    -        )
    +        ast_query = CreateMLEngine(name=Identifier(name), handler="byom", params=connection_args)
             sql_session = SessionController()
             command_executor = ExecuteCommands(sql_session)
             command_executor.execute_command(ast_query)
     
    -        return '', 200
    +        return "", 200
    
  • requirements/requirements.txt+1 1 modified
    @@ -3,7 +3,7 @@ flask == 3.0.3
     werkzeug == 3.0.6
     flask-restx >= 1.3.0, < 2.0.0
     pandas == 2.2.3
    -python-multipart == 0.0.18
    +python-multipart == 0.0.20
     cryptography>=35.0
     psycopg[binary]
     psutil~=7.0
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.