Skip to content

Commit 7b06e04

Browse files
Refactor examples for security and robustness
Co-authored-by: notapalindrome <notapalindrome@proton.me>
1 parent 24ef8e1 commit 7b06e04

File tree

3 files changed

+118
-30
lines changed

3 files changed

+118
-30
lines changed

python-examples/djvu-pdf-example.py

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import fnmatch
66
import os
77
import subprocess
8+
import shutil
9+
from pathlib import Path
810
# global variables (change to suit your needs)
911
inputfolderpath = '~' # set to import folder path
1012
outputpath = '~' # set to output folder (must exist)
@@ -26,32 +28,67 @@ def find_files(directory, pattern):
2628
for filename in find_files(inputfolderpath, '*.djvu'):
2729
print(f"[*] Processing DJVU to PDF for {filename}...")
2830
i = i + 1
29-
inputfull = inputfolderpath+filename
30-
outputfilename = filename[:-4]+i+'pdf' # make filename unique
31-
outputfilepath = outputpath
32-
p = subprocess.Popen(["djvu2pdf", inputfull], stdout=subprocess.PIPE)
31+
inputfull = os.path.join(inputfolderpath, filename)
32+
# Validate that the file exists and is a regular file
33+
if not os.path.isfile(inputfull):
34+
print(f"[!] Skipping {filename} - not a valid file")
35+
continue
36+
outputfilename = f"{filename[:-5]}_{i}.pdf" # make filename unique
37+
outputfilepath = os.path.join(outputpath, outputfilename)
38+
# Use list for subprocess to avoid shell injection
39+
p = subprocess.Popen(
40+
["djvu2pdf", inputfull],
41+
stdout=subprocess.PIPE,
42+
stderr=subprocess.PIPE
43+
)
3344
output, err = p.communicate()
34-
subprocess.call(["mv", outputfilename, outputfilepath])
45+
# Use shutil.move instead of shell command for better security
46+
if p.returncode == 0 and os.path.exists(outputfilename):
47+
shutil.move(outputfilename, outputfilepath)
3548
print('[-] Processing finished for %s' % filename)
3649
print(f"[--] processed {i} file(s) [--]")
3750
exit('\n\"Sanity is madness put to good uses.\" - George Santayana\n')
3851

3952
elif operationtype == '2':
4053
filename = input('What filename to process? (leave blank for example): ')
41-
if 'djvu' in filename:
54+
if filename and 'djvu' in filename:
55+
# Validate filename to prevent path traversal
56+
safe_path = Path(filename).resolve()
57+
if not safe_path.is_file() or not str(safe_path).endswith('.djvu'):
58+
print('[!] Invalid file or not a .djvu file')
59+
exit('Invalid input')
4260
print('Processing DJVU to PDF...')
43-
p = subprocess.Popen(["djvu2pdf", filename], stdout=subprocess.PIPE)
61+
p = subprocess.Popen(
62+
["djvu2pdf", str(safe_path)],
63+
stdout=subprocess.PIPE,
64+
stderr=subprocess.PIPE
65+
)
4466
output, err = p.communicate()
45-
print('Processing finished')
46-
exit('Completed sucessfully')
67+
if p.returncode == 0:
68+
print('Processing finished')
69+
exit('Completed successfully')
70+
else:
71+
print(f'[!] Error processing file: {err.decode() if err else "Unknown error"}')
72+
exit('Failed')
4773
else:
4874
print('No djvu file to process, running sample')
4975
print('Processing DJVU to PDF...')
50-
p = subprocess.Popen(["djvu2pdf", "assets/example.djvu"],
51-
stdout=subprocess.PIPE)
76+
sample_file = Path("assets/example.djvu")
77+
if not sample_file.is_file():
78+
print('[!] Sample file not found')
79+
exit('Sample file missing')
80+
p = subprocess.Popen(
81+
["djvu2pdf", str(sample_file)],
82+
stdout=subprocess.PIPE,
83+
stderr=subprocess.PIPE
84+
)
5285
output, err = p.communicate()
53-
print('Processing finished')
54-
exit('Completed sucessfully')
86+
if p.returncode == 0:
87+
print('Processing finished')
88+
exit('Completed successfully')
89+
else:
90+
print(f'[!] Error: {err.decode() if err else "Unknown error"}')
91+
exit('Failed')
5592

5693

5794
elif operationtype == '':

