From 4d48fd40c083cf012c2e68785fda40919b1f97fa Mon Sep 17 00:00:00 2001 From: sascha Date: Thu, 17 Sep 2026 21:54:49 +0200 Subject: [PATCH] Fix media/verify apostrophe false-positive in app.py --- app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index 7549309..44508ca 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,7 @@ """Homelab Butler v2.1 – Unified API proxy for Pfannkuchen homelab. Reads service config from butler.yaml, credentials from Vaultwarden cache with flat-file fallback.""" -import os, json, asyncio, logging, time, base64, re, subprocess, ipaddress, secrets, sqlite3, math, hashlib +import os, json, asyncio, logging, time, base64, re, subprocess, ipaddress, secrets, sqlite3, math, hashlib, shlex from datetime import datetime, timezone import httpx, yaml from typing import Literal @@ -12,7 +12,7 @@ from contextlib import asynccontextmanager from contextvars import ContextVar log = logging.getLogger("butler") -VERSION = "2.4.7" +VERSION = "2.4.8" API_DIR = os.environ.get("API_KEY_DIR", "/data/api") VAULT_CACHE_DIR = os.environ.get("VAULT_CACHE_DIR", "/data/vault-cache") @@ -1577,7 +1577,7 @@ async def media_verify(payload: MediaVerifyRequest, _=Depends(_verify)): raise HTTPException(400, f"No ffprobe container configured for host {payload.host}") if not re.fullmatch(r"/data/[^\x00]+\.(mkv|mp4|avi|m4v|ts)", payload.path): raise HTTPException(400, "Path must be an absolute /data media file") - if ".." in payload.path or "'" in payload.path: + if ".." in payload.path or "\n" in payload.path or "\x00" in payload.path: raise HTTPException(400, "Path contains unsafe characters") container, _default_user = MEDIA_VERIFY_PROBES[payload.host] @@ -1590,7 +1590,7 @@ async def media_verify(payload: MediaVerifyRequest, _=Depends(_verify)): probe_cmd = ( f"docker exec {container} ffprobe -v error " - f"-show_entries format=duration -of default=noprint_wrappers=1:nokey=1 '{payload.path}'" + f"-show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(payload.path)}" ) rc, out, err = await asyncio.to_thread( _ssh, f'{target["user"]}@{target["ip"]}', f"sudo -n {probe_cmd} || {probe_cmd}", 30