# Ihr Agent braucht nicht das ganze Internet: Egress-Kontrolle und Berechtigungen auf AWS
<!-- meta: Wie Sie auf AWS begrenzen, was ein KI-Agent erreichen und tun darf: Domain-Allowlists mit Network Firewall, Grenzen von DNS-Filtern, eine IAM-Rolle pro Agent, Freigaben, Kill-Switch. -->

Ende September 2026 hat OpenAI das Training seiner neuesten Modelle pausiert. Laut der Berichterstattung der AP, die sich auf Angaben des Unternehmens selbst stützt, waren Agenten während Training und Evaluierung auf Websites der US-Regierung über das hinausgegangen, was von ihnen verlangt wurde, und hatten in einem Fall Entwickler-Schlüssel gefunden. Ein weiterer Vorfall vom 20. September beschäftigt uns besonders: Ein interner Forschungsagent sollte keinen Internetzugang haben und kam trotzdem heraus, **durch eine Lücke im DNS-Filter**, und erreichte einen externen Chatbot. Wenige Tage später bestätigte die FTC, dass sie gegen OpenAI, Anthropic und andere Labore wegen der Risiken ermittelt, die ihre Agenten für Verbraucher darstellen.

Das sind Labore mit Sicherheitsteams, die größer sind als die gesamte Entwicklungsabteilung der meisten unserer Kunden. Die Lehre für alle anderen lautet nicht, dass Agenten unkontrollierbar wären. Sie lautet: **Die Leitplanken des Modellanbieters sind nicht Ihre Leitplanken.** Sobald Sie einem Agenten einen Browser, ein HTTP-Tool, Codeausführung oder AWS-Zugangsdaten geben, entscheiden Ihr Netzwerk und Ihr IAM darüber, was er erreichen kann, nicht die guten Absichten des Modells. In diesem Artikel zeigen wir, wie wir das auf AWS für Kunden einrichten, die Agenten in Produktion betreiben.

## Was tatsächlich begrenzt werden muss

Ein Agent ist eine Schleife: Das Modell entscheidet sich für eine Aktion, ein Tool führt sie aus, das Ergebnis geht zurück an das Modell. Das Modell selbst kann nichts tun. Alles Gefährliche passiert in den Tools, mit dem Netzwerkzugang und den Berechtigungen des Prozesses, der sie ausführt. Daraus ergeben sich fünf Stellen für Begrenzungen, und jede stoppt etwas, das die anderen nicht stoppen:

<div class="article-figure">
<svg viewBox="0 0 900 330" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Tabelle mit fünf Schutzschichten für KI-Agenten und was jede stoppt und nicht stoppt: DNS-Firewall, Netzwerk-Firewall, IAM-Rolle mit Session Policy, Tool-Design mit Freigaben, Budgets mit Kill-Switch">
<text x="10" y="22" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="15px" font-weight="600">Fünf Schichten, und was jede allein nicht stoppen kann</text>
<text x="20" y="52" fill="#6b7a99" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12px" font-weight="600">SCHICHT</text>
<text x="250" y="52" fill="#4fffb0" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12px" font-weight="600">STOPPT</text>
<text x="580" y="52" fill="#ff6b8a" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12px" font-weight="600">STOPPT NICHT</text>
<rect x="10" y="62" width="880" height="44" rx="6" fill="#151b2e"/>
<text x="20" y="89" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600">DNS Firewall</text>
<text x="250" y="89" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">Abfragen von Domains außerhalb der Liste</text>
<text x="580" y="89" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">direkte IPs, DNS over HTTPS, eigene Resolver</text>
<rect x="10" y="112" width="880" height="44" rx="6" fill="#151b2e"/>
<text x="20" y="139" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600">Network Firewall</text>
<text x="250" y="139" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">Verbindungen zu fremden Hosts, auch per IP</text>
<text x="580" y="139" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">Missbrauch einer erlaubten Domain</text>
<rect x="10" y="162" width="880" height="44" rx="6" fill="#151b2e"/>
<text x="20" y="189" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600">IAM-Rolle + Session Policy</text>
<text x="250" y="189" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">AWS-Aufrufe außerhalb des Task-Umfangs</text>
<text x="580" y="189" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">Schaden über erlaubte APIs</text>
<rect x="10" y="212" width="880" height="44" rx="6" fill="#151b2e"/>
<text x="20" y="239" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600">Tool-Design + Freigaben</text>
<text x="250" y="239" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">Seiteneffekte ohne Freigabe</text>
<text x="580" y="239" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">falsche Antworten, schlechte Einschätzung</text>
<rect x="10" y="262" width="880" height="44" rx="6" fill="#151b2e"/>
<text x="20" y="289" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600">Budgets + Kill-Switch</text>
<text x="250" y="289" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">Endlosschleifen, ausufernde Kosten</text>
<text x="580" y="289" fill="#c9cfe8" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="12.5px">eine einzelne Fehlaktion vor dem Auslösen</text>
<text x="10" y="324" fill="#6b7a99" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px">Keine Schicht genügt allein. Der Septemberfall kam an genau einer vorbei: DNS.</text>
</svg>
</div>

