Startseite / Artikel

Drei LangChain-Middleware-Schutzmechanismen, die verhindern, dass Agenten Geheimnisse preisgeben

Verhindern Sie, dass ein Agent API-Schlüssel an das Modell sendet oder Dateien ohne Anfrage löscht: PIIMiddleware, HumanInTheLoopMiddleware, Aufrufbeschränkungen und warum die Reihenfolge der Middleware die Sicherheit bestimmt.

Verlag: REACTAPP.TOP10 Min. Lesezeit2230 Wörter

Quellen: **If You’re Not Using These Three Guards in Your AI Agent, It Has Already Sent Your Secrets to the Provider**

Etwas Sensibles in ein Chat-Fenster einzufügen ist einfach – und genauso leicht zu vergessen. Vielleicht wurde ein Datei zur Fehlerbehebung an ChatGPT gesendet, und man erstarrt nach dem Versand und fragt sich, ob darin eine API-Schlüssel, ein Datenbankpasswort oder ein Client-Token enthalten ist. Man liest die Nachricht erneut, schließt das Tab und entscheidet, dass wahrscheinlich alles in Ordnung ist. Wahrscheinlich handelt es sich dabei aber nicht um eine zulässige Vorgehensweise.

Eine KI-Assistentin verschlimmert die Situation, da sie dasselbe automatisch und mehrfach pro Sitzung ohne Benachrichtigung durchführt. Wenn man ihr Zugang zu einer Codebasis zur Überprüfung gibt, wird sie letztendlich eine .env-Datei öffnen, die Inhalte wie OPENAI_API_KEY=sk-proj-... enthält. Ohne Schutzmaßnahmen gelangt dieser Schlüssel wortwörtlich zum Sprachmodell, landet in Protokollen und bleibt in den Aufzeichnungen des Anbieters. Ob er weitergeleitet wird – in ein Feintunings-Set oder einen Logging-Dienst, den man nicht sehen kann – bleibt von Ihrer Seite aus unbekannt.

Eine Weile später ruft derselbe Agent ein Aufräumwerkzeug auf und löscht einen Verzeichnis, von dem Sie nie gesprochen haben, indem er leise delete_directory(path="./tmp_backups") protokolliert und fortfährt. Sie bemerken es erst, wenn eine Datei, die Sie benötigen, verschwunden ist.

Es handelt sich um zwei getrennte Fehler ohne Bug und ohne dramatische Fehlvorstellungen. Der Agent hatte einfach keine klare Trennung zwischen dem, was er tun konnte, und dem, was er tun sollte. Middleware ist die Lösung, und dieselben Prinzipien stecken hinter Schutzmechanismen in Tools wie Cursor und GitHub Copilot. So können Sie sie in Ihren eigenen Agent integrieren.

Was Middleware leistet

Bilden Sie sich den Agenten als eine Produktionslinie vor. Daten kommen von Ihnen, aus Dateien und aus Suchergebnissen, gelangen zum Modell, das Entscheidungen trifft, die wiederum zu Aktionen werden. Ein Middleware-Element ist eine Kontrollstelle auf dieser Linie. Es fängt die Daten ab, führt eine Überprüfung oder Transformation durch und leitet sie anschließend unverändert weiter, ändert sie oder stoppt sie.

Die richtige Zeitpunkt ist entscheidend. Zu spät und die sensiblen Daten haben bereits eine Grenze überschritten. Zu früh und man prüft das Falsche.

Copilots geheimes Scannen findet auf der Ausgabeseite statt. Bevor ein Vorschlag in Ihrem Editor erscheint, prüft ein Mustererkennungsmodul diesen, und alles, was wie ein Schlüssel mit den Anfangsbuchstaben sk-, ghp_ oder AKIA aussieht, wird unterdrückt. Cursors Funktion „Review Changes“ erfolgt vor jeder Dateispeicherung: Die Ausführung wird vollständig pausiert, ein Diff wird angezeigt, und der Agent kann nicht fortfahren, bis Sie zustimmen. Wenn Sie ablehnen, findet keine Speicherung statt, und der Agent sucht nach einer anderen Lösung.

