Merge pull request 'fix: sanitize filenames to prevent path traversal (#1)' (#13) from fix/issue-1 into main

Reviewed-on: https://git.example.com/jarianc/NASAImageDownloader/pulls/13
This commit is contained in:
Jarian Cottingham 2026-07-05 02:50:07 -05:00
commit e692423da2

View File

@ -44,6 +44,7 @@ import argparse
import datetime
import json
import os
import re
import sys
import time
from pathlib import Path
@ -180,6 +181,16 @@ def download_file(url: str, dest: Path) -> bool:
raise RuntimeError(f"Download error for {url}: {e}") from e
def sanitize_filename(name: str) -> str:
"""Sanitize a string for use as a filename, preventing path traversal."""
# Strip path separators and parent directory references
sanitized = re.sub(r'[\/\\:\*\?"<>|]', "_", name)
sanitized = re.sub(r"^\.{1,2}($|_)", "item_", sanitized)
# Collapse multiple underscores
sanitized = re.sub(r"_+", "_", sanitized)
return sanitized.strip(".") or "item"
def save_metadata(item: Dict, dest: Path) -> None:
"""Persist the full API item as formatted JSON."""
with dest.open("w", encoding="utf-8") as f:
@ -224,12 +235,13 @@ def main() -> None:
for idx, item in enumerate(tqdm(items, desc="Downloading"), start=1):
data = item.get("data", [{}])[0]
nasa_id = data.get("nasa_id") or data.get("title", f"item_{idx}")
raw_nasa_id = data.get("nasa_id") or data.get("title", f"item_{idx}")
nasa_id = sanitize_filename(str(raw_nasa_id))
# ------------------------------------------------------------------
# Asset lookup: fetch highresolution image URL
# ------------------------------------------------------------------
asset_url = f"{API_ASSET_URL}/{nasa_id}"
asset_url = f"{API_ASSET_URL}/{raw_nasa_id}"
try:
asset_resp = requests.get(asset_url, timeout=20)
asset_resp.raise_for_status()
@ -243,7 +255,7 @@ def main() -> None:
print(f"[{idx}] Failed asset lookup for {nasa_id}: {e}", file=sys.stderr)
continue
filename = Path(asset_href).name
filename = sanitize_filename(Path(asset_href).name)
image_path = IMG_DIR / filename
meta_path = META_DIR / f"index-{nasa_id}.json"