← Zurück zu allen Beiträgen
Veröffentlicht am · von Renaud Deraison

Das Token des Agenten gehörte dem Owner

Am 23. Oktober hat LiteLLM die Ursache seines vergifteten Releases 1.107.0 veröffentlicht. Die erste gestohlene Zugangsberechtigung stammte nicht aus einem Log. Sie stammte von LiteLLMs eigener Agentenplattform, deren API das GitHub-Token zurückgab, mit dem die Cloud-Agenten Pull Requests öffneten: das Token eines Organisations-Owners, nicht eingeschränkt. Der Schlüssel zu dieser API lag viereinhalb Monate lang in einer öffentlichen Konfigurationsdatei. In Bromure Agentic Coding hält ein Agent, der Pull Requests öffnet, einen Platzhalter, das echte Token bleibt verschlüsselt auf dem Mac, und keine API gibt es heraus.

LiteLLMs Agenten brauchten ein GitHub-Token, um Pull Requests zu öffnen. Sie bekamen eines, das einem Owner der Organisation gehörte. Ein einziger API-Aufruf mit einem geleakten Schlüssel gab es zurück.

Am 23. Oktober hat LiteLLM die Angriffskette hinter seinem Release 1.107.0 veröffentlicht, der Version, die am 21. Oktober mit einem Credential-Stealer an Bord ausgeliefert wurde. Der Beitrag bezeichnet sich als „die maßgebliche Quelle der Wahrheit“ und korrigiert LiteLLMs erste Theorie. Die gestohlenen GitHub-Tokens stammten nicht aus protokolliertem Modellverkehr. Das erste stammte aus einem experimentellen Dienst, den LiteLLM für seine eigenen Coding-Agenten betrieb.

Über das Release selbst haben wir in Das Release war zwei Stunden alt berichtet und sind dort der ersten Theorie gefolgt. Der Abschlussbericht beginnt an einer anderen Stelle. Die erste Zugangsberechtigung, die der Angreifer an sich nahm, gehörte den Agenten.

Eine Agentenplattform mit dem Token eines Owners

LiteLLM Agent Platform war nach LiteLLMs Worten „ein experimentelles Projekt für selbst gehostete Cloud-Agenten, die PRs in GitHub-Repositories einreichen“. Um Pull Requests zu öffnen, hielt die Plattform ein GitHub-Token. Dieses Token „war keinem Service-Account zugeordnet und nicht eingeschränkt“. Es war das OAuth-Token der GitHub CLI eines der beiden Owner der Organisation.

Die Zeitleiste des Berichts, in UTC:

  • 27. Mai, 18:53. Eine Datei opencode.json wird in das öffentliche Repository LiteLLM-Labs/lite-harness committet. Sie ist die Konfiguration eines Agenten-Harness und enthält den Master-Key zu einem Deployment der Agent Platform. Das Team bemerkt es, überschreibt den Commit per Force-Push und markiert die offengelegten Schlüssel zum Widerruf. „Für den Master-Key fand der Widerruf nicht statt.“
  • 10. Oktober, 21:27. Von 69.48.229.143 aus ruft der Angreifer mit diesem Schlüssel die API der Plattform auf. GET /api/v1/managed_agents/agents?limit=100 liefert die Konfiguration jedes verwalteten Agenten und damit das Token des Owners.
  • 10. Oktober, 22:11. Mit dem Token des Owners pusht der Angreifer einen Wegwerf-Branch, ci-tmp-a351cc, nach BerriAI/litellm. Dessen Workflow schickt sechs Repository-Secrets an webhook.site. Eines davon, GH_TOKEN, ist das GitHub-CLI-Token eines zweiten Maintainers. Vierundzwanzig Sekunden später schickt ein zweiter Branch in BerriAI/litellm-auto das Token eines dritten Maintainers an denselben Endpunkt. Beide Läufe werden innerhalb von drei Minuten gelöscht.
  • 12. Oktober. Weitere Workflows in privaten Repositories schicken jedes Repository-Secret ab, das sie erreichen können.
  • 21. Oktober, 10:08. Ein Push in das Release-Repository mit dem Token des zweiten Maintainers wird vom Branch-Schutz abgewiesen. Fünf Sekunden später schaltet das Token des Owners den Schutz ab und force-pusht die Änderung, die den Release-Workflow auf den Doppelgänger Berri-AI/litellm umleitet.
  • 21. Oktober, 11:11. Das Token des zweiten Maintainers genehmigt das Produktions-Gate. Eine Regel, die die Freigabe durch ein an ein Gerät in LiteLLMs Büro gebundenes Konto verlangte, war in jener Woche gelockert worden, sodass zwei beliebige Ingenieure veröffentlichen konnten.
  • 21. Oktober, 11:39 bis 11:46. Sieben Wheels landen auf PyPI, danach die Images auf Docker Hub und GHCR.
  • 21. Oktober, 11:53. Das Token des dritten Maintainers löscht die Aufzeichnungen des Release-Laufs. Um 12:24 force-pusht das Token des Owners main wieder sauber.