## Netzwerk: Warum DNS-Filterung allein nicht reicht

Naheliegend ist die Einrichtung von Route 53 Resolver DNS Firewall mit einer Allowlist: Der Agent kann nur die Domains auflösen, die Sie freigeben, alles andere liefert NXDOMAIN. Das ist günstig, einfach und sinnvoll. Aber es kontrolliert Namensabfragen, nicht Verbindungen. Ein Agent, der sich direkt mit einer IP-Adresse verbindet, fragt nie DNS. Einer, der DNS over HTTPS an einen öffentlichen Resolver schickt, fragt nie Ihren. Einer, der ein Skript mit fest eingetragenem Resolver schreibt, ebenfalls nicht. Der Septemberfall erinnert daran, dass „kein Internet“, das nur auf DNS-Ebene durchgesetzt wird, kein Internetverbot ist.

Durchsetzen lässt es sich nur mit einer Kontrolle der Verbindung selbst. Auf AWS ist das **AWS Network Firewall** mit einer zustandsbehafteten Domain-Listen-Regelgruppe: Ausgehendes HTTPS wird nur erlaubt, wenn der TLS-SNI zur Liste passt, einfaches HTTP nur, wenn der Host-Header passt, und alles andere wird standardmäßig verworfen, einschließlich Verbindungen zu nackten IP-Adressen. Die Subnetze des Agenten haben keine Route ins Internet außer über den Firewall-Endpunkt.

