From 0fc83d0990588559cf71f77cc29b4285bebb3114 Mon Sep 17 00:00:00 2001 From: Ganesh Patil <7030871503ganeshpatil@gmail.com> Date: Sat, 14 Feb 2026 13:31:33 +0530 Subject: [PATCH 1/3] security: prevent path traversal in FRI /download endpoint (fixes #262) --- fri/server/main.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/fri/server/main.py b/fri/server/main.py index f94bc663..bf81ce33 100644 --- a/fri/server/main.py +++ b/fri/server/main.py @@ -1,4 +1,4 @@ -from flask import Flask, request, jsonify, send_file, send_from_directory +from flask import Flask, request, jsonify, send_file, send_from_directory, abort from werkzeug.utils import secure_filename import xml.etree.ElementTree as ET import os @@ -330,14 +330,35 @@ def contribute(): def download(dir): download_file = request.args.get('fetch') sub_folder = request.args.get('fetchDir') + + if not download_file: + abort(400, description="Missing file parameter") + + # Normalize the requested file path + safe_path = os.path.normpath(download_file) + + # Prevent absolute paths + if os.path.isabs(safe_path): + abort(400, description="Invalid file path") + + # Prevent directory traversal + if ".." in safe_path.split(os.sep): + abort(400, description="Directory traversal attempt detected") + dirname = secure_filename(dir) + "/" + secure_filename(sub_folder) directory_name = os.path.abspath(os.path.join(concore_path, dirname)) if not os.path.exists(directory_name): resp = jsonify({'message': 'Directory not found'}) resp.status_code = 400 return resp + + # Ensure final resolved path is within the intended directory + full_path = os.path.abspath(os.path.join(directory_name, safe_path)) + if not full_path.startswith(os.path.abspath(directory_name) + os.sep): + abort(403, description="Access denied") + try: - return send_from_directory(directory_name, download_file, as_attachment=True) + return send_from_directory(directory_name, safe_path, as_attachment=True) except: resp = jsonify({'message': 'file not found'}) resp.status_code = 400 From de233e87a62c5fab7c681b0e2d8f97a708f451fd Mon Sep 17 00:00:00 2001 From: Ganesh Patil <7030871503ganeshpatil@gmail.com> Date: Sat, 14 Feb 2026 13:40:43 +0530 Subject: [PATCH 2/3] security: use realpath to prevent symlink-based traversal bypass (fixes #262) --- fri/server/main.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fri/server/main.py b/fri/server/main.py index bf81ce33..82b94474 100644 --- a/fri/server/main.py +++ b/fri/server/main.py @@ -346,15 +346,18 @@ def download(dir): abort(400, description="Directory traversal attempt detected") dirname = secure_filename(dir) + "/" + secure_filename(sub_folder) - directory_name = os.path.abspath(os.path.join(concore_path, dirname)) + concore_real = os.path.realpath(concore_path) + directory_name = os.path.realpath(os.path.join(concore_real, dirname)) + if not directory_name.startswith(concore_real + os.sep): + abort(403, description="Access denied") if not os.path.exists(directory_name): resp = jsonify({'message': 'Directory not found'}) resp.status_code = 400 return resp - # Ensure final resolved path is within the intended directory - full_path = os.path.abspath(os.path.join(directory_name, safe_path)) - if not full_path.startswith(os.path.abspath(directory_name) + os.sep): + # Ensure final resolved path is within the intended directory, resolving symlinks + full_path = os.path.realpath(os.path.join(directory_name, safe_path)) + if not full_path.startswith(directory_name + os.sep): abort(403, description="Access denied") try: From 7ad1c2552b8af1af006c3eee0fc3dd8336e8dc1e Mon Sep 17 00:00:00 2001 From: Ganesh Patil <7030871503ganeshpatil@gmail.com> Date: Sat, 14 Feb 2026 13:42:00 +0530 Subject: [PATCH 3/3] style: replace bare except with except Exception --- fri/server/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fri/server/main.py b/fri/server/main.py index 82b94474..20862861 100644 --- a/fri/server/main.py +++ b/fri/server/main.py @@ -362,7 +362,7 @@ def download(dir): try: return send_from_directory(directory_name, safe_path, as_attachment=True) - except: + except Exception: resp = jsonify({'message': 'file not found'}) resp.status_code = 400 return resp