Fix CodeQL path injection vulnerabilities

- projects.py: Add path traversal validation to attachment endpoints
  - Reject filenames containing /, \, or ..
  - Prevents directory traversal attacks via URL parameters

- archives.py: Strengthen timelapse processing input validation
  - Validate audio suffix against whitelist (not just filename check)
  - Reject output filenames with .., empty, or dot-prefixed names
  - Fall back to safe default filename if validation fails
This commit is contained in:
maziggy
2026-02-05 18:09:42 +01:00
parent 46ba5ff417
commit 4d94286e53
2 changed files with 16 additions and 2 deletions
+8 -2
View File
@@ -1590,7 +1590,10 @@ async def process_timelapse(
raise HTTPException(400, "Audio must be .mp3, .wav, .m4a, .aac, or .ogg")
audio_content = await audio.read()
suffix = Path(audio.filename).suffix
# Extract and validate suffix to prevent path injection
suffix = Path(audio.filename).suffix.lower()
if suffix not in (".mp3", ".wav", ".m4a", ".aac", ".ogg"):
raise HTTPException(400, "Invalid audio file extension")
audio_temp_path = Path(tempfile.gettempdir()) / f"audio_{archive_id}{suffix}"
audio_temp_path.write_bytes(audio_content)
@@ -1605,8 +1608,11 @@ async def process_timelapse(
else:
# Save as new file alongside original
filename = output_filename or f"{archive.print_name or 'timelapse'}_edited.mp4"
# Sanitize filename
# Sanitize filename - remove path separators and traversal sequences
filename = "".join(c for c in filename if c.isalnum() or c in "._- ")
# Prevent path traversal
if ".." in filename or not filename or filename.startswith("."):
filename = f"timelapse_{archive_id}_edited"
if not filename.endswith(".mp4"):
filename += ".mp4"
output_path = archive_dir / filename
+8
View File
@@ -907,6 +907,10 @@ async def download_attachment(
_: User | None = RequirePermissionIfAuthEnabled(Permission.PROJECTS_READ),
):
"""Download an attachment from a project."""
# Validate filename to prevent path traversal
if "/" in filename or "\\" in filename or ".." in filename or not filename:
raise HTTPException(status_code=400, detail="Invalid filename")
# Verify project exists
result = await db.execute(select(Project).where(Project.id == project_id))
project = result.scalar_one_or_none()
@@ -939,6 +943,10 @@ async def delete_attachment(
_: User | None = RequirePermissionIfAuthEnabled(Permission.PROJECTS_UPDATE),
):
"""Delete an attachment from a project."""
# Validate filename to prevent path traversal
if "/" in filename or "\\" in filename or ".." in filename or not filename:
raise HTTPException(status_code=400, detail="Invalid filename")
# Verify project exists
result = await db.execute(select(Project).where(Project.id == project_id))
project = result.scalar_one_or_none()