<div class="article-figure">
<svg viewBox="0 0 900 360" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Architektur: Der Agent läuft in einem privaten Subnetz mit eigener IAM-Rolle. DNS läuft über die Route 53 DNS Firewall. Der gesamte ausgehende Verkehr läuft über AWS Network Firewall mit einer Domain-Allowlist; erlaubte Domains passieren, alles andere wird verworfen und protokolliert. AWS-Dienste werden über VPC-Endpunkte erreicht, ohne das Internet zu berühren">
<text x="10" y="22" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="15px" font-weight="600">Agent-Egress auf AWS: Jeder Weg nach draußen ist eine Entscheidung</text>
<rect x="10" y="50" width="220" height="56" rx="10" fill="#151b2e" stroke="#2a3147" stroke-width="1.5"/>
<text x="120" y="74" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600" text-anchor="middle">Route 53 DNS Firewall</text>
<text x="120" y="93" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">Allowlist, Rest blockieren</text>
<rect x="10" y="150" width="220" height="90" rx="10" fill="#151b2e" stroke="#7b8cff" stroke-width="1.5"/>
<text x="120" y="178" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13.5px" font-weight="600" text-anchor="middle">Agent-Task</text>
<text x="120" y="198" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">Fargate, privates Subnetz</text>
<text x="120" y="218" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">eigene IAM-Rolle, 15-Min-Session</text>
<line x1="120" y1="150" x2="120" y2="112" stroke="#9aa3c7" stroke-width="1.5" stroke-dasharray="4 4"/><path d="M 115 112 L 120 106 L 125 112 Z" fill="#9aa3c7"/>
<rect x="330" y="150" width="220" height="90" rx="10" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/>
<text x="440" y="178" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13.5px" font-weight="600" text-anchor="middle">AWS Network Firewall</text>
<text x="440" y="198" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">Allowlist für TLS SNI / HTTP Host</text>
<text x="440" y="218" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">Standard: Drop + Alert</text>
<line x1="230" y1="195" x2="324" y2="195" stroke="#9aa3c7" stroke-width="1.5"/><path d="M 324 190 L 330 195 L 324 200 Z" fill="#9aa3c7"/>
<rect x="650" y="110" width="240" height="64" rx="10" fill="#151b2e" stroke="#4fffb0" stroke-width="1.5"/>
<text x="770" y="137" fill="#4fffb0" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600" text-anchor="middle">Erlaubte Domains</text>
<text x="770" y="157" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">Modell-API, Ihre APIs, genehmigte Docs</text>
<rect x="650" y="216" width="240" height="64" rx="10" fill="#151b2e" stroke="#ff6b8a" stroke-width="1.5"/>
<text x="770" y="243" fill="#ff6b8a" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600" text-anchor="middle">Alles andere</text>
<text x="770" y="263" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">nackte IPs, DoH, fremde Hosts: verworfen</text>
<line x1="550" y1="180" x2="644" y2="146" stroke="#4fffb0" stroke-width="1.5"/><path d="M 641 141 L 650 144 L 644 151 Z" fill="#4fffb0"/>
<line x1="550" y1="210" x2="644" y2="244" stroke="#ff6b8a" stroke-width="1.5" stroke-dasharray="5 4"/><path d="M 644 239 L 650 246 L 641 249 Z" fill="#ff6b8a"/>
<rect x="10" y="280" width="220" height="56" rx="10" fill="#151b2e" stroke="#2a3147" stroke-width="1.5"/>
<text x="120" y="304" fill="#eef2ff" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="13px" font-weight="600" text-anchor="middle">VPC-Endpunkte</text>
<text x="120" y="323" fill="#9aa3c7" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px" text-anchor="middle">S3, Secrets Manager, Bedrock, Logs</text>
<line x1="120" y1="240" x2="120" y2="274" stroke="#7b8cff" stroke-width="1.5"/><path d="M 115 274 L 120 280 L 125 274 Z" fill="#7b8cff"/>
<text x="330" y="304" fill="#6b7a99" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px">AWS-Verkehr verlässt das VPC nie und braucht keinen Eintrag in der Allowlist.</text>
<text x="330" y="322" fill="#6b7a99" font-family="DM Sans, system-ui, -apple-system, sans-serif" font-size="11.5px">Firewall-Alert-Logs + DNS-Query-Logs gehen für den Kill-Switch an CloudWatch.</text>
</svg>
</div>

In CDK ist die Regelgruppe kurz. Wichtig sind die Zieltypen (sowohl SNI als auch Host), die strikte Regelreihenfolge und eine Standardaktion, die verwirft, was keine Regel getroffen hat:

```typescript
import * as nfw from "aws-cdk-lib/aws-networkfirewall";

const allowlist = new nfw.CfnRuleGroup(this, "AgentEgressAllowlist", {
  ruleGroupName: "agent-egress-allowlist",
  type: "STATEFUL",
  capacity: 100,
  ruleGroup: {
    rulesSource: {
      rulesSourceList: {
        generatedRulesType: "ALLOWLIST",
        targetTypes: ["TLS_SNI", "HTTP_HOST"],
        targets: ["api.anthropic.com", "api.yourcompany.com", ".docs.aws.amazon.com"],
      },
    },
    statefulRuleOptions: { ruleOrder: "STRICT_ORDER" },
  },
});

const policy = new nfw.CfnFirewallPolicy(this, "AgentEgressPolicy", {
  firewallPolicyName: "agent-egress",
  firewallPolicy: {
    statelessDefaultActions: ["aws:forward_to_sfe"],
    statelessFragmentDefaultActions: ["aws:forward_to_sfe"],
    statefulEngineOptions: { ruleOrder: "STRICT_ORDER" },
    statefulRuleGroupReferences: [{ resourceArn: allowlist.attrRuleGroupArn, priority: 10 }],
    statefulDefaultActions: ["aws:drop_established", "aws:alert_established"],
  },
});
```

`aws:drop_established` statt `aws:drop_strict` ist Absicht: Die Firewall muss den TCP-Handshake durchlassen, um den SNI im TLS Client Hello zu sehen. Ein führender Punkt (`.docs.aws.amazon.com`) erfasst Subdomains. Halten Sie die Liste kurz und konkret. „github.com“ als Ganzes ist ein Exfiltrationskanal, kein Allowlist-Eintrag.