Das eine schützt das, was aus dem Modell kommt, das andere das, was das Modell tun könnte. LangChain ermöglicht es Ihnen, beides sowie Ressourcenlimits zu implementieren.

Geheimnisse aufspüren: PIIMiddleware

Ihr Agent berührt das Dateisystem niemals direkt. Er sieht nur das, was in seinem Kontextfenster angezeigt wird – also das, was Sie eingeben, sowie die Ergebnisse seiner Tools. Sensible Daten können daher nur auf zwei Arten eingegeben werden: Entweder kopieren Sie sie ein, oder ein Tool liest sie von der Festplatte, wie zum Beispiel beim Scannen eines Verzeichnisses, bei dem eine .env-Datei geöffnet wird. Beide Wege führen über die Middleware-Schicht, bevor das Modell etwas sieht – und genau an dieser Stelle wirkt PIIMiddleware.

Es gibt eine Flagge namens apply_to_tool_results, die in den meisten Tutorials ohne Erklärung verwendet wird. Ihr Standardwert ist False. Aktivieren Sie nur apply_to_input=True, um den Fall abzudecken, in dem Sie sensible Daten einfügen – der Fall, in dem der Agent eine Datei liest, bleibt dabei ungeschützt. Ein Agent mit Dateizugriff wird gerne eine ganze .env-Datei als Ergebnis eines Tools abrufen, und das Modell erhält sie anschließend.

Eine minimale, korrekte Einrichtung sieht so aus:

from langchain.agents.middleware import PIIMiddleware

PIIMiddleware(
    "email",
    strategy="redact",            # replaces match with [REDACTED_EMAIL]
    apply_to_input=True,          # scans what you type
    apply_to_tool_results=True,   # scans what tools return - never skip this
)

API-Schlüssel sind es, die tatsächliche Probleme verursachen, und kein Lagererkennungsprogramm kennt das Format Ihres Schlüssels – daher liefern Sie eine Regex. Diese umfasst die gängigen Präfixe:

import re
API_KEY_PATTERN = r"(?:sk-|ghp_|AKIA)[a-zA-Z0-9]{20,48}"
# sk-   → OpenAI and Anthropic keys
# ghp_  → GitHub personal access tokens
# AKIA  → AWS access key IDs
PIIMiddleware(
    "api_key",
    detector=API_KEY_PATTERN,
    strategy="redact",
    apply_to_input=True,
    apply_to_tool_results=True,
)

Das Middleware bietet vier Strategien:

  • redact ersetzt den gefundenen Wert durch [REDACTED_FIELDNAME]. Sie eignet sich für Protokolle, Spuren und einweg-Pipelines.
  • mask ersetzt Zeichen durch Asterisk-Symbole, behält aber die Länge bei – nützlich, wenn man zeigen möchte, dass ein Wert vorhanden ist, ohne ihn preiszugeben.
  • block löst eine Ausnahme aus und beendet den gesamten Ablauf. Verwenden Sie sie, wenn eine Toleranz nicht vorhanden ist, beispielsweise in hochsicheren Umgebungen.
  • hash ersetzt den Wert durch einen deterministischen Hash – hilfreich, wenn man einer Entität folgen muss, ohne ihren Wert speichern zu wollen.

Die meisten Menschen gehen davon aus, dass block die sicherste Wahl ist – doch bei den Ergebnissen von Tools ist das in der Regel falsch. Angenommen, der Agent liest während einer Überprüfung 30 Dateien und in einer davon befindet sich eine E-Mail-Adresse in einem Kommentar. Mit block wird dadurch die gesamte Sitzung beendet. redact entfernt den Wert und ermöglicht die Fortsetzung der Arbeit, was fast immer das ist, was man möchte. Verwenden Sie block nur in Situationen, in denen allein das Vorhandensein sensibler Daten bedeutet, dass der Agent an einen Ort gelangt ist, an den er nicht hätte gelangen dürfen.

Testen Sie sich selbst: Wenn apply_to_input=True gesetzt ist und apply_to_tool_results bei seinem Standardwert bleibt, welche spezifische Situation bleibt ungeschützt? Sie sollten in einem Satz antworten können.