python-examples/flask-example.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,57 @@ def hello_world():
3939
@app.route("/upload", methods=["POST"])
4040
def upload_csv() -> str:
4141
"""Upload CSV example."""
42+
if "file" not in request.files:
43+
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "No file provided"}), 400
44+
4245
submitted_file = request.files["file"]
43-
if submitted_file and allowed_filename(submitted_file.filename):
44-
filename = secure_filename(submitted_file.filename)
45-
directory = os.path.join(app.config["UPLOAD_FOLDER"])
46-
if not os.path.exists(directory):
47-
os.mkdir(directory)
48-
basedir = os.path.abspath(os.path.dirname(__file__))
49-
submitted_file.save(
50-
os.path.join(basedir, app.config["UPLOAD_FOLDER"], filename)
51-
)
52-
out = {
53-
"status": HTTPStatus.OK,
54-
"filename": filename,
55-
"message": f"{filename} saved successful.",
56-
}
57-
return jsonify(out)
46+
47+
if not submitted_file or not submitted_file.filename:
48+
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "No file selected"}), 400
49+
50+
if not allowed_filename(submitted_file.filename):
51+
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "File type not allowed"}), 400
52+
53+
filename = secure_filename(submitted_file.filename)
54+
55+
# Additional security check: ensure filename is not empty after sanitization
56+
if not filename:
57+
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "Invalid filename"}), 400
58+
59+
basedir = os.path.abspath(os.path.dirname(__file__))
60+
upload_folder = os.path.abspath(os.path.join(basedir, app.config["UPLOAD_FOLDER"]))
61+
62+
# Create directory with secure permissions if it doesn't exist
63+
if not os.path.exists(upload_folder):
64+
os.makedirs(upload_folder, mode=0o755, exist_ok=True)
65+
66+
# Construct full path and verify it's within the upload directory (prevent path traversal)
67+
file_path = os.path.abspath(os.path.join(upload_folder, filename))
68+
69+
if not file_path.startswith(upload_folder):
70+
return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "Invalid file path"}), 400
71+
72+
# Limit file size (optional but recommended)
73+
# submitted_file.seek(0, os.SEEK_END)
74+
# file_size = submitted_file.tell()
75+
# submitted_file.seek(0)
76+
# if file_size > MAX_FILE_SIZE:
77+
# return jsonify({"status": HTTPStatus.BAD_REQUEST, "message": "File too large"}), 400
78+
79+
submitted_file.save(file_path)
80+
81+
out = {
82+
"status": HTTPStatus.OK,
83+
"filename": filename,
84+
"message": f"{filename} saved successfully.",
85+
}
86+
return jsonify(out)
5887

5988

6089
if __name__ == "__main__":
6190
app.config["UPLOAD_FOLDER"] = "flaskme/"
62-
app.run(port=6969, debug=True)
91+
# Debug mode disabled for security - use environment variable to enable in development
92+
debug_mode = os.environ.get("FLASK_DEBUG", "False").lower() == "true"
93+
app.run(port=6969, debug=debug_mode)
6394

6495
# curl -X POST localhost:6969/upload -F file=@"assets/archive_name.tar.gz" -i

python-examples/pickle_load-example.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
11
# pickle load example
2+
# WARNING: pickle.load() can execute arbitrary code and should only be used
3+
# with trusted data. For untrusted data, use safer alternatives like JSON.
4+
# See: https://docs.python.org/3/library/pickle.html#module-pickle
25
import pickle
36
import random
7+
import os
48

5-
with open('assets/discordia.pkl', 'rb') as f:
9+
# Only load pickle files from trusted sources in trusted locations
10+
pickle_file = 'assets/discordia.pkl'
11+
12+
# Verify the file exists and is in the expected location
13+
if not os.path.exists(pickle_file):
14+
raise FileNotFoundError(f"Pickle file not found: {pickle_file}")
15+
16+
# Resolve to absolute path to prevent path traversal
17+
pickle_file = os.path.abspath(pickle_file)
18+
expected_dir = os.path.abspath('assets')
19+
20+
if not pickle_file.startswith(expected_dir):
21+
raise ValueError("Pickle file must be in the assets directory")
22+
23+
with open(pickle_file, 'rb') as f:
24+
# SECURITY NOTE: This loads a pickle file that must be from a trusted source
25+
# Never load pickle files from untrusted sources (user uploads, internet, etc.)
626
discordia = pickle.load(f)
727

828

0 commit comments

Comments
 (0)