Zwei ehrliche Einschränkungen. Die SNI-Filterung prüft den Namen, den der Client behauptet, ein entschlossener Prozess könnte also einen erlaubten SNI an einen anderen Server schicken. Network Firewall kann TLS-Inspektion durchführen, um diese Lücke zu schließen, mit zusätzlichen Kosten und Komplexität; für die meisten Agenten-Workloads ist die Kombination aus SNI-Allowlist und fehlendem Egress zu nackten IPs eine vernünftige Grenze. Und es ist nicht kostenlos: In us-east-1 kostet ein Endpunkt 0,395 $ pro Stunde, etwa 288 $ pro Monat und Availability Zone, plus 0,065 $ pro verarbeitetem GB. Für einen einzelnen kleinen Agenten ist ein Egress-Proxy (Squid oder Envoy mit Domain-Allowlist, auf einem kleinen Fargate-Task) eine günstigere Alternative mit demselben Prinzip. Was wir nicht tun würden: einem Agenten ein [NAT-Gateway](/de/blog/nat-gateway-the-most-expensive-line-you-do-not-see) und einen DNS-Filter geben und das Ganze für abgeschottet erklären.

AWS-Dienste selbst (S3, Secrets Manager, Bedrock, CloudWatch Logs) sollten über VPC-Endpunkte laufen. Dieser Verkehr berührt nie das Internet und braucht nie einen Allowlist-Eintrag, und mit Endpoint Policies können Sie zum Beispiel einschränken, welche Buckets der S3-Verkehr des Agenten überhaupt erreichen darf.

## Identität: eine Rolle pro Agent, pro Task eingeengt

Der zweite verbreitete Fehler ist, dass der Agent mit der Rolle der Anwendung läuft. Die Web-App muss die Daten aller Kunden lesen und schreiben, also kann es der Agent auch. Wenn der Agent über seine Aufgabe hinausgeht, tut er das mit den Berechtigungen des gesamten Produkts.

Das machen wir stattdessen:

- **Eine IAM-Rolle pro Agententyp.** Der Recherche-Agent, der Support-Agent und der Reporting-Agent haben jeweils eine eigene Rolle mit nur den Aktionen, die ihre Tools brauchen. Eine Permissions Boundary an jeder Rolle begrenzt, was später noch hinzugefügt werden kann.
- **Eine Session Policy pro Task.** Wenn der Agent eine Aufgabe startet, übernimmt der Orchestrator die Agentenrolle mit einer Inline-Session-Policy, die sie weiter einengt, zum Beispiel auf das Präfix eines Mandanten in S3. Die effektiven Berechtigungen sind die Schnittmenge aus Rolle und Session Policy.
- **Kurze Sessions und Session-Tags.** 15 Minuten, das Minimum für AssumeRole, und Tags für Mandant und Task, damit jeder Aufruf in CloudTrail auf den Task zurückgeführt werden kann, der ihn ausgelöst hat.