LiteLLMs eigene Zusammenfassung: „Dieser Vorfall hat die Risiken eindrücklich veranschaulicht, die entstehen, wenn ein möglicherweise offengelegter Schlüssel nicht rotiert wird.“ Das Unternehmen widerruft nun jedes Token, das mit einem internen System oder einer Anbieterplattform verbunden ist.

Jede Agentenflotte hält so ein Token

LiteLLM hat ein fähiges Sicherheitsteam, und sein Aufbau war ein üblicher. Ein Agent, der Pull Requests öffnet, braucht ein Token, das pushen darf. Das am schnellsten greifbare Token ist der eigene gh-Login, und genau den hatten LiteLLMs Agenten bekommen.

Dann muss das Token irgendwo liegen, wo die Agenten es lesen können: in einer Umgebungsvariable, einer Konfigurationsdatei, einer Datenbankzeile hinter einer API, die die Agenten konfiguriert. Wer diesen Ort lesen kann, hat das Token. Hier reichte ein API-Schlüssel, der einmal geleakt und nie rotiert worden war.

Tokens der GitHub CLI machen es schlimmer. LiteLLM merkt an, dass sie „nach der 2FA ausgestellt werden und nicht den ‚Super-User‘-2FA-Reauth-Abläufen von GitHub für sensible Organisations- oder Repository-Aktionen unterliegen“. Mit einem davon konnte der Angreifer den Branch-Schutz abschalten, force-pushen, ein Release genehmigen und die Spuren löschen.

Suchen Sie in Ihrem eigenen Aufbau nach denselben Teilen: ein GH_TOKEN in der Umgebung des Agenten, eine MCP-Konfiguration mit einem Bearer-Token darin, eine Harness-Konfiguration, die eines Tages jemand committen wird. LiteLLMs opencode.json enthielt mehr als den Plattformschlüssel. Darin standen auch Bearer-Tokens für die MCP-Server von GitHub, Linear und Slack sowie der Schlüssel für das Modell-Gateway, alles im Klartext.

Dieselbe Flotte auf einer Bromure-Maschine

Bromure Agentic Coding betreibt Claude Code, Codex, Grok, Kimi und omp in Linux-VMs auf Ihrem Mac. Seine Automationen tun, was LiteLLMs Agentenplattform tat: Ein Agent startet nach Zeitplan oder bei einem neuen GitHub-Issue, arbeitet in einem frischen Worktree und pusht einen Branch oder öffnet einen Pull Request. Spielen Sie LiteLLMs Kette gegen eine Maschine durch, auf der diese Automationen laufen.

Der Agent hält einen Platzhalter

Fügen Sie das GitHub-Token der Maschine unter Credentials hinzu. Beim Start schreibt Bromure eine Fälschung in die VM: einen ghp_-Wert der richtigen Länge, in GH_TOKEN, in die Konfiguration von gh und in ~/.git-credentials. gh pr create und git push des Agenten funktionieren wie gewohnt. Wenn eine Anfrage die VM in Richtung github.com verlässt, tauscht der Proxy auf dem Mac die Fälschung gegen das echte Token, und nur für diesen Host.

Das echte Token liegt verschlüsselt auf dem Mac, unter einem Schlüssel, der im macOS-Schlüsselbund verwahrt wird. Es wird nie in die VM geschrieben, weder in eine Datei noch in eine Variable. Das Handbuch sagt über den Kanal zwischen Host und VM: „Es gibt keine Anfrage, mit der die VM ein echtes Token zurückverlangen kann.“