Stopp von stillen Löschungen: HumanInTheLoopMiddleware

Zur gelöschten Verzeichnisstruktur zurück. HumanInTheLoopMiddleware ist das Review-Changes-Muster von Cursor in eine Code-Primitivfunktion umgewandelt. Bevor irgendein zerstörender oder ändernder Tool ausgeführt wird, pausiert der Agent und speichert seinen Zustand. Man sieht genau, was er vorhat, und muss dies genehmigen, bevor etwas geschieht.

Die Pause ist der einfachere Teil. Das Wiederaufnehmen ist der schwierigere Teil. Zurück in den genauen Zustand zu gelangen, in dem sich der Agent befunden hat – möglicherweise Stunden oder Tage später und nach einem Prozessneustart – erfordert, dass der Konversationszustand, die ausstehende Aktion sowie der Stand des logischen Denkprozesses des Agents an einem dauerhaften Ort gespeichert werden. Diese Speicherstelle wird Checkpointer genannt. Ohne einen solchen Checkpointer tritt ein Unterbruch ein, der Zustand verschwindet und der nächste Aufruf beginnt von vorne; dadurch beendet das Genehmigungsverfahren einfach nur die Sitzung.

Die Pause tritt ein, nachdem das Modell entschieden hat, was zu tun ist, aber bevor irgendein Tool ausgeführt wird. Das ist das einzige praktikable Zeitfenster, da die Entscheidung sichtbar ist und noch nichts Unumkehrbares geschehen ist.

from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.sqlite import SqliteSaver

# The checkpointer is not optional. Without it, resume is impossible.
with SqliteSaver.from_conn_string("./review_agent.db") as checkpointer:
    agent = create_agent(
        model="anthropic:claude-sonnet-4-20250514",
        tools=[read_file, list_directory, write_file, search_codebase],
        middleware=[
            HumanInTheLoopMiddleware(
                interrupt_on={
                    "write_file": True,       # always pause before writing
                    "read_file": False,        # reading is safe - no pause needed
                    "list_directory": False,
                    "search_codebase": False,
                }
            ),
        ],
        checkpointer=checkpointer,            # saved to SQLite, persists across restarts
    )

Die Fortsetzung nach der Genehmigung sieht so aus:

# First call — agent hits the interrupt at write_file and pauses
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Review and fix the config files"}]},
    config={"configurable": {"thread_id": "session-001"}}
    # thread_id ties the saved state to this specific session
)


# result.interrupted == True
# result.pending_tool_call == {"name": "write_file", "args": {"path": "src/config.py", ...}}
# You show this to the user and wait for approval
# User approves - resume the same thread
final_result = agent.invoke(
    Command(resume=True),
    config={"configurable": {"thread_id": "session-001"}}
    # same thread_id - loads state from the checkpointer and picks up where it stopped
)

Ein Fehler kann den ganzen Nachmittag in Anspruch nehmen. interrupt_on={"write_file": True} übereinstimmt mit dem Toolnamen als exakter Zeichenfolge. Wenn Ihr Tool tatsächlich file_write oder update_file heißt, tritt die Unterbrechung niemals ein und der Aufruf läuft unbemerkt weiter. Überprüfen Sie die Protokolle, um den tatsächlichen Namen beim Aufruf des Tools zu ermitteln, und passen Sie interrupt_on entsprechend an.

Zwei Grenzen für zwei Fehlermodi

Jeder Schritt beinhaltet, dass der Agent das Modell aufruft, welches eine Aktion auswählt und gelegentlich Tools benutzt. Ein Modellaufruf kann mehrere Toolaufrufe gleichzeitig starten. Dabei handelt es sich um getrennte Ressourcen, wobei einer den anderen nicht berücksichtigt.

ModelCallLimitMiddleware zählt die Aufrufe des Modells, während ToolCallLimitMiddleware die Ausführungen der Tools zählt. Zehn Modellaufrufe, von denen jeder drei parallele Toolaufrufe auslöst, ergeben insgesamt 30 Ausführungen – ein alleiniger Limit für die Modellaufrufe würde dies niemals erkennen. Es funktioniert auch in die andere Richtung: Ein Agent, der sich ständig selbst überlegt, ruft weiterhin das Modell auf, ohne ein Tool zu verwenden, sodass die Anzahl der Toolaufrufe bei null bleibt und kein Limit überschritten wird.

