"""Geheimnis-Ablage auf der Box (v3-Umbau P1). WARUM ES DAS GIBT: Bis zum 28.08.2026 lagen das Box-Sudo-Passwort und der HuggingFace-Token im `localStorage` des Browsers und reisten bei jedem mutierenden Request mit (Header `X-Sudo-Password` **und** im JSON-Rumpf). Da der Dienst-Nutzer laut `/etc/sudoers` mit `NOPASSWD: ALL` läuft, wäre ein einziger XSS in der SPA gleichbedeutend mit Root auf der Box gewesen. Das Sudo-Passwort ist ersatzlos entfallen — auf der Box gemessen: `sudo -n true` läuft durch, es wurde also nie gebraucht. Bleibt der HF-Token; der liegt jetzt hier: eine Datei neben den anderen `mc2-*.json` unter MODELS_DIR, Rechte 0600, und er wird **nie** an den Browser zurückgegeben. Die Oberfläche erfährt nur, OB einer gesetzt ist. Absichtlich kein Verschlüsseln: Der Schlüssel müsste auf derselben Maschine liegen und wäre damit Theater. Der Gewinn ist, dass das Geheimnis den Browser gar nicht erst erreicht — nicht, dass die Datei unlesbar wäre. """ import json import logging import os from pathlib import Path from config import MODELS_DIR log = logging.getLogger(__name__) PFAD = Path(os.environ.get("MC_GEHEIMNISSE_PFAD", str(MODELS_DIR / "mc2-geheimnisse.json"))) # Was hier abgelegt werden darf. Neue Schlüssel bewusst eintragen — so kann ein # fehlgeleiteter Request keine beliebigen Felder in die Datei schreiben. ERLAUBT = frozenset({"hf_token"}) def _lesen() -> dict[str, str]: """Ganze Ablage. Fehlt die Datei (frische Box, Windows-Entwicklungsrechner ohne /srv/models), ist das kein Fehler, sondern schlicht 'nichts gesetzt'.""" try: daten = json.loads(PFAD.read_text(encoding="utf-8")) return {k: v for k, v in daten.items() if k in ERLAUBT and isinstance(v, str)} except (OSError, ValueError): return {} def _schreiben(daten: dict[str, str]) -> bool: """Atomar über eine Nachbardatei, damit ein Absturz mittendrin keine halbe Datei hinterlässt. Rechte 0600 werden VOR dem Umbenennen gesetzt — sonst gäbe es ein Zeitfenster, in dem das Geheimnis world-readable auf der Platte liegt.""" try: PFAD.parent.mkdir(parents=True, exist_ok=True) tmp = PFAD.with_suffix(".json.tmp") tmp.write_text(json.dumps(daten, indent=2, ensure_ascii=False), encoding="utf-8") try: os.chmod(tmp, 0o600) except OSError: pass # Windows kennt keine Unix-Rechte — lokal harmlos, auf der Box greift es tmp.replace(PFAD) return True except OSError as exc: log.warning("Geheimnis-Ablage nicht schreibbar (%s): %s", PFAD, exc) return False def hf_token() -> str | None: """Der HF-Token für Modell-Downloads. Reihenfolge: Prozess-Env schlägt Datei — so kann die systemd-Unit ihn setzen, ohne dass jemand die Oberfläche anfassen muss.""" return os.environ.get("HF_TOKEN") or _lesen().get("hf_token") or None def setzen(schluessel: str, wert: str | None) -> bool: """Setzt oder löscht (wert=None oder leer) ein Geheimnis.""" if schluessel not in ERLAUBT: return False daten = _lesen() if wert: daten[schluessel] = wert else: daten.pop(schluessel, None) return _schreiben(daten) def status() -> dict[str, bool]: """Was die Oberfläche erfahren darf: nur, OB etwas gesetzt ist — nie der Wert. `aus_env` sagt dem Nutzer, warum ein Löschen in der Oberfläche wirkungslos bliebe.""" daten = _lesen() return { "hf_token_gesetzt": bool(daten.get("hf_token") or os.environ.get("HF_TOKEN")), "hf_token_aus_env": bool(os.environ.get("HF_TOKEN")), "schreibbar": _schreibbar(), } def _schreibbar() -> bool: """Ehrlich melden, wenn die Ablage nicht beschreibbar ist (z. B. lokal auf Windows ohne /srv/models) — sonst speichert die Oberfläche scheinbar erfolgreich ins Leere.""" try: PFAD.parent.mkdir(parents=True, exist_ok=True) return os.access(PFAD.parent, os.W_OK) except OSError: return False