From 668681dd248885389b4b8d44d77daf715c48c148 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Sun, 4 Oct 2026 18:57:34 +0200 Subject: [PATCH] fix(security): private temp dir for docker installer, https for OMDb, md5 non-security flag Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + guided_installer.py | 13 +++++++++---- routes/sources.py | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afa33d3..858f67b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ยท versioned wi - **Voice list rows no longer show the "Source unknown" badge** (it crowded the play buttons). The badge stays in the voice inspector, next to the Source & consent panel. ### Fixed +- **Security hardening (Sonar findings).** The Docker installer downloads `get-docker.sh` into a private `mkdtemp` dir (not a predictable `/tmp` path) and removes it afterwards; the OMDb poster lookup uses HTTPS; the fish.audio cache key marks MD5 as `usedforsecurity=False`. - **Background jobs could be garbage-collected mid-run.** Fire-and-forget `asyncio.create_task` calls (magazine, conversation, tts, stt, library, sources) now go through `core.tasks.spawn`, which keeps a strong reference until the task ends and logs failures by exception class only. - **Voice archetype gallery broke `npm run check:frontend`**: its `say` helper collided with `magazine.js`; renamed to `archSay`. - **Export failed with HTTP 500 when provenance tagging failed** (EPUB/ZIP/article MP3 with audio ffmpeg cannot tag); it now ships the untagged file and logs a warning. diff --git a/guided_installer.py b/guided_installer.py index b790af3..b50734e 100755 --- a/guided_installer.py +++ b/guided_installer.py @@ -6,6 +6,7 @@ import platform import queue import shutil import subprocess +import tempfile import threading import webbrowser from pathlib import Path @@ -186,7 +187,7 @@ class GuidedInstaller(tk.Tk): self.log.see("end") self.after(120, self._drain_log) - def run_threaded(self, title: str, commands: list[list[str]]) -> None: + def run_threaded(self, title: str, commands: list[list[str]], cleanup_dir: str | None = None) -> None: def worker(): self.log_line(f"\n== {title} ==") for cmd in commands: @@ -203,6 +204,8 @@ class GuidedInstaller(tk.Tk): except Exception as exc: self.log_line(f"Failed: {exc}") break + if cleanup_dir: + shutil.rmtree(cleanup_dir, ignore_errors=True) threading.Thread(target=worker, daemon=True).start() def command_ok(self, cmd: list[str]) -> bool: @@ -260,11 +263,13 @@ class GuidedInstaller(tk.Tk): "Run Docker's official Linux convenience installer?\n\nThis may ask for sudo privileges and needs internet access.", ): return + tmp = tempfile.mkdtemp(prefix="docker-install-") # 0700, unpredictable name + script = os.path.join(tmp, "get-docker.sh") self.run_threaded("Install Docker", [ - ["curl", "-fsSL", "https://get.docker.com", "-o", "/tmp/get-docker.sh"], - ["sh", "/tmp/get-docker.sh"], + ["curl", "-fsSL", "https://get.docker.com", "-o", script], + ["sh", script], ["docker", "version"], - ]) + ], cleanup_dir=tmp) def generate_compose(self) -> None: GENERATED_DIR.mkdir(parents=True, exist_ok=True) diff --git a/routes/sources.py b/routes/sources.py index c0ba300..9c5585c 100644 --- a/routes/sources.py +++ b/routes/sources.py @@ -875,7 +875,7 @@ async def fishaudio_voices(request: Request): params["tag"] = tags import hashlib cache_dir = CONFIG_DIR / "fishaudio_cache" - cache_key = hashlib.md5(json.dumps(params, sort_keys=True, default=str).encode()).hexdigest() + cache_key = hashlib.md5(json.dumps(params, sort_keys=True, default=str).encode(), usedforsecurity=False).hexdigest() cache_file = cache_dir / f"{cache_key}.json" try: r = await asyncio.to_thread( @@ -1176,7 +1176,7 @@ async def movie_poster(title: str = ""): clean = re.sub(r"\s*\(.*?\)\s*$", "", title).strip() result = {"poster": "", "year": "", "title": title} try: - url = f"http://www.omdbapi.com/?t={_q(clean)}&type=movie&apikey=trilogy" + url = f"https://www.omdbapi.com/?t={_q(clean)}&type=movie&apikey=trilogy" r = await asyncio.to_thread(lambda: requests.get(url, timeout=8)) if r.ok: d = r.json()