from langchain.agents.middleware import ModelCallLimitMiddleware, ToolCallLimitMiddleware

ModelCallLimitMiddleware(
    max_calls=30,
    on_limit="raise",   # raises MaxCallsExceeded - catch this in your application
)
ToolCallLimitMiddleware(
    max_calls=60,
    on_limit="raise",
)

Nutzen Sie beide, da jeder Aspekt etwas abdeckt, was der andere nicht kann.

Zu on_limit="raise" im Vergleich zu on_limit="return": Wählen Sie "return" für eine interaktive Sitzung, in der ein teilweises Ergebnis hilfreich ist, wie zum Beispiel eine Zusammenfassung dessen, was bisher überprüft wurde. Wählen Sie "raise", wenn ein unvollständiges Ergebnis etwas weiter im Ablauf stören würde.

Achten Sie auch auf die Zahlen. Eine Grenze von max_calls=10 klingt wie ein sicheres Limit für einen schnellen Test, doch ein Agent mit einem mehrstufigen Plan kann bereits in seiner ersten Runde 8 Aufrufe verwenden. Das Limit unterbricht ihn dann mitten in der Aufgabe und liefert ein gekürztes Ergebnis – Sie können dabei zwanzig Minuten verlieren, bevor die Protokolle das erklären. Die Lösung besteht nicht darin, das Limit zu senken, sondern es auf das Niveau einzustellen, das die Aufgabe tatsächlich benötigt.

Reihenfolge ist die Sicherheitsgarantie

Middleware wird in der von Ihnen angegebenen Reihenfolge ausgeführt, und das ist kein unbedeutender Aspekt. Wenn ein Middleware, der den Inhalt einer Nachricht umschreibt oder zusammenfasst, vor PIIMiddleware kommt, scannt die PII-Prüfung Texte, die bereits verändert wurden. Wenn eine Änderung den Inhalt komprimiert, umformuliert oder umstrukturiert hat, sodass Ihr Muster nicht mehr zutrifft, überseht die Redaktion diese Änderung – das Geheime befindet sich bereits im Kontext des Modells, und die Redaktion wurde erst nach dem Schaden durchgeführt.

Für den hier verwendeten Sicherheitsstack ist die Reihenfolge wie folgt:

middleware=[
    # PIIMiddleware always first — it must see raw, untransformed data
    PIIMiddleware("api_key", detector=API_KEY_PATTERN, strategy="redact",
                  apply_to_input=True, apply_to_tool_results=True),
    PIIMiddleware("email", strategy="redact",
                  apply_to_input=True, apply_to_tool_results=True),

# Limits next - exact position within the group is flexible
    ModelCallLimitMiddleware(max_calls=30, on_limit="raise"),
    ToolCallLimitMiddleware(max_calls=60, on_limit="raise"),
    # Human-in-the-loop last in the safety group
    # (it fires in the after_model hook regardless of list position,
    # but last is a readable convention)
    HumanInTheLoopMiddleware(interrupt_on={"write_file": True}),
]

Betrachten Sie die Liste als Pipeline und nicht als Plugin-Register. Der vierte Eintrag erhält keine ursprüngliche Nachricht, sondern nur das, was die Einträge eins bis drei hinterlassen haben. Verschieben Sie PIIMiddleware unter alles, was mit dem Inhalt der Nachricht arbeitet – dann verschwindet diese Garantie stillschweigend.

Noch eine Überprüfung: Was passiert, wenn PIIMiddleware nach einem Middleware-Modul kommt, das die Konversationsgeschichte zusammenfasst? Wenn Sie diese Frage ohne Scrollen beantworten können, gilt die Reihenfolgeregel.

Aufbau eines Code-Review-Agenten

Kombinieren Sie nun all das zu einem Agenten, der sich wie die kommerziellen Tools verhält. Er verfügt über vier Werkzeuge: read_file, list_directory, write_file und search_codebase. Drei Sicherheitsschichten schützen diese Werkzeuge: Geheime Daten erreichen das Modell niemals, Schreibvorgänge benötigen Ihre Genehmigung und der Ressourcenverbrauch hat eine feste Obergrenze.

