Dev

Die Injection, gegen die kein Review hilft

Code-Injection wird an der Naht gestoppt, an der Daten zu Code werden, indem man eine ganze syntaktische Klasse ablehnt statt jeden Einzelfall abzuwägen. Das trägt überall dort, wo diese Grenze entscheidbar ist, also genau die Eigenschaft, die Prompt-Injection in natürlicher Sprache nie besitzt.

Es gibt eine beruhigende Geschichte darüber, wie man KI-Agenten sicher hält: Setz einen Menschen in die Schleife. Der Agent schlägt vor, ein Mensch genehmigt, und alles Bösartige wird am Tor abgefangen. Ich baue Pipelines, die genau so gebaut sind: Ein Agent zieht aus dem Web, stellt einen Entwurf zusammen und legt ihn einer Person vor, bevor irgendetwas veröffentlicht wird. Und je länger ich anschaue, was sie tatsächlich sicher hält, desto weniger von dieser Sicherheit geht auf die menschliche Freigabe zurück. Die Teile, die Code am Ausführen hindern, klären sich von selbst, bevor der Entwurf die Augen einer prüfenden Person erreicht, und keiner von ihnen beurteilt irgendetwas.

Wo die Injection wirklich sitzt

Die Injection, auf die es ankommt, steckt nicht in der Anweisung, die ein Mensch prüft. Sie steckt in den Daten. Die Quellen sind nutzergeneriert, Reddit-Threads, Hacker-News-Kommentare, per Definition nicht vertrauenswürdig. Und das Format, in dem diese Entwürfe geschrieben sind, MDX, behandelt geschweifte Klammern als Code: Ein Ausdruck wie {something} ist ein JavaScript-Ausdrucksknoten. Nicht vertrauenswürdiger Text plus ein Format, das Klammern als Code liest, ergibt eine Daten-zu-Code-Naht mitten im Inhalt.

Die Pipeline hat eine Regel gegen das Füttern dieser Naht: Roher Forentext gelangt nie wörtlich in einen Entwurf, Agenten entnehmen ihm nur Themen, und jedes echte Zitat muss auf eine geprüfte Quelle zurückgehen und in einem Code-Fence stehen. Aber eine Regel ist eine Anweisung an einen Agenten, und eine Anweisung ist genau die Art von Garantie, der dieser Essay nicht traut. Wenn eine Klammer trotzdem einmal ins MDX gelangt, ein Agent, der die Regel ignoriert hat, ein Zitat mit falsch gesetztem Fence, dann hätte die Sicherheit nicht davon abhängen dürfen, dass die Regel hält. Deshalb sitzen die Teile, die es wirklich stoppen, weiter unten, und es ist ihnen egal, wie die Klammer dorthin kam.

Das ist keine Gefahr, die ich erfunden habe, um einen Lint zu rechtfertigen. Im Juli 2026 veröffentlichte Hugging Face eine Ersthand-Chronik eines Angriffs genau dieser Form: eine Dataset-Konfiguration, also Daten, die eine Jinja2-Template-Injection und einen HDF5-Dateizugriff trug, verarbeitet von einem autonomen Agenten, ohne einen Menschen irgendwo in der Schleife. Die Datei war die Payload; sie zu rendern war die Ausführung. Daten wurden zu Code, und es gab keinen Prüfer zu umgehen, weil nie ein Prüfer auf dem Pfad war.

Die Naht ist entscheidbar

Das Entscheidende am MDX-Fall ist, dass die Grenze syntaktisch ist. MDX hat eine Grammatik. Ein Parser kann den Baum durchlaufen und mit Sicherheit auf jeden Ausdrucksknoten zeigen, das ist Code, das ist Prosa, ohne zu raten, weil die Unterscheidung in die Sprache eingeschrieben ist und nicht aus der Bedeutung erschlossen wird. Jede echte Verteidigung weiter unten baut darauf auf.

Ein Renderer kann das bereits ausnutzen. next-mdx-remote, ein MDX-Renderer, hat eine blockJS-Option, die standardmäßig aktiv ist. Diese Option ist kein Scanner, der entscheidet, welche Ausdrücke gefährlich aussehen. Sie durchläuft den geparsten Baum und löscht jeden Ausdrucksknoten, restlos, bevor irgendetwas rendert. Ein bösartiges {fetch(evilUrl)} und ein harmloses {1 + 1} treffen dasselbe Ende: entfernt. Nicht beurteilt, nicht in eine Sandbox gesteckt, sondern verweigert, denn an einer syntaktischen Naht muss man nicht beurteilen. Man kann die ganze Klasse verbieten.

