Durchlässige CORS-Richtlinie in LoopX serve-status legt Zustand der Control-Plane und private Markdown-Dateien offen
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:
// 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-registrybetrifft 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, unddo_OPTIONSgibtAccess-Control-Allow-Headers: Content-Typezurück, sodass ein Cross-Origin-JSON-POST den Preflight besteht. Die Antwort legt diebefore/after-Control-Plane-Konfiguration für ein Ziel offen. (Es werden keine Schreibvorgänge ausgeführt – verifiziert durchwritten=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:
- Feste Standardports.
8765(Projekt) und8766(global) sind fest vorgegebene Standards, sodass eine Angreiferseite einfach beide ausprobieren kann – ein Erkennungsschritt ist nicht erforderlich. - Es ist als dauerhafter Dienst dokumentiert.
docs/guides/getting-started.mddokumentiertloopx serve-status, undscripts/macos-dashboard-launchagent.shinstalliert einen LaunchAgent, derserve-status --global-registry --port 8766nach 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():
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-apiund--enable-control-plane-write-apiaktiviert sind, gebenPOST /reward/appendundPOST /control-plane/configure-goal/applyfür ein fremdesOrigin403 „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.mdaußerhalb der Ziel-Roots → 400 („outside the goal repo/state/runtime roots“).path_is_under()arbeitet korrekt. - Erweiterungs-Allowlist greift.
secrets.envinnerhalb des Repositories → 400. Es können nur.md/.markdowngelesen werden. - Dry-Run schreibt nicht.
configure_goal_with_global_sync(execute=False)kehrt vor jeglichem Schreibvorgang zurück; beobachtet wurdewritten=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:
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 aufPOST /control-plane/configure-goal/dry-runundPOST /reward/dry-runanwenden, damit die Konfigurationsprojektion nicht über Cross-Origin lesbar ist. - Eine Überprüfung des
Host-Headers hinzufügen (Hostablehnen, falls nicht Loopback), um DNS-Rebinding für Clients zu verhindern, die keinOriginsenden.
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.
| Plattform | Paket | Anfällige Versionen | Behoben in |
|---|---|---|---|
| Python (PyPI) | loopx | >= 0.0.1, < 0.4.5 | 0.4.5 |
Verweise
- https://github.com/huangruiteng/loopx/security/advisories/GHSA-vx2m-gpq4-8j5q
- https://github.com/loopx-project/loopx/security/advisories/GHSA-vx2m-gpq4-8j5q
- https://nvd.nist.gov/vuln/detail/CVE-2026-108372
- https://github.com/loopx-project/loopx/pull/3137
- https://github.com/loopx-project/loopx/commit/838720fba42bd22f71b5bb763ec406f7bdf4c940
- https://github.com/huangruiteng/loopx
- https://github.com/loopx-project/loopx/releases/tag/v0.4.5
- 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.