fix(security): private temp dir for docker installer, https for OMDb, md5 non-security flag

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-10-04 18:57:34 +02:00
parent 4f30ee5c0d
commit 668681dd24
3 changed files with 12 additions and 6 deletions

View File

@ -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. - **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 ### 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. - **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`. - **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. - **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.

View File

@ -6,6 +6,7 @@ import platform
import queue import queue
import shutil import shutil
import subprocess import subprocess
import tempfile
import threading import threading
import webbrowser import webbrowser
from pathlib import Path from pathlib import Path
@ -186,7 +187,7 @@ class GuidedInstaller(tk.Tk):
self.log.see("end") self.log.see("end")
self.after(120, self._drain_log) 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(): def worker():
self.log_line(f"\n== {title} ==") self.log_line(f"\n== {title} ==")
for cmd in commands: for cmd in commands:
@ -203,6 +204,8 @@ class GuidedInstaller(tk.Tk):
except Exception as exc: except Exception as exc:
self.log_line(f"Failed: {exc}") self.log_line(f"Failed: {exc}")
break break
if cleanup_dir:
shutil.rmtree(cleanup_dir, ignore_errors=True)
threading.Thread(target=worker, daemon=True).start() threading.Thread(target=worker, daemon=True).start()
def command_ok(self, cmd: list[str]) -> bool: 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.", "Run Docker's official Linux convenience installer?\n\nThis may ask for sudo privileges and needs internet access.",
): ):
return return
tmp = tempfile.mkdtemp(prefix="docker-install-") # 0700, unpredictable name
script = os.path.join(tmp, "get-docker.sh")
self.run_threaded("Install Docker", [ self.run_threaded("Install Docker", [
["curl", "-fsSL", "https://get.docker.com", "-o", "/tmp/get-docker.sh"], ["curl", "-fsSL", "https://get.docker.com", "-o", script],
["sh", "/tmp/get-docker.sh"], ["sh", script],
["docker", "version"], ["docker", "version"],
]) ], cleanup_dir=tmp)
def generate_compose(self) -> None: def generate_compose(self) -> None:
GENERATED_DIR.mkdir(parents=True, exist_ok=True) GENERATED_DIR.mkdir(parents=True, exist_ok=True)

View File

@ -875,7 +875,7 @@ async def fishaudio_voices(request: Request):
params["tag"] = tags params["tag"] = tags
import hashlib import hashlib
cache_dir = CONFIG_DIR / "fishaudio_cache" 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" cache_file = cache_dir / f"{cache_key}.json"
try: try:
r = await asyncio.to_thread( r = await asyncio.to_thread(
@ -1176,7 +1176,7 @@ async def movie_poster(title: str = ""):
clean = re.sub(r"\s*\(.*?\)\s*$", "", title).strip() clean = re.sub(r"\s*\(.*?\)\s*$", "", title).strip()
result = {"poster": "", "year": "", "title": title} result = {"poster": "", "year": "", "title": title}
try: 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)) r = await asyncio.to_thread(lambda: requests.get(url, timeout=8))
if r.ok: if r.ok:
d = r.json() d = r.json()