Der Schritt, der LiteLLMs Organisation öffnete, hat also nichts zurückzugeben. Lesen Sie die Umgebung des Agenten aus, seine Konfiguration, listen Sie jede Automation über die API auf: Der GitHub-Wert ist überall der Platzhalter. Fügen Sie ihn in Ihr eigenes gh ein, und GitHub lehnt ihn ab, denn nur der Proxy dieser Maschine weiß, wofür er steht.

Auch die committete Konfiguration hält Platzhalter

Das Leak begann mit einer Harness-Konfiguration in einem öffentlichen Repository. Auf einer Bromure-Maschine werden die Konfigurationsdateien, die ein Agent liest, mit Platzhaltern geschrieben. Das Bearer-Token eines MCP-Servers, gesetzt im Bereich MCP der Maschine, erreicht den Agenten als brm-mcp_…-Wert, der auf der Leitung für Anfragen an den Host dieses Servers ersetzt wird. Ein Modellschlüssel aus Preferences › Models gelangt nie in die VM; der Agent sieht eine Fälschung im Format des Anbieters. Jeder andere API-Schlüssel kommt unter Other API keys mit einem Host-Filter, und die VM erhält einen brm_…-Wert in der Variable, die Sie benennen.

Committet ein Agent seine Konfiguration aus Versehen, oder kopiert ein Entwickler sie in ein Demo-Repository, enthält die Datei Platzhalter. Nichts darin funktioniert außerhalb dieses Macs, also entscheiden weder ein Force-Push noch eine Widerrufsliste, die niemand abarbeitet, darüber, ob Sie kompromittiert werden.

Es gibt keine Plattform-API zum Aufrufen

LiteLLMs Angreifer erreichte die API der Plattform aus dem Internet. Bromures Automationen laufen auf Ihrem Mac. In den Worten des Handbuchs: „Es gibt keinen Cloud-Dienst und keine eingehende Netzwerkangriffsfläche.“ GitHub- und Linear-Trigger werden alle drei Minuten vom Mac aus mit dem gespeicherten Token der Maschine abgefragt, das auf dem Host bleibt. Es gibt keinen Webhook-Endpunkt, keinen Master-Key und keine Route /managed_agents, die ein geleakter Schlüssel öffnen könnte.

Ein gestohlener Platzhalter löst Alarm aus

Die Workflows vom 10. Oktober schickten Secrets an webhook.site. Stellen Sie sich denselben Zug gegen eine Bromure-Maschine vor: Eine vergiftete Abhängigkeit oder eine per Prompt Injection eingeschleuste Anweisung liest GH_TOKEN und schickt es an webhook.site.

Der Proxy durchsucht jede ausgehende Anfrage, Header und Body, nach Platzhaltern, die an einen anderen Host gehen als den, für den sie erzeugt wurden. Ein ghp_-Platzhalter auf dem Weg zu webhook.site wird mit HTTP 451 blockiert, bevor ein einziges Byte hinausgeht. Die VM pausiert, und eine Warnung auf dem Mac meldet This environment may have been compromised, mit der Token-Vorschau, dem Namen der Zugangsberechtigung, dem Host, zu dem sie gehört, und dem Host, an den sie geschickt wurde. Sie können die Maschine herunterfahren oder ihre Festplatte und ihren Home-Ordner für die Untersuchung sichern.

Steht Unmatched traffic in der Sandbox-Richtlinie der Maschine auf Deny, ist webhook.site gar nicht erst erreichbar, und der Versuch erscheint als rote Zeile in der Security Timeline.

Ein Push wartet auf Sie

Neue Bromure-Maschinen starten mit GitHubs Schreibrichtlinie auf Prompt before write. Lesezugriffe gehen durch. Ein git push oder jeder andere Schreibzugriff auf github.com hält an einem Dialog auf Ihrem Mac an:

Allow write on "GitHub" from workspace "release-bot"?
POST /repos/<owner>/<repo>/git/refs

Die Züge des Angreifers waren allesamt Schreibzugriffe: die ci-tmp--Branches, die Änderung am Release-Workflow, der Force-Push auf main, das Löschen der Laufaufzeichnungen. Von einer Bromure-Maschine aus hätte jeder davon an diesem Dialog angehalten. Allow once erlaubt nichts über diesen einen Schreibzugriff hinaus, der nächste Push fragt also erneut. Ein Force-Push ist auf der Leitung immer noch ein Push, und Prompt before write kontrolliert ihn.