Zwei Prüfungen, keine urteilt

Die Pipeline stellt eine zweite Prüfung vor diese, noch gröber. Bevor der Vorschau-Server startet, entfernt ein Lint das Frontmatter, entfernt Codeblöcke in Fences, entfernt Inline-Code und schlägt in dem Moment fehl, in dem irgendwo sonst eine einzelne geschweifte Klammer überlebt. Er parst nicht und urteilt nicht über Absicht. Jede Klammer außerhalb eines Fence, auf oder zu, und der Entwurf kommt nicht weiter.

Warum die Mühe, wenn das Framework Ausdrücke ohnehin entfernt? Weil die beiden verschiedene Fehler fangen. Einen wohlgeformten Ausdruck löscht blockJS, dort ist der Lint für die Sicherheit überflüssig, aber nicht für den Text: Ohne ihn würde eine aus einem Zitat übernommene Klammer einfach aus dem veröffentlichten Beitrag verschwinden, lautlos, ohne dass jemand den fehlenden Satz bemerkt. Eine fehlerhafte Klammer, ein einzelnes { ohne Partner, erreicht den Löschschritt gar nicht erst, weil sie zuerst nicht als MDX parst, und das zeigt sich als Kompilierfehler mehrere Schichten tief, wenn die Vorschau-Route angefragt wird. Der Lint macht aus beidem einen benannten Fehler, noch bevor der Dev-Server überhaupt läuft.

Ich werde es nicht überverkaufen, denn die beiden sind nicht dasselbe. blockJS läuft innerhalb des Render-Pfads, ohne es rendert nichts. Der Lint ist ein Schritt, den die Pipeline zuerst ausführen soll, und ein Schritt lässt sich überspringen; nichts im Code erzwingt ihn. Er ist also weniger eine zweite Mauer als eine frühere, billigere, plus eine Absicherung für den Tag, an dem jemand blockJS abschaltet und der Render-Pfad aufhört, sich selbst zu schützen. Er ist auch für sich genommen grob: ein Regex, kein Parser, mit einer Lücke bei nicht zueinander passenden Backticks, die der echte MDX-Parser nicht hat, und einer kurzen Sperrliste von Injection-Phrasen, die genau der durchlässige sprachseitige Filter ist, dem der Rest dieses Essays nicht zu trauen rät, behalten nur, weil er fast nichts kostet. Bleibt eine Regel, die man sich merken sollte: Vermittle eine Naht mit dem Parser, der sie tatsächlich rendern wird, nie mit einem Doppelgänger, denn die Lücke zwischen beiden ist der Ort, an dem eine eingeschmuggelte Payload wohnt.

Die Naht in natürlicher Sprache hat kein solches Gate

Simon Willison hat Jahre mit demselben Problem von der Sprachseite verbracht, und seine Schlussfolgerung ist das Spiegelbild meiner: Es gibt keinen zuverlässigen Detektor für Prompt-Injection. Nicht, weil niemand klug genug wäre, sondern weil diese Grenze nicht syntaktisch ist. Eine bösartige Anweisung und eine legitime bestehen aus demselben Stoff, aus Wörtern, und kein Parser kann diesen Baum durchlaufen und urteilen: „Dieses Token ist Daten, jenes ist Code." Du kannst filtern, du wirst manches fangen, und der Angreifer redigiert um den Filter herum. Seine „lethal trifecta", also nicht vertrauenswürdiger Inhalt, Zugriff auf private Daten und ein Weg, diese Daten wieder hinauszuschicken, benennt die Bedingungen, unter denen diese Naht teuer wird.

Das ist die Trennlinie. Wo die Code-Daten-Grenze syntaktisch ist, bekommst du eine entscheidbare Naht und kannst die Klasse rundheraus verbieten, ohne Detektor und ohne Prüfer. Wo sie semantisch ist, im Kontext des Modells selbst, kannst du es nicht, und ein Mensch in der Schleife schließt die Lücke nicht. Er verlagert denselben Fehler auf einen langsameren, selbstsichereren Kontrollpunkt.

Der Kniff, und wo er endet

Das Prinzip ist also nicht „prüf gründlicher", und es ist nicht einmal „lass den Build scheitern". Es lautet: Trenne Code von Daten durch Konstruktion, an der Naht, wo das eine zum anderen wird, bevor irgendetwas ausgewertet wird. Dafür gibt es zwei Wege. Entweder beschriftet die Grammatik die Seiten für dich, MDX markiert seine Ausdrücke, und du löschst die markierte Klasse, oder die Schnittstelle führt sie auf getrennten Kanälen, so wie eine parametrisierte Query die Anweisung über den einen Pfad schickt und die Werte über den anderen, sodass weiter unten nie etwas entscheiden muss, was was ist. Eine Klasse zu verbieten ist der billige Spezialfall, verfügbar nur, wenn die gefährliche Form im nicht vertrauenswürdigen Kanal keinen legitimen Nutzen hat: Klammern qualifizieren sich, weil ich in Prosa nie eine brauche; per String zusammengesetztes SQL nicht, weshalb die Lösung dort getrennte Kanäle sind und kein Verbot. Ein sicherer Deserializer und eine Template-Sandbox greifen dieselbe Naht von weiter hinten an, mit einer Allowlist von Typen oder einem ummauerten Render, sie kosten mehr und lassen sich umgehen, weil sie dort schlichten, wo die Grammatik nicht beschriftet. Das sauberste Gate braucht überhaupt keine Intelligenz; der Rest erkauft Sicherheit mit Urteilsvermögen und bezahlt sie mit Ausbrüchen.

Und wenn die Naht nicht entscheidbar ist, wenn der „Code" Anweisungen sind, die im Kontext eines Modells leben, kannst du die Seiten nicht beschriften und den Kanal nicht aufteilen, denn Anweisungen aus Text zu lesen ist das ganze Produkt. Der Werkzeugkasten verschwindet aber nicht. Er verschiebt sich. Du hörst auf, die Anweisung mit einem Gate zu versehen, und versiehst stattdessen die Fähigkeit mit einem Gate: was der Agent aufrufen kann, was er erreichen kann, was er wieder hinausschicken kann. Nimm der Trifecta ein Bein, und die unentscheidbare Naht hört auf, eine Rolle zu spielen. Du hast die Injection nicht erkannt; du hast ihr weggenommen, wonach sie gegriffen hat.

Das bringt mich zurück zu jenem Prüf-Gate. Die Code-Ausführungs-Klasse war entschieden, bevor irgendjemand den Entwurf öffnete, zwei Prüfungen, die nicht verhandeln, hatten bereits jede Klammer verweigert, an einer Naht, wo die Antwort nie Ansichtssache war. Aber diese Klasse ist nicht die ganze Gefahr. Ein bösartiger Link rendert einwandfrei, rohes Markup geht unangetastet durch, und eine falsche Behauptung liest sich so sauber wie eine wahre. Nichts davon ist eine geschweifte Klammer, also fängt es kein Gate, und all das ist genau das, wofür ein menschliches Lesen da ist. Die Maschine nimmt, was entscheidbar ist, und ein Mensch nimmt, was es nicht ist, was die prüfende Person nicht zur unwichtigsten Zeile macht, sondern zur einzigen, die bleibt, sobald der entscheidbare Teil geklärt ist.

Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit

Max Nardit

@mnardit

Weitere Artikel

Setz das agentischere Modell auf den Review-Platz

Die Eigenschaften, die ein Modell agentischer machen (es erweitert den Scope, prüft sich ungefragt, schreibt länger), sind in einem eng begrenzten unbeaufsichtigten Worker ein Nachteil und in einem Reviewer ein Vorteil. Also laufen die Worker der Flotte auf Claude Opus 4.8, während Opus 5 das Review macht. Eine Notiz darüber, die Disposition eines Modells auf seine Rolle abzustimmen, nicht auf den Benchmark.

Claude hat mir um 10:47 Uhr morgens gesagt, ich soll schlafen gehen

Anthropic nennt es einen Character-Tic. Ich habe geschaut, was es tatsächlich produziert, und am Ende den veröffentlichten System-Prompt, das Character-Training-Paper und das Emotion-Concepts-Paper gelesen. Das Verhalten ist das, was der Stack wahrscheinlich macht.

Anthropic hängt auch den Rest des Agenten an den Zähler

Ab dem 15. Juni läuft programmatische Claude-Nutzung über ein eigenes Guthaben von 20–200 $ zu API-Preisen. Die E-Mail kam heute Morgen. Hier ist, was sie wirklich bedeutet und was sie über das Bauen auf fremder Rechenkapazität lehrt.