Extract uploaded theme archives safely

Python · Python · advanced · greenfield

Adds the safe extraction step for uploaded theme archives. Resolves the destination once, then validates every entry's normalized target path against it before a single file is written; absolute paths and `..` traversal both reject with the entry name in the error. Extraction itself is a plain `extractall` after the whole archive has passed.

Themes are user-uploaded ZIPs unpacked on the web host; a traversal entry like `../../app/config.py` would let an upload overwrite server code.

Requirements

Files touched

--- app/themes/extract.py
+from pathlib import Path
+from zipfile import ZipFile
+
+
+def safe_extract(archive_path, dest_dir):
+    """Extract ``archive_path`` under ``dest_dir``, rejecting path traversal."""
+    dest = Path(dest_dir).resolve()
+    with ZipFile(archive_path) as archive:
+        for info in archive.infolist():
+            name = info.filename
+            if Path(name).is_absolute() or ".." in Path(name).parts:
+                raise ValueError(f"unsafe path in archive: {name!r}")
+            target = (dest / name).resolve()
+            if not target.is_relative_to(dest):
+                raise ValueError(f"unsafe path in archive: {name!r}")
+        archive.extractall(dest)
+

Review this PR

Python practice