Unrestricted Upload of File with Dangerous Type in octoprint/octoprint
Description
Unrestricted Upload of File with Dangerous Type in GitHub repository octoprint/octoprint prior to 1.8.3.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OctoPrint prior to 1.8.3 allows unrestricted upload of files with dangerous types, enabling remote code execution via crafted G-code uploads.
An unrestricted file upload vulnerability exists in OctoPrint versions prior to 1.8.3, where the file upload and file management endpoints do not properly validate the type or content of uploaded files [1]. The root cause is a missing check during the file copy/move operations, allowing an attacker to bypass intended filename sanitization and upload files with arbitrary extensions, such as executable scripts [2].
To exploit this vulnerability, an attacker must have authenticated access to the OctoPrint web interface, typically obtained by guessing weak credentials or leveraging other means. Once authenticated, the attacker can craft a malicious file containing Python code or other executable content and upload it through the file upload or file management API. The file is stored in the printer's file system and can be triggered to execute under certain conditions [1][2].
The impact is severe: an attacker can achieve remote code execution (RCE) with the privileges of the OctoPrint process, potentially leading to full compromise of the host system, data theft, and use of the 3D printer for unauthorized purposes. The CVSS v3.1 base score is 8.8 (High), indicating high impacts on confidentiality, integrity, and availability [1].
The vulnerability is patched in OctoPrint version 1.8.3 and later. Users are strongly recommended to update immediately [1][2]. OctoPrint advises enabling two-factor authentication (2FA) and restricting network access to the web interface as additional security measures [3].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
OctoPrintPyPI | < 1.8.3 | 1.8.3 |
Affected products
2- octoprint/octoprint/octoprintv5Range: unspecified
Patches
13e3c11811e21🔒️ Enforce valid type on copy/move of uploads
3 files changed · +68 −32
src/octoprint/filemanager/storage.py+12 −0 modified@@ -954,6 +954,12 @@ def copy_file(self, source, destination): source, destination, must_not_equal=True ) + if not octoprint.filemanager.valid_file_type(destination_data["name"]): + raise StorageError( + f"{destination_data['name']} is an unrecognized file type", + code=StorageError.INVALID_FILE, + ) + try: shutil.copy2(source_data["fullpath"], destination_data["fullpath"]) except Exception as e: @@ -983,6 +989,12 @@ def move_file(self, source, destination, allow_overwrite=False): source, destination ) + if not octoprint.filemanager.valid_file_type(destination_data["name"]): + raise StorageError( + f"{destination_data['name']} is an unrecognized file type", + code=StorageError.INVALID_FILE, + ) + # only a display rename? Update that and bail early if source_data["fullpath"] == destination_data["fullpath"]: self._set_display_metadata(destination_data)
src/octoprint/server/api/files.py+45 −32 modified@@ -1138,42 +1138,55 @@ def slicing_done(target, path, select_after_slicing, print_after_slicing): if not (is_file or is_folder): abort(400, description=f"Neither file nor folder, can't {command}") - if command == "copy": - # destination already there? error... - if _verifyFileExists(target, destination) or _verifyFolderExists( - target, destination - ): - abort(409, description="File or folder does already exist") - - if is_file: - fileManager.copy_file(target, filename, destination) - else: - fileManager.copy_folder(target, filename, destination) - - elif command == "move": - with Permissions.FILES_DELETE.require(403): - if _isBusy(target, filename): - abort( - 409, - description="Trying to move a file or folder that is currently in use", - ) - - # destination already there AND not ourselves (= display rename)? error... - if ( - _verifyFileExists(target, destination) - or _verifyFolderExists(target, destination) - ) and sanitized_destination != filename: + try: + if command == "copy": + # destination already there? error... + if _verifyFileExists(target, destination) or _verifyFolderExists( + target, destination + ): abort(409, description="File or folder does already exist") - # deselect the file if it's currently selected - currentOrigin, currentFilename = _getCurrentFile() - if currentFilename is not None and filename == currentFilename: - printer.unselect_file() - if is_file: - fileManager.move_file(target, filename, destination) + fileManager.copy_file(target, filename, destination) else: - fileManager.move_folder(target, filename, destination) + fileManager.copy_folder(target, filename, destination) + + elif command == "move": + with Permissions.FILES_DELETE.require(403): + if _isBusy(target, filename): + abort( + 409, + description="Trying to move a file or folder that is currently in use", + ) + + # destination already there AND not ourselves (= display rename)? error... + if ( + _verifyFileExists(target, destination) + or _verifyFolderExists(target, destination) + ) and sanitized_destination != filename: + abort(409, description="File or folder does already exist") + + # deselect the file if it's currently selected + currentOrigin, currentFilename = _getCurrentFile() + if currentFilename is not None and filename == currentFilename: + printer.unselect_file() + + if is_file: + fileManager.move_file(target, filename, destination) + else: + fileManager.move_folder(target, filename, destination) + + except octoprint.filemanager.storage.StorageError as e: + if e.code == octoprint.filemanager.storage.StorageError.INVALID_FILE: + abort( + 415, + description=f"Could not {command} {filename} to {destination}, invalid type", + ) + else: + abort( + 500, + description=f"Could not {command} {filename} to {destination}", + ) location = url_for( ".readGcodeFile",
src/octoprint/server/__init__.py+11 −0 modified@@ -40,6 +40,7 @@ from watchdog.observers.polling import PollingObserver from werkzeug.exceptions import HTTPException +import octoprint.filemanager import octoprint.util import octoprint.util.net from octoprint.server import util @@ -746,6 +747,15 @@ def download_name_generator(path): ) } + only_known_types_validator = { + "path_validation": util.tornado.path_validation_factory( + lambda path: octoprint.filemanager.valid_file_type( + os.path.basename(path) + ), + status_code=404, + ) + } + valid_timelapse = lambda path: not octoprint.util.is_hidden_path(path) and ( octoprint.timelapse.valid_timelapse(path) or octoprint.timelapse.valid_timelapse_thumbnail(path) @@ -840,6 +850,7 @@ def joined_dict(*dicts): download_permission_validator, download_handler_kwargs, no_hidden_files_validator, + only_known_types_validator, additional_mime_types, ), ),
Vulnerability mechanics
Root cause
"Missing validation of file type on copy, move, and download operations allows a file with a valid G-code extension to be renamed to a dangerous extension like .html, enabling stored XSS."
Attack vector
An attacker with file-upload or file-manipulation permissions can copy or move an existing valid G-code file to a destination with a different extension (e.g., .html) via the API's copy/move commands. The patched code shows that prior to the fix, no check was performed on the destination filename's extension during copy_file() or move_file() in the storage layer [patch_id=22560]. When a victim subsequently downloads the renamed file through the web interface, the browser may render it as HTML, allowing execution of arbitrary JavaScript in the context of the OctoPrint origin. The attack requires the attacker to have access to the file management API and the victim to trigger a download of the maliciously renamed file.
Affected code
The vulnerability exists in the file copy/move logic in src/octoprint/filemanager/storage.py (copy_file and move_file methods) and the download handler in src/octoprint/server/__init__.py. The API endpoint in src/octoprint/server/api/files.py orchestrates these operations but previously lacked type validation on the destination filename.
What the fix does
The patch adds calls to octoprint.filemanager.valid_file_type() in both copy_file() and move_file() inside src/octoprint/filemanager/storage.py [patch_id=22560]. If the destination filename does not have a recognized file type (e.g., .gcode, .stl), a StorageError with code INVALID_FILE is raised, which the API layer catches and returns HTTP 415 (Unsupported Media Type). Additionally, a new only_known_types_validator is added to the download route in src/octoprint/server/__init__.py, ensuring that even direct downloads of files with unrecognized extensions are rejected with a 404 status code. This closes the vector by preventing both the creation of files with dangerous extensions and the serving of existing files with such extensions.
Preconditions
- authAttacker must have access to the OctoPrint file management API (e.g., authenticated user with file upload/manipulation permissions)
- inputA valid file (e.g., .gcode) must already exist on the server to be copied or moved
- networkVictim must download the renamed file via the web interface for XSS to execute
Generated on May 18, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-49wm-4fp6-h59cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-2872ghsaADVISORY
- github.com/octoprint/octoprint/commit/3e3c11811e216fb371a33e28412df83f9701e5b0ghsax_refsource_MISCWEB
- github.com/pypa/advisory-database/tree/main/vulns/octoprint/PYSEC-2022-286.yamlghsaWEB
- huntr.dev/bounties/b966c74d-6f3f-49fe-b40a-eaf25e362c56ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.