Das Find-Replace, das 300 Dateien beschädigte
Agentengetriebene Codebasen bewegen sich schnell – und versagen auf Weisen, die ein menschlicher Reviewer auf einen Blick erkannt hätte. Hier sind zwei Fehlschläge, öffentlich gemacht, und die Schutzmechanismen, die sie jetzt eiskalt stoppen.
Der Tag, an dem man nach Ärger sucht, ist meist der Tag, an dem man welchen findet. Diesen Samstag hatte ich einen tiefen Audit-Durchgang über die Codebasis angesetzt. Der Plan war, technische Schulden aufzuarbeiten: nackte except-Blöcke, hartcodierte Konstanten, Abweichungen in Konventionen, die Art von Haufen, der sich in jedem Projekt ansammelt, das mit Tempo läuft.
Ich fand zwei Dinge, die mir keine Freude bereiteten. Beide sind jetzt behoben. Beide sind die Art von Fund, über die ich öffentlich sprechen möchte, weil sie etwas Reales darüber erklären, wie eine agentengetriebene Codebasis versagt und wie die Disziplin, ein Audit durchzuführen, die Fehlschläge abfängt.
Fund eins: das Find-Replace, das die Codebasis auffraß
Früher im Projekt war ein Agent gebeten worden, einen marketingartigen Sweep über Docstrings und Kommentare durchzuführen. Die Absicht war vernünftig: eine bestimmte Phrase umbenennen, die in ein paar öffentlich sichtbaren Strings vorkam. Die Ausführung war bei den Wortgrenzen nicht sorgfältig genug.
Die Phrase, die der Agent austauschen sollte, war ein bestimmter Marketing-Slogan, der die Wörter „process”, „success” und „access” als Teil längerer Phrasen enthielt. Die Find-Replace-Operation traf diese Substrings an Stellen, an denen sie nicht hätten treffen sollen. Variablennamen. Funktionsnamen. Testbeschreibungen. Inline-Kommentare. Überall, wo diese drei Substrings vorkamen, wurde der Ersatzstring des Agenten eingesetzt.
Das Ergebnis waren dreihundert Dateien mit subtil verstümmelten Bezeichnern und Prosa. Variablen namens process_event wurden zu etwas mit „Raku Game Engine Milestone” mitten im Token eingebettet. Funktionsbeschreibungen lasen sich wie Unsinn. Testbeschreibungen behaupteten, Dinge zu testen, die nicht existierten. Die Codebasis kompilierte, weil die kaputten Bezeichner innerhalb ihrer Dateien konsistent waren, aber die menschenlesbare Schicht der Codebasis war an subtilen Stellen überall vandalisiert.
Ich möchte konkret sein, wie diese Art von Fehlschlag passiert, weil es eine Klasse agentengetriebenen Versagens ist, die andere Teams treffen wird.
Der Suchbereich war zu weit gefasst. Der Agent wurde angewiesen, eine Phrase zu finden und zu ersetzen. Die Phrase war zufällig ein Substring gängiger englischer Wörter. Die richtige Art, das einzugrenzen, ist über Wortgrenzen (\bwort\b in Regex), mit expliziter Groß-/Kleinschreibungs-Sensitivität, mit einer expliziten Positivliste von Dateiendungen, mit einer expliziten Negativliste von Bezeichner-Kontexten. Die Anweisung, die der Agent erhielt, hatte keine dieser Einschränkungen.
Der Agent markierte die Breite nicht. Dreihundert Dateien sind viele Dateien. Ein Agent, der einen PR landet, der für eine kleine Marketing-Anpassung dreihundert Dateien anfasst, hätte die Breite beim Öffnen des PR markieren sollen. Der Agent tat das nicht. Der PR-Titel lautete ungefähr „Marketing-Text in Docstrings aktualisieren”. Der PR-Body listete die Dateianzahl als Zahl auf, nicht als Bedenken.
Mein Review-Prozess fing es nicht ab. Der PR-Diff bestand aus dreihundert Dateien kleiner Zwei-Zeilen-Änderungen, die alle wie derselbe Edit aussahen. Der Diff liest sich beim Überfliegen wie ein sauberer Sweep. Die Verunstaltung zeigt sich nur, wenn man den tatsächlich geänderten Inhalt einer Datei zu dem Zeitpunkt liest, an dem die Substitution des Agenten Unsinn produzierte. Das tat ich nicht. Ich habe gemerged.
Die CI fing es nicht ab, weil die Namen immer noch parsten. Die verstümmelten Bezeichner waren syntaktisch gültig. Compiler kümmert es nicht, ob Ihre Variable so heißt, dass es wie ein Marketing-Slogan aussieht. Der Build war grün. Tests liefen weiter. Der Schaden lag auf der menschlichen Ebene des Codes, nicht auf der Maschinenebene.
Wie ich es diesen Samstag behoben habe
Ein Skript. Das Skript tut drei Dinge.
Erstens: die kanonischen Bezeichnernamen zurückableiten. Aus der Git-Historie vor der Landung des schlechten Find-Replace rekonstruiert das Skript, wie jeder Bezeichner eigentlich heißen sollte. Die Rekonstruktion ist mechanisch: Für jede Datei, die vom schlechten PR angefasst wurde, wird die Version vor dem PR mit der Version nach dem PR verglichen, und für jedes ersetzte Token wird eine Wiederherstellung des Namens von vor dem PR vorgeschlagen. Die meisten Dateien lassen sich sauber wiederherstellen. Eine kleine Anzahl braucht manuelles Review, weil sie legitime Änderungen über der Beschädigung geschichtet hatten.
Zweitens: ein grep-getriebener Plausibilitätsdurchgang. Selbst nach der Wiederherstellung waren manche der beschädigten Bezeichner aus neuem Code referenziert worden, der nach der Landung des schlechten PR geschrieben wurde. Diese Referenzen waren gegen die verstümmelten Namen geschrieben. Der grep-Durchgang findet jede Referenz auf einen im verstümmelten Stil gehaltenen Bezeichner in Code, der nach der Landung des schlechten PR geschrieben wurde, und markiert jede zur manuellen Entscheidung: War beabsichtigt, dass dieser neue Code den verstümmelten Namen verwendet (selten), oder verwendete er einfach, welcher Name zu dem Zeitpunkt zufällig existierte (die meisten Fälle)?
Drittens: ein Schutzmechanismus für die Zukunft. Jede Find-Replace-Operation, die ein Agent jetzt durchführt, muss (a) Wortgrenzen-Eingrenzung, (b) Groß-/Kleinschreibungs-Sensitivität, (c) eine Positivliste von Dateiendungen, (d) einen maximalen Dateianzahl-Schwellenwert, oberhalb dessen der Agent markieren und explizites Review anfordern muss, und (e) eine Stichprobe von drei zufälligen Treffern, die der Agent vor der Anwendung des vollständigen Replace zeigen muss, spezifizieren. Der Schutzmechanismus steht im Copilot-Guide und ist jetzt Teil jeder Find-Replace-Aufgabenrahmung.
Die Beschädigung ist jetzt repariert. Das Audit-Skript, das die Reparatur durchgeführt hat, liegt im Repository, jederzeit ausführbar, mit den Diff-Ausgaben als Beleg gesichert. Die Lektion steht im Copilot-Guide.
Fund zwei: das hartcodierte HMAC-Geheimnis
Der tiefe Audit-Durchgang förderte auch etwas zutage, das ich früher hätte abfangen sollen. Die Lizenzierungsschicht der Runtime verwendet HMAC-SHA-256, um Lizenztoken zu verifizieren. Das HMAC-Geheimnis war in eine Quelldatei hartcodiert. Die Quelldatei war im öffentlichen Repository. Das Geheimnis war ein echtes Geheimnis, das von einem echten Produktions-Verifikationspfad verwendet wurde.
Das ist der peinlichste Fund des Tages. Ich möchte ehrlich darüber sein, weil es genau die Art von Sache ist, die in schnell bewegten agentengetriebenen Codebasen passiert, und die öffentliche Diskussion darüber, wie man es abfängt, wertvoller ist als die private Diskussion.
Der Weg, den es genommen hat, um zu landen: Eine frühe Version der Lizenzierungsschicht wurde mit einem Platzhalter-Geheimniswert prototypisiert, der ersetzt werden sollte, bevor die Schicht an irgendjemanden ausgeliefert wurde. Der Prototyp landete in einem PR mit einem offensichtlich aussehenden Dev-Platzhalter. Mit der Zeit wurde echte Verifikationslogik über dem Platzhalter hinzugefügt. Der Platzhalter hörte auf, wie ein Platzhalter auszusehen, sobald er in echt aussehenden Validierungscode eingewickelt war. Bis es jemand bemerkte, wurde das Geheimnis in produktionsähnlichen Abläufen verwendet, und die Datei war im öffentlichen Repository.
Was ich heute getan habe:
- Das Geheimnis rotiert. Der kompromittierte Wert ist nicht mehr der Produktionswert. Der neue Wert steht in einer Umgebungsvariable, mit einem
warnings.warn()-Fallback für Dev-Umgebungen, der Dev-Arbeit ohne echtes Geheimnis fortsetzen lässt, aber laut darüber schreit. - Den hartcodierten Wert aus der Quelldatei entfernt. Der Ersatz ist ein
getenvmit einer klaren Fehlermeldung, falls die Umgebungsvariable in einem Produktions-Build nicht gesetzt ist. - Eine CI-Prüfung hinzugefügt, die nach hartcodierten Geheimnissen scannt, die zu gängigen Mustern passen (hoch-entropische Strings, Base64-förmige Token, alles, was wie ein Schlüssel aussieht). Die Prüfung ist die Art von kleiner Infrastruktur, die den nächsten Versuch abfängt, bevor er landet.
- Ein Follow-up eingereicht, um den Rest der Codebasis auf ähnliche Muster zu prüfen. Das Audit ist die Arbeit eines separaten Wochenendes. Heute ging es darum, den unmittelbaren Fund zu schließen.
Die Lizenzierungsschicht funktioniert weiterhin. Der neue Pfad ist sicherer. Das kompromittierte Geheimnis wurde innerhalb von Stunden nach der Entdeckung rotiert.
Worauf sich das verallgemeinern lässt
Ein paar ehrliche Punkte.
Agentengetriebenes Find-Replace braucht explizite Eingrenzungsregeln. Das ist das dritte Mal in der Geschichte des Projekts, dass mich ein zu breiter Sweep gebissen hat. Die ersten beiden Male waren weniger schädlich. Dieses Mal war es schlimm genug, um einen dauerhaften Schutzmechanismus zu verdienen. Der Schutzmechanismus ist jetzt vorhanden.
Hartcodierte Geheimnisse in Quelldateien sind ein Disziplinversagen, kein Tooling-Versagen. Kein Tool wird ein Team retten, das ein echtes Geheimnis in eine öffentliche Datei einfließen lässt. Die Disziplin „jeder Commit wird auf hartcodierte Zugangsdaten geprüft” ist der eigentliche Fix. Der CI-Scan hilft. Die Disziplin ist das, was zählt.
Audits finden, was das Review verpasst hat. Die Disziplin, einen geplanten Audit-Durchgang über die Codebasis laufen zu lassen, gezielt auf der Suche nach den Fehlermodi, die PR-für-PR-Review tendenziell verpasst, lohnt die Zeit. Das heutige Audit fing zwei Dinge ab, die das PR-Review durchgelassen hatte. Zukünftige Audits werden andere Dinge abfangen. Der Rhythmus ist der Punkt.
Was Partner und Entwickler daraus mitnehmen sollten
Wenn Sie eine Engine für eine Partnerschaft evaluieren, fragen Sie das Team, wie sie mit dem Fehlermodus „agentengetriebener, zu breiter Sweep” umgehen. Die richtige Antwort umfasst explizite Eingrenzungsregeln, verpflichtende Markierung bei großen Änderungen, und Audit-Durchgänge. Die falsche Antwort ist: „Dieses Problem haben wir noch nicht gesehen.”
Wenn Sie selbst einen agentengetriebenen Workflow betreiben und in letzter Zeit kein Audit auf hartcodierte Geheimnisse durchgeführt haben, tun Sie es. Die Wahrscheinlichkeit, dass sich etwas eingeschlichen hat, ist nicht null. Die Kosten, es jetzt zu finden, sind klein.
Wenn Sie ein Sicherheitsprofi sind, der dies liest, und Vorschläge haben, bin ich wirklich interessiert. Die Klasse von Fehlschlag, gegen die ich mich zu wappnen versuche, ist „der Agent tut etwas, das ein menschlicher Reviewer auf einen Blick erkannt hätte, aber im Bulk-Review-Muster, das ein agentengetriebener Workflow fördert, nicht abgefangen wurde.” Vorschläge willkommen.
Samstagnachmittag. Die Codebasis hat einen harten Blick abbekommen. Zwei Funde, beide behoben. Das nächste Audit steht im Kalender.
Zurück zum Bauen.
Eine Runtime, gebaut, um geprüft zu werden
RakuAI ist die räumliche Runtime, mit der KI-Labore und Smart-Glasses-Hersteller bauen – diszipliniert durch Audits, gehärtet durch öffentliche Lektionen. Sehen Sie, wie wir für partnergerechtes Vertrauen konstruieren.