From e1b425ed5bf0fc9c373efc1ba235c7c70e23d465 Mon Sep 17 00:00:00 2001 From: raizasafeel <89463672+raizasafeel@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:50:37 +0530 Subject: [PATCH] fix: prevent path traversal in scorm file --- lms/page_renderers.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/lms/page_renderers.py b/lms/page_renderers.py index a95c9cd7..c4e7e904 100644 --- a/lms/page_renderers.py +++ b/lms/page_renderers.py @@ -16,41 +16,49 @@ class SCORMRenderer(BaseRenderer): def can_render(self): return "scorm/" in self.path + def _is_safe_path(self, path): + scorm_root = os.path.realpath(os.path.join(frappe.local.site_path, "public", "scorm")) + resolved = os.path.realpath(path) + return resolved.startswith(scorm_root + os.sep) or resolved == scorm_root + + def _serve_file(self, path): + f = open(path, "rb") + response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) + response.mimetype = mimetypes.guess_type(path)[0] + return response + def render(self): path = os.path.join(frappe.local.site_path, "public", self.path.lstrip("/")) + if not self._is_safe_path(path): + raise frappe.PermissionError + extension = os.path.splitext(path)[1] if not extension: path = f"{path}.html" # check if path exists and is actually a file and not a folder if os.path.exists(path) and os.path.isfile(path): - f = open(path, "rb") - response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) - response.mimetype = mimetypes.guess_type(path)[0] - return response + return self._serve_file(path) else: path = path.replace(".html", "") if os.path.exists(path) and os.path.isdir(path): index_path = os.path.join(path, "index.html") if os.path.exists(index_path): - f = open(index_path, "rb") - response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) - response.mimetype = mimetypes.guess_type(index_path)[0] - return response + return self._serve_file(index_path) elif not os.path.exists(path): chapter_folder = "/".join(self.path.split("/")[:3]) chapter_folder_path = os.path.realpath(frappe.get_site_path("public", chapter_folder)) file = path.split("/")[-1] correct_file_path = None + if not self._is_safe_path(chapter_folder_path): + raise frappe.PermissionError + for root, _dirs, files in os.walk(chapter_folder_path): if file in files: correct_file_path = os.path.join(root, file) break - if correct_file_path: - f = open(correct_file_path, "rb") - response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) - response.mimetype = mimetypes.guess_type(correct_file_path)[0] - return response + if correct_file_path and self._is_safe_path(correct_file_path): + return self._serve_file(correct_file_path)