Eine unbeaufsichtigte Automation kann nicht auf einen Klick warten, also geben Sie ihr das engste Token, das ihre Aufgabe erfüllt, unter einer Richtlinie, die für diese Maschine gilt. Das Token, das Sie einer release-bot-Maschine geben, kann ein feingranulares für ein einziges Repository sein. Ihr eigener gh-Login mit Owner-Rechten auf alles hat auf dieser Maschine nichts zu suchen.

Die Aufzeichnung liegt auf Ihrem Mac

LiteLLM glich Token-Hashes im Audit-Log von GitHub ab, um herauszufinden, welches gestohlene Token was getan hatte. Auf einer Bromure-Maschine erscheint jeder Tausch einer Zugangsberechtigung auf der Leitung als Zeile Credential brokering in der Security Timeline, mit dem maskierten Token und dem Host. Jeder Schreibzugriff, den Sie ablehnen, ist eine rote Zeile Guardrails mit Methode und Pfad. Auf einem bei bromure.io registrierten Mac erreichen dieselben Ereignisse Ihre Organisation, sodass ein Token, das von einer unerwarteten Maschine aus benutzt wird, auffällt, ohne dass jemand Audit-Logs durchsuchen muss.

Von einer Konfigurationsdatei zum signierten ReleaseLiteLLM, 27. Mai bis 21. Oktober1 · Schlüssel in Configopencode.json in öffentlichemRepo, nie widerrufen2 · API gibt Token ausAgenten-Config enthält dasgh-Token eines Org-Owners3 · Secrets abgeflossenci-tmp-Branches schickenzwei Tokens an webhook.site4 · Pushes und ReleaseSchutz aus, Force-Push,Gate genehmigt, Logs gelöschtAuf einer Bromure-MaschinePlatzhalter in ConfigsMCP-, Modell- und API-Keysin der VM sind falsch. EinCommit leakt nichts.Kein Token zu holenkeine eingehende API; derAgent hält nur einen ghp_-Platzhalter, sonst wertlos.Blockiert, VM pausiertein Platzhalter fürwebhook.site bekommt 451und einen Alarm.Prompt before writejeder Push, Force-Pushund API-Write wartet aufeinen Dialog am Mac.
LiteLLMs Angriffskette, und wo eine Bromure-Maschine mit denselben Agenten jeden Schritt stoppt. Jede Prüfung läuft auf dem Mac, außerhalb der VM.

Agenten bekommen Platzhalter

GH_TOKEN, die Konfiguration von gh und ~/.git-credentials halten eine ghp_-Fälschung. Das echte Token wird auf dem Mac eingesetzt, nur für die Hosts von GitHub.

Keine API gibt Tokens heraus

Automationen laufen auf Ihrem Mac ohne eingehende Angriffsfläche. Nichts, was die VM aufrufen kann, liefert eine echte Zugangsberechtigung.

Leaks pausieren die Maschine

Ein Platzhalter, der irgendwohin außer zu seinem eigenen Host geht, wird mit einem 451 blockiert, und die VM pausiert, bis Sie entscheiden.

Schreibzugriffe fragen zuerst

Neue Maschinen fragen vor jedem Push und jedem Schreibzugriff auf GitHub nach, Force-Pushes eingeschlossen, mit der genauen Anfrage auf dem Bildschirm.

Geben Sie dem Agenten das Token gar nicht erst

LiteLLM widerruft jedes Token und will nächste Woche seine Prozessänderungen veröffentlichen. Rotation, enge Berechtigungen und Service-Accounts helfen alle. Jede dieser Maßnahmen lässt aber ein funktionierendes Token irgendwo liegen, wo ein Agent es lesen kann, und genau danach suchte der Angreifer am 10. Oktober.

Auf einer Bromure-Maschine erledigt der Agent seine Arbeit mit einem Token, das nur über seinen eigenen Proxy funktioniert, in Richtung des einen Hosts, für den es erzeugt wurde. Was der Agent lesen kann, kann auch ein Angreifer lesen, der den Agenten erreicht, und auf einer Bromure-Maschine gehört das echte Token nie dazu.

Wie dieselben Platzhalter gegen einen Stealer bestehen, der es auf die Dateien von Coding-Agenten abgesehen hat, lesen Sie in Der Stealer packte die Platzhalter ein. Zu den Kopien, die ein Agent in seinen eigenen Transkripten hinterlässt, lesen Sie Das Transkript behielt das Token.