from langchain.agents import create_agent
from langchain.agents.middleware import (
    PIIMiddleware,
    HumanInTheLoopMiddleware,
    ModelCallLimitMiddleware,
    ToolCallLimitMiddleware,
)
from langgraph.checkpoint.sqlite import SqliteSaver

# Custom pattern for secrets common in codebases
API_KEY_PATTERN = r"(?:sk-|ghp_|AKIA)[a-zA-Z0-9]{20,48}"
# sk-   → OpenAI / Anthropic keys
# ghp_  → GitHub personal access tokens
# AKIA  → AWS access key IDs
with SqliteSaver.from_conn_string("./review_agent.db") as checkpointer:
    agent = create_agent(
        model="anthropic:claude-sonnet-4-20250514",
        tools=[read_file, list_directory, write_file, search_codebase],
        middleware=[
            # 1. Catch secrets before they reach the model - on both surfaces
            PIIMiddleware(
                "api_key",
                detector=API_KEY_PATTERN,
                strategy="redact",
                apply_to_input=True,
                apply_to_tool_results=True,  # this is the one that catches .env reads
            ),
            PIIMiddleware(
                "email",
                strategy="redact",
                apply_to_input=True,
                apply_to_tool_results=True,
            ),
            # 2. Hard resource limits - stops infinite loops and runaway sessions
            ModelCallLimitMiddleware(max_calls=30, on_limit="raise"),
            ToolCallLimitMiddleware(max_calls=60, on_limit="raise"),
            # 3. Approval gate - nothing gets written without your explicit sign-off
            HumanInTheLoopMiddleware(
                interrupt_on={
                    "write_file": True,
                    "read_file": False,
                    "list_directory": False,
                    "search_codebase": False,
                }
            ),
        ],
        checkpointer=checkpointer,  # required for interrupt/resume to work
    )

Wenn der Agent eine Datei schreiben möchte, verläuft der Austausch wie folgt:

Agent: I'd like to update src/config.py to fix the circular import.
       Here is what I plan to write:
--- src/config.py ---
       from typing import Optional
       from pydantic import BaseModel
       class Settings(BaseModel):
           debug: bool = False
           database_url: str = "sqlite:///app.db"
       ...
       Approve this change? (yes/no)
User: yes
Agent: Written. Moving to tests/test_config.py next.
The agent cannot touch anything silently. Every write surfaces for review before it happens. Secrets in any file the agent reads are replaced with [REDACTED_API_KEY] before reaching the model — which also means they never appear in the approval prompt you read. What you are reviewing is already clean.

Zwei einfache Erweiterungen sind erwähnenswert. Wenn die Codebasis Benutzerdaten enthält, beispielsweise Telefonnummern in Testkonfigurationen, fügen Sie eine PIIMiddleware("phone", strategy="redact")-Einstellung vor die für E-Mails ein. Jeder PII-Detektor wird unabhängig von derselben Eingabe ausgeführt, daher spielt die Reihenfolge innerhalb dieser Gruppe keine Rolle. Falls Sie lieber sanft abbrechen möchten anstatt abrupt zum Stillstand zu kommen, ändern Sie bei beiden Limit-Middlewares on_limit="raise" in on_limit="return"; der Agent gibt dann das vorhandene, unvollständige Ergebnis zurück.

Nun kann der Agent keine Geheimnisse an das Modell weitergeben, Dateien nicht ändern, es sei denn, Sie geben Ihre Zustimmung, und er darf den von Ihnen festgelegten Ressourcenrahmen nicht überschreiten. Es bleibt eine Einschränkung: Er kann während einer langen Sitzung nicht kohärent bleiben. Die Prüfung von Hunderten von Dateien mit Dutzenden von Tool-Aufrufen füllt das Kontextfenster bereits vor Abschluss, wodurch der Agent den Überblick über das Verhandelte verliert und seine Vorschläge abweichen. Das ist ein separates Problem vom hier gelösten, nämlich der Kontrolle darüber, was der Agent bearbeitet.