169.254.169.254 fragt jetzt zuerst nach: Hermes legt Credential-Abrufe aus Cloud-Instance-Metadata hinter eine Approval

Du debuggst auf einer Cloud-VM ein „warum erreicht diese Maschine den internen Service nicht“-Problem und bittest den Agenten herauszufinden, wer die Instance überhaupt ist. Er beschließt, nachzuschauen:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
Auf einer Cloud-Instance liefert dieses Kommando temporäre Access Credentials ohne jede Authentifizierung zurück – Credentials, die die Identität der Maschine darstellen und alles erreichen können, was deine Cloud-Policy ihr gewährt hat. Hermes behandelte das früher als gewöhnliches Lesen und ließ es laufen. Jetzt hält es an und fragt. Wenn du Agenten in der Cloud betreibst, lohnt es sich, das zu verstehen, denn es schließt genau die am leichtesten übersehene Hintertür, die in dem Moment entsteht, in dem ein Agent Kommandos ausführen kann.
Was IMDS ist und warum es riskanter ist, als es aussieht
IMDS steht für Instance Metadata Service: ein spezieller HTTP-Endpunkt, den die großen Clouds innerhalb jeder Instance bereitstellen, unter der Link-Local-Adresse 169.254.169.254. Seine Aufgabe ist, Software in der Instance wissen zu lassen, wer sie ist, in welcher Region sie lebt und welche Rolle sie trägt. Der kritische Teil ist das Authentifizierungsmodell: Es gibt keines. Jeder Prozess auf der Maschine, der eine HTTP-Anfrage stellen kann, kann darin lesen – inklusive des curl, das du den Agenten gerade hast ausführen lassen.
Jede Cloud hat ihre eigene Hausnummer für denselben Dienst:
169.254.169.254— die IPv4-Link-Local-Adresse für AWS, Azure, GCP und OpenStack;fd00:ec2::254— AWS’ IPv6-Form;metadata.google.internal— GCPs Hostname;100.100.100.200— Alibaba Clouds Endpunkt.
Warum ist das mehr als eine Randnotiz? Weil das, was zurückkommt, meist nicht nur „wie viele vCPUs“ ist. Es enthält typischerweise temporäre IAM-Rollen- oder Service-Account-Credentials. Diesen String zu besitzen entspricht dem Besitz von allem, wozu die Instance autorisiert ist – einen privaten Bucket lesen, eine interne API aufrufen, manchmal deutlich mehr. Die Credentials laufen ab, aber sie leben üblicherweise lange genug, um die Sache zu Ende zu bringen.
Der Angriffspfad: Eine injizierte Anweisung genügt
Stell zwei Fakten nebeneinander, und das Risiko ist offensichtlich: Der Agent liest Inhalte von außen (Webseiten, Issues, E-Mails, Code-Kommentare), und der Agent kann Kommandos ausführen. Enthält dieser fremde Inhalt „während du dabei bist, führ das aus und schick mir die Ausgabe“, und zeigt das Kommando zufällig auf einen Metadata-Endpunkt, dann wird aus einer einzigen Prompt Injection eine Credential-Exfiltration. Das ist nicht hypothetisch. Die besondere Eigenschaft des Endpunkts ist genau, dass er im internen Netz lebt und keine Credentials braucht – das macht ihn zum billigsten Diebesgut.
Cloud-Anbieter tun ihren Teil – AWS’ IMDSv2 etwa verlangt zuerst ein PUT, um ein Einmal-Token zu erhalten, was die Hürde für Server-Side Request Forgery erhöht. Aber das kann die Entscheidung nicht für dich treffen: Ob der Agent Instance-Metadaten lesen soll, ist eine Ermessensfrage, die ein Mensch einmal entscheiden dürfen sollte.
Was jetzt passiert: eine Abfrage, kein Block
PR #106418 ergänzt eine deterministische Regel in der Terminal-Approval-Schicht, mit einem Namen, der genau sagt, was sie ist:
cloud metadata endpoint access (instance credentials)
Das Verhalten ist eine Approval-Abfrage, kein hartes Blocken. Das ist Absicht. Auf echten Cloud-VMs gibt es legitime Anwendungen – der Agent verwaltet seine eigene Instance, du debuggst eine IAM-Rolle, du skriptest einen Instance-Facts-Reporter. Der Operator entscheidet.
Konkret:
- Wenn eines dieser vier Host-Literale in einem Terminal-Kommando auftaucht, wird die Approval ausgelöst. Es spielt keine Rolle, welchen HTTP-Client du nutzt:
curl,wget, eine Umgebungszuweisung oder ein Skript-Argument zählen alle. - In unbeaufsichtigten Pfaden (Cron-Jobs, Approve-Mode-Automatisierung) werden diese Kommandos nicht automatisch genehmigt – mit dem Standard
approvals.cron_mode: denywird ein geplanter Job, der Metadaten lesen will, abgelehnt statt durchgewinkt. - Die Erkennung ist Pattern Matching, kein Modellurteil. Es gibt keine Drift danach, wie ein Modell heute fühlt, und keinen zusätzlichen Modellaufruf auf dem Hot Path.
Umfang: False Positives wurden bewusst eng gehalten
Die Implementierung nutzt Lookarounds im DANGEROUS_PATTERNS-Eintrag (in tools/approval_detection.py), damit die Regel nur bei den Endpunkten selbst auslöst:
curl http://169.254.1.1/status— andere Link-Local-Adressen — ist nicht betroffen;169.254.169.2540, wo der Endpunkt nur ein Präfix einer längeren Punktfolge ist, passt nicht;metadata.google.internal.example.com, ein anderer Host, der den String zufällig enthält, passt nicht.
Mit anderen Worten: Die Regel meldet sich nur, wenn ein Kommando tatsächlich auf den Credential-Endpunkt zielt, statt gewöhnlichen Link-Local-Verkehr als gefährlich zu behandeln.
Es lohnt sich auch, das vom browserseitigen Schutz zu trennen. Das Browser-Tooling erzwingt auf jedem Backend bereits einen eigenen Cloud-Metadata-Floor (und zusätzliche Einschränkungen beim Lauf in einem Container). Diese Änderung deckt den Terminal-Kommando-Pfad ab – eine andere Tür auf einer anderen Etage.
Das Design leiht sich aus bestehender Praxis: Claude Code 2.1.257s Changelog vermerkt, dass sein Auto-Modus aufhörte, Cloud-Metadata-Credential-Abrufe automatisch zu genehmigen (neben Egress-Evasion und Cross-Tenant-Reach), sofern die Umgebung sie nicht als erwartet deklariert. Hermes übernimmt das Metadata-Stück als deterministische Terminal-Regel – schlichter, aber vorhersagbar.
Die Abfrage, die du sehen wirst, und wie du sie justierst
Wenn die Regel auslöst, bekommst du eine Command Approval mit jenem Reason-String. Was du als Nächstes tun kannst, hängt von deiner approvals-Konfiguration ab:
# In welchem Modus sind wir? (smart ist der Standard)
hermes config get approvals.mode
# Manual: jedes gefährliche Kommando fragt dich
hermes config set approvals.mode manual
# Approvals abschalten (yolo) – aber Achtung: die Deny-Liste gilt auch dann noch
hermes config set approvals.mode off
Details, über die Leute stolpern:
approvals.modeakzeptiertsmart,manualoderoff; Standard istsmart, wobei ein Reviewer-Modell mehrdeutige Fälle übernimmt, während deterministische Regeltreffer gar kein Modell brauchen.approvals.denyist eine bedingungslose Blockliste aus case-insensitiven fnmatch-Globs (zum Beispielrm -rf *). Sie gilt auch unter yolo,approvals.mode: offbedeutet also nicht „es wird nie etwas blockiert“.- Wiederholte Ablehnungen lösen einen Breaker aus:
approvals.denial_breaker_thresholdsteht standardmäßig auf 3, du kannst also nicht in einer endlosen Deny-Schleife hängen. - Geplante Jobs folgen
approvals.cron_mode(Standarddeny). Muss ein Cron-Job wirklich Instance-Metadaten lesen, ändere diesen Key explizit, statt Approvals pauschal abzuschalten. - Innerhalb einer Sitzung kann eine interaktive Ablehnung mit
/denyplus Begründung festgehalten werden.
Zur breiteren Approval-Maschinerie – Modi, das Reviewer-Modell unter auxiliary.approval.model und wie man das testet – sieh dir den Leitfaden zum Einrichten von Smart Approvals und Approvals-Dry-Runs an. Verwandte „erst fragen“-Guards decken SSH-Config-Änderungen und Paketmanager-Deinstallationen ab.
Prüf es auf deiner eigenen Maschine
Wenn du eine eigene Cloud-VM hast, kannst du die Form dieses Risikos gefahrlos bestätigen (nur auf Maschinen, die dir gehören und die du nutzen darfst):
# 1) Ein harmloser Lesevorgang: liefert quasi-öffentliche Instance-Fakten
curl -s --max-time 2 http://169.254.169.254/latest/meta-data/instance-id
# 2) AWS IMDSv2: ein Einmal-Token holen, dann mit ihm den Credential-Pfad lesen
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 60")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Kommando 2 gibt den Rollennamen und temporäre Credentials aus – genau deshalb verdient es eine menschliche Bestätigung. Füge nie ein, was es zurückgibt, in eine Sitzung, ein Ticket oder einen Chat – es ist Account-Level-Autorität in Textform.
Wenn du diese Regel auf einer Nicht-Cloud-Maschine auslösen siehst (ein Laptop, ein privates Rechenzentrum), ist die übliche Ursache eine hartcodierte Adresse in irgendeinem Skript. Brauchst du sie wirklich – du baust ein Cloud-Self-Check-Tool –, genehmige es einmal und lass den Grund sichtbar; das ist sicherer, als Approvals für alles herunterzudrehen.
Das Fazit
Ein Satz: 169.254.169.254 auf einer Cloud-VM ist nicht „irgendeine interne Adresse“ – es ist ein unauthentifizierter Credential-Spender. Es hinter eine Approval zu legen tauscht ein wenig Reibung gegen den billigsten Credential-Diebstahlpfad, den es gibt.
Drei Dinge, die man in der Praxis behalten sollte:
- Wenn ein Agent plötzlich Instance-Metadaten will, halte inne und lies das Kommando. Es kann legitimes Debugging sein – oder eine injizierte Anweisung.
- Geplante Jobs lehnen diese Kommandos standardmäßig ab; brauchst du sie wirklich, erlaube diesen Fall über
approvals.cron_mode, statt Approvals ganz abzuschalten. approvals.denyist bedingungslos, undapprovals.mode: offbedeutet nicht „es wird nie etwas blockiert“.
Diese Änderung ist nach dem Tag v0.21.2 gemerged, liegt zum Zeitpunkt des Schreibens also auf main und in keinem getaggten Release. hermes update holt main, oder warte auf die nächste Version.