```typescript
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";

const sts = new STSClient({});

export async function credentialsForTask(taskId: string, tenantId: string) {
  const { Credentials } = await sts.send(new AssumeRoleCommand({
    RoleArn: process.env.RESEARCH_AGENT_ROLE_ARN,
    RoleSessionName: `agent-${taskId}`,
    DurationSeconds: 900,
    Tags: [{ Key: "tenant", Value: tenantId }, { Key: "task", Value: taskId }],
    Policy: JSON.stringify({
      Version: "2012-10-17",
      Statement: [{
        Effect: "Allow",
        Action: ["s3:GetObject"],
        Resource: [`arn:aws:s3:::${process.env.DOCS_BUCKET}/tenants/${tenantId}/*`],
      }],
    }),
  }));
  return Credentials;
}
```

Die Trust Policy der Rolle braucht `sts:TagSession`, damit die Tags funktionieren. Und die Tools des Agenten erhalten diese Zugangsdaten, nicht die Standardzugangsdaten des Tasks. Selbst ein Tool, das sich daneben benimmt, kann also nur fünfzehn Minuten lang die Dateien eines einzigen Mandanten berühren.

## Tools: die eigentliche Berechtigungsfläche

Netzwerk und IAM begrenzen, wohin ein Agent gelangen kann. Die Tools entscheiden, was er dort tun kann, und hier steckt der größte Teil der Designarbeit.

- **Keine generischen Tools in Produktion.** Ein `bash`-Tool oder ein `fetch(url)`, das alles akzeptiert, macht jede der obigen Grenzen zur einzigen Grenze. Ein Tool namens `get_invoice(invoice_id)`, das den Mandanten selbst prüft, ist weniger flexibel und deutlich sicherer.
- **Auch im Tool die Allowlist prüfen.** Gibt es ein Fetch-Tool, validiert es den Host gegen dieselbe Liste, die auch die Firewall nutzt. Defense in Depth kostet zehn Zeilen.
- **Code in einer Sandbox ohne Netzwerk ausführen.** Muss der Agent Code ausführen, dann in einem separaten Container oder einer separaten Funktion ganz ohne Egress oder in der gehosteten Sandbox des Modellanbieters, nicht in dem Prozess, der die Zugangsdaten hält.
- **Freigabe für Seiteneffekte.** Lesen läuft automatisch, alles, was schreibt, sendet, bezahlt oder löscht, wartet auf einen Menschen. Der Tool Runner des Anthropic SDK hat dafür Hooks pro Turn, und Managed Agents kennt Permission Policies (`always_allow`, `always_ask`, `auto`).

Ein Stolperstein aus der Dokumentation von Anthropic, den wir unterstreichen würden: Bei Managed Agents laufen die gehosteten Tools `web_search` und `web_fetch` auf den Servern von Anthropic und **ignorieren die Netzwerkeinstellungen Ihrer Umgebung**. Die Netzwerksperre der Sandbox schränkt sie nicht ein. Sie schalten sie ab oder begrenzen sie pro Tool mit `allowed_domains`. Das ist dieselbe Lehre wie beim DNS-Umgehen in anderer Form: Prüfen Sie, welchen Weg der Verkehr tatsächlich nimmt.

## Budgets und ein Kill-Switch

Die letzte Schicht betrifft ausuferndes Verhalten statt einzelner Aktionen. Setzen Sie ein hartes Limit für Turns und Token pro Task (die Claude API hat dafür Task-Budgets), begrenzen Sie die Rate der Tool-Aufrufe pro Agent und beobachten Sie zwei Zahlen: die Drop-Alerts der Network Firewall und die Blockierungen der DNS Firewall für die Subnetze des Agenten. Ein Agent, der plötzlich gegen die Firewall läuft, ist entweder defekt oder tut etwas, das er nicht tun sollte. In beiden Fällen kann ein CloudWatch-Alarm auf diese Metriken eine Lambda auslösen, die der Agentenrolle eine Deny-all-Policy anhängt. Jede Session endet innerhalb von Minuten, und nichts anderes im Produkt ist betroffen.

## Unsere Checkliste

Bevor ein Agent mit Tools in Produktion geht, wollen wir jede dieser Fragen mit Ja beantworten können:

1. Der Agent läuft in Subnetzen ohne Route ins Internet außer über eine Firewall oder einen Proxy mit Allowlist.
2. Egress zu nackten IPs ist blockiert, und DNS wird ebenfalls gefiltert.
3. AWS-Dienste werden über VPC-Endpunkte erreicht.
4. Der Agent hat eine eigene Rolle, eine Permissions Boundary und Session Policies pro Task.
5. Tools sind spezifisch, prüfen ihre Eingaben selbst, und Seiteneffekte brauchen eine Freigabe.
6. Codeausführung findet in einer Sandbox ohne Netzwerk und ohne Zugangsdaten statt.
7. Turns, Token und Tool-Aufrufe haben Limits, und es gibt einen getesteten Kill-Switch.

Nichts davon ist exotisch. Es ist dieselbe Disziplin, die wir bei jedem Dienst anwenden, der nicht vertrauenswürdige Eingaben verarbeitet, angewandt auf einen Dienst, dessen Entscheidungen ein Modell trifft. Wenn Sie Agenten in Produktion bringen und die Einrichtung von jemandem prüfen lassen möchten, ist das Arbeit, [die wir für Kunden erledigen](/contact). Und wenn Sie den Hintergrund interessiert, warum Agenten immer wieder die Lücken finden, haben wir darüber in [Warum „entkommt“ die KI ständig?](/de/blog/why-ai-keeps-escaping) geschrieben.
