Files
ClawdDotNet/docs/Security-Konzept.md
T

473 lines
27 KiB
Markdown

# Security-Konzept: Einschließen, Regeln, Überwachen
> **Rolle:** Bauplan. Stand 2026-09-19, entstanden aus dem Orchestrator-Vergleich
> (OpenClaw, Grok Bot, Claude Code/Codex, OpenHands, LlamaFirewall, CaMeL) und der
> Abstimmung mit dem Nutzer. Reihenfolge und Status stehen im Deploymentcenter, nicht
> hier.
## 1 — Das Ziel in einem Satz
**Ein bösartiger Agent — auch einer, der durch eingeschleusten Text übernommen wurde —
darf seinen Workspace nicht verlassen und nichts Schädliches tun.** Das muss auch dann
gelten, wenn Agenten eigene Skripte schreiben und ausführen.
## 2 — Der Grundsatz: das Modell ist nie die Grenze
Ein Überwachungsagent kann einen Ausbruch nicht verhindern, nur hinterher bemerken. Und
er ist selbst angreifbar: Er liest genau die Verläufe, in denen der eingeschleuste Text
steht. Deshalb vier Schichten, nach Kosten sortiert — die unteren drei sind
deterministisch und kosten keine Tokens, erst die oberste fragt ein Modell:
| Schicht | Was | Kosten | Verhindert / erkennt |
|---|---|---|---|
| 0 Einschließen | Sandbox-Runner für jede Codeausführung | keine Tokens | verhindert |
| 1 Regeln vor dem Aufruf | PermissionGate, Staging, Herkunftsregel, Netzprofil | keine Tokens | verhindert |
| 2 Regelbasierte Überwachung | Detektoren über das Audit, Köderwerte | keine Tokens | erkennt sofort |
| 3 Security-Agent | Einschätzung auf Anlass, Skriptprüfung, Stichprobe | gedeckelt | erkennt, bewertet |
So machen es die Systeme, die tragen: Claude Code und Codex setzen die Grenze im
Betriebssystem (bubblewrap bzw. Landlock + seccomp) und prüfen mit einem Modell nur
zusätzlich. OpenClaw zeigt die Gegenseite — dort ist die Sandbox *optional*, und die
Vorfälle mit ClawHub kamen genau daher.
### Warum das jetzt dran ist
Heute laufen alle Agenten **in einem Prozess unter einem Linux-Benutzer**. Die
Workspace-Grenze ist C#-Code in den Werkzeugen (`PathBoundary`); #373 hat gezeigt, wie
leicht sie bricht. Das trägt, solange Agenten keinen eigenen Code ausführen. Ein
Python-Skript fragt `PathBoundary` nicht — `open('/home/...')` geht am Werkzeug vorbei.
Und der Hauptprozess hält den entsperrten Hauptschlüssel.
**Regel, nicht verhandelbar: Fremder Code läuft nie im Hauptprozess.** Kein Skript,
keine Shell, kein MCP-stdio-Server ohne den Runner aus Schicht 0.
### Das Bedrohungsmodell wird um eine Klasse erweitert
Bisher: rein interner Betrieb; Schutz gegen Ordner-Kopieren und gegen Schadsoftware, die
nach Zugangsdaten sucht. Das bleibt. Neu: **Der Angreifer ist Inhalt, keine Person** —
eine Webseite, eine Mail, eine Chatnachricht, ein Tool-Ergebnis, das einen Agenten
umsteuert. Interner Betrieb schützt davor nicht, weil die Agenten Fremdinhalt lesen,
sobald sie ihre Arbeit tun.
## 3 — Schicht 0: Sandbox-Runner
Ein gemeinsamer Runner für alles, was Code ausführt: Shell (#196), freigegebene Skripte
(Abschnitt 7), MCP-stdio-Server (#197) und die heutigen `yt-dlp`/`ffmpeg`-Aufrufe des
`SocialMediaManagerTool`.
Garantien, jede mit einem Test, der den Ausbruch versucht:
- **Dateisystem:** Nur der Workspace des Agenten ist beschreibbar und sichtbar; System
schreibgeschützt, `$HOME`, Instanzverzeichnis, Schlüsselbund und die Workspaces anderer
Agenten sind nicht vorhanden (eigener Mount-Namespace, nicht bloß gesperrt).
- **Erbe:** Keine Umgebungsvariablen, keine offenen Dateideskriptoren, keine Secrets des
Hauptprozesses. Was ein Skript braucht, bekommt es ausdrücklich übergeben.
- **Netz:** Standardmäßig kein Netz (eigener Netz-Namespace). Freigabe nur über den
Egress-Proxy, siehe Abschnitt 5.
- **Grenzen:** Laufzeit, Speicher, Prozesszahl, Ausgabegröße; seccomp-Filter; kein
Rechteerwerb (`no_new_privs`).
- **Umsetzung:** `bwrap` unter Linux. Ein eigener Linux-Benutzer je Agent ist die
spätere Stufe, falls die Namespace-Trennung einmal nicht reicht.
- **Nichts, was der Hauptprozess liest, ist in der Sandbox beschreibbar.** Task-Dateien,
Skills, Identity/Soul, Skript-Manifeste, Gedächtnis-Importe und jede Konfiguration
liegen außerhalb des beschreibbaren Bereichs oder sind dort schreibgeschützt
eingebunden. Das ist die Lehre aus CVE-2026-25725 bei Claude Code: Code in der Sandbox
legte Hooks an, die beim nächsten Start mit vollen Rechten liefen. Eine Sandbox, die
Dateien schreiben darf, die draußen ausgeführt oder als Anweisung gelesen werden, ist
keine Grenze, sondern ein verzögerter Ausbruch. Ein Test gehört dazu: Die Sandbox
versucht jede dieser Dateien zu schreiben.
Unter Windows gibt es keinen Runner und damit **keine Codeausführung** — die Funktion
fehlt dort, statt schwächer zu sein.
*Gebaut am 2026-09-25 (#654), geprüft unter Ubuntu 26.04 mit bwrap 0.11.1 auf der
Test-VM.* Was beim Bau dazukam und in keinem Handbuch steht:
- **Die Wurzel der Sandbox ist beschreibbar**, wenn man nichts tut: bwrap baut sie als
eigenes tmpfs. Erst `--remount-ro /` nach allen Einbindungen macht daraus „nur `/work`
und `/tmp`“. Der Ausbruchstest hatte unter `/etc` eine Datei angelegt.
- **Der seccomp-Deskriptor wird weitergereicht.** bwrap liest den Filter über fd 3 und
lässt ihn im Programm offen; die innere Shell schließt ihn vor dem Start.
- **Die Prozessgrenze** (RLIMIT_NPROC) wird *innerhalb* des eigenen User-Namensraums
gesetzt. Dort zählt sie nur die Prozesse der Sandbox, nicht die Threads der Instanz
(auf der VM über tausend unter demselben Benutzer). Das ist mit einem Gegenfall
belegt: 5 von 10 gehen, 12 nicht. dash kennt dafür `ulimit -p`, bash `ulimit -u`.
- **`--die-with-parent` hängt am Thread, nicht am Prozess.** Das Elternsterbe-Signal
des Kernels feuert, wenn der *Thread* endet, der den Prozess gestartet hat. .NET
startet aus einem Threadpool-Thread, und unter Last baut der Threadpool Threads ab.
Folge auf der VM: bwrap bekam mitten im Lauf SIGKILL, und starb er noch im Aufbau,
lebte das Innere als Waise weiter. Alle Sandboxen starten deshalb aus einem eigenen,
dauerhaften Thread. Zum Beenden wird der ganze Baum unter bwrap aus `/proc`
eingesammelt und getötet, samt PID 1 des Namensraums, dessen Tod den Rest mitnimmt.
- **Erster Nutzer ist ffmpeg** im SocialMediaManager, weil es heruntergeladene Medien
zerlegt. yt-dlp braucht Netz und kommt mit dem Egress-Proxy (#655) in die Sandbox; bis
dahin läuft es wie bisher und das Protokoll sagt es.
## 4 — Schicht 1: Regeln vor dem Aufruf
Vorhanden: PermissionGate (Werkzeug je Agent), Staging mit Plan-Freeze, `PathBoundary`,
`UrlGuard`, `SqlGuard`. Neu:
### 4.1 Herkunftsregel
Vorbild CaMeL, in der einfachsten Form. Sobald ein Lauf **Fremdinhalt** gelesen hat —
WebFetch, Mail, Chat, eine Datei, die nicht der Agent selbst geschrieben hat, das
Ergebnis eines MCP-Servers —, gilt der Lauf als *belastet*. Ab dann ist jede Aktion mit
**Außenwirkung** freigabepflichtig, egal was die Policy sonst sagt: Mail senden, posten,
FTP-Upload, Schreiben in die Datenbank, Nachricht an andere Agenten, Task an andere
Agenten.
Das kostet nichts und nimmt einer übernommenen Anweisung genau die Wirkung, um die es
geht: Sie kann lesen und vorschlagen, aber nicht handeln. Welche Werkzeuge Fremdinhalt
liefern und welche Außenwirkung haben, erklärt jedes Werkzeug selbst (wie
`StagingAktion` seit #609). Die Freigabe-Nachricht (#644) nennt die Quelle, die den Lauf
belastet hat.
**Freigabemüdigkeit ist der eigentliche Gegner dieser Regel.** Der Sicherheitsvergleich
vom 2026-09-19 hat sie als realistischstes Einfallstor ausgemacht: Fast jeder Agent liest
Fremdinhalt, fast jeder Lauf wird belastet, und wer 40 Freigaben am Tag abnickt, ist
selbst der Umgehungsweg. Deshalb gehört zur Regel:
- **Feste Ziele sind ausgenommen.** Außenwirkung an Ziele, die ein Mensch vorab
eingerichtet hat — die eigenen Kanäle, `@human`, fest hinterlegte Empfänger je Agent —
bleibt ohne Freigabe. Eine Injection kann dorthin schreiben, aber niemanden erreichen,
den der Betreiber nicht selbst ausgewählt hat.
- **Muster-Freigabe auf Zeit.** Ein wiederkehrender Aufruf (gleicher Agent, gleiches
Werkzeug, gleiches Ziel) kann für eine begrenzte Frist freigegeben werden, statt jeden
Einzelfall zu bestätigen. Nie für hohe Risiken, nie ohne Ablauf.
- **Die Freigabequote wird sichtbar** (Abschnitt 10). Wer 100 % durchwinkt, hat keine
Kontrolle mehr, sondern eine Verzögerung — das soll man sehen, nicht vermuten.
- **Die Vorschau zeigt Herkunft.** Argumente, die Daten aus sensiblen Quellen tragen
(Gedächtnis, Datenbank, Dateien außerhalb von `out/`), werden in der Freigabe
hervorgehoben — sonst geht ein Abfluss, versteckt in einem langen Argument, durch die
Freigabe hindurch.
### 4.2 Schutz vor Amoklauf
Rate-Limits je Werkzeug und Agent, Circuit-Breaker bei gehäuften Fehlern oder
Ablehnungen — der Übergang zur Quarantäne (Abschnitt 6). Aus dem
[Erweiterungskonzept](Orchestrator-Erweiterungskonzept-2026.md).
### 4.3 Belastung wandert mit
Die Lücke, die der Vergleich aufgedeckt hat und die kein anderes Konzept schließt: Ein
belasteter Lauf schreibt einen Gedächtniseintrag, einen Skill-Entwurf oder einen Task.
Ein *späterer, unbelasteter* Lauf lädt ihn — und die eingeschleuste Anweisung wirkt
jetzt ohne Herkunftsmarke. Die Herkunftsregel wäre über einen Umweg ausgehebelt.
Deshalb: **Was ein belasteter Lauf hinterlässt, trägt die Marke weiter** — Gedächtnis,
Skill-Entwürfe, Tasks und Task-Kommentare, Dateien im SharedWorkspace. Die Engine
stempelt sie, nicht der Agent. **Wer einen markierten Eintrag lädt, wird selbst
belastet.** Ein Mensch kann einen Eintrag nach Prüfung entlasten; das steht im Audit.
Skill-Entwürfe aus belasteten Läufen sind in der Freigabe (#198) ausdrücklich markiert.
## 5 — Netzprofil je Agent
Ein Agent hat **drei getrennte Wege ins Netz**. Sie werden oft in einen Topf geworfen,
sind aber verschieden gefährlich und brauchen eigene Schalter.
| Weg | Wer ruft auf | Wer wählt das Ziel | Heute | Einstellung |
|---|---|---|---|---|
| **Modell** | die Engine, zu OpenRouter | niemand — immer OpenRouter | fest | Modell-Netzprofil (5.1) |
| **Werkzeuge** | der Hauptprozess (WebFetch, Mail, MCP …) | der Agent, im Rahmen der Allowlist | WebFetch mit Domain-Allowlist, muss je Agent freigegeben werden | bleibt je Werkzeug |
| **Sandbox** | ein Skript oder Shell-Befehl | der Code | gibt es noch nicht | aus; Freigabe je Skript (5.2) |
Der Weg zu OpenRouter ist **kein** Internetzugang des Agenten: Die Engine spricht mit
genau einem Ziel, der Agent kann weder Adresse noch Inhalt der Verbindung bestimmen. Er
wird aber dann zum Internetzugang, wenn das *Modell selbst* suchen darf.
### 5.1 Modell-Netzprofil (je Agent, in den Modelleinstellungen)
- **Websuche des Modells:** OpenRouter kann dem Modell eine eigene Websuche geben (Plugin
`web` oder das Suffix `:online` am Modellnamen). Das ist ein Netzweg, an dem weder
Staging noch Audit vorbeikommen. **Voreinstellung aus**; ein Modellname mit `:online`
wird abgelehnt, solange der Schalter aus ist.
- **Datennutzung beim Anbieter:** `data_collection: deny` als Voreinstellung (das Feld
ist in `ProviderPreferences` schon vorhanden, wird aber noch nicht gesetzt); optional
nur Anbieter ohne Datenspeicherung.
- **Anbieter-Allowlist und Ausweichen:** Welche Anbieter ein Agent nutzen darf, ob auf
andere ausgewichen werden darf (`allow_fallbacks`).
- `require_parameters` bleibt an (#550).
### 5.2 Sandbox-Netz
Standard: kein Netz. Braucht ein Skript Netz, **deklariert es die Ziele in seinem
Manifest** (Abschnitt 7), und die Freigabe des Skripts umfasst diese Ziele. Durchgesetzt
wird es vom Egress-Proxy: nur die deklarierten Domains, nur HTTPS, jeder Abruf im Audit.
Ein Skript, das etwas anderes erreichen will, scheitert und erzeugt einen Befund.
*Der Egress-Proxy ist gebaut (2026-09-25, mit #712), die Anbindung an Skript-Manifeste
noch nicht (#655, #695).* So umgesetzt:
- **Die Sandbox teilt nie das Netz des Hosts.** Mit geteiltem Netz sähe sie alles, was der
Host auf Loopback anbietet, darunter den Port der Weboberfläche und eine Datenbank. Sie
sähe außerdem abstrakte Unix-Sockets wie ein X11-Display, denn die gehören zum
Netz-Namensraum, nicht zum Dateisystem. Die Sandbox behält ihr Netz mit nur Loopback. Ein
Starter in der Sandbox (die Anwendung mit `--sandbox-launch`) lauscht dort auf
`127.0.0.1:3128` und reicht jede Verbindung an einen Unix-Socket weiter. Der Socket ist
eingebunden, dahinter steht der Proxy im Hauptprozess.
- **Nur `CONNECT host:port`**, also ein TLS-Tunnel. Klartext-HTTP wird abgewiesen. Der Host
muss auf der Liste stehen (`host`, `host:port`, `*.domain` nur für Subdomains).
Adressliterale stehen nie auf der Liste.
- **Namen löst der Proxy auf, nicht die Sandbox.** Löst ein gelisteter Name auch nur
teilweise auf eine nicht-öffentliche Adresse auf, wird er ganz abgewiesen. Das betrifft
Loopback, private Netze, CGNAT, Link-Local (Metadaten-Dienste), Multicast und
IPv6-Hüllen wie 6to4 und NAT64. So kommt kein DNS-Rebinding auf die Instanz.
- Jede Verbindung und jede Abweisung steht im Log. Im Audit steht sie noch nicht, das kommt
mit den Manifesten.
Geprüft unter Linux in der echten Sandbox (`SandboxLaunchTests`): Ein gelisteter Host
antwortet, ein anderer bekommt 403, `--noproxy` findet kein Netz, und ein Dienst auf dem
Loopback des Hosts ist nicht erreichbar.
## 6 — Befunde und Quarantäne
### 6.1 Zwei Stufen
| Stufe | Beispiele | Reaktion | Aufheben |
|---|---|---|---|
| **Niedrig** | gehäufte Ablehnungen, Kostensprung, neue Zieldomain, Änderung an Identity/Soul | Nachricht über den Kanal (#644) mit Quarantäne-**Vorschlag** — per Button bestätigen oder verwerfen | im Messenger oder in der Oberfläche |
| **Hoch** | Köderwert in einem ausgehenden Aufruf, Ausbruchsversuch aus der Sandbox, Zugriff auf fremde Workspaces, Skript weicht vom freigegebenen Hash ab | **sofortige Quarantäne**, Nachricht über den Kanal **ohne** Schaltfläche | **nur in der Oberfläche**, nach Prüfung |
Warum hoch nicht per Messenger: Der Messenger ist ein Kanal, über den auch Fremdinhalt
kommt, und ein Telefon geht leichter verloren als der Zugang zur Oberfläche. Wer einen
Agenten nach einem Köder-Treffer wieder freilässt, soll die Belege vor sich haben.
### 6.2 Was Quarantäne heißt
- Der Agent nimmt keine neuen Läufe an (Scanner überspringt ihn, Chat wird abgewiesen).
- Ein laufender Lauf endet beim nächsten Schritt; Sandbox-Prozesse des Agenten werden
beendet.
- Alle ausgehenden Nachrichten des Agenten werden zurückgehalten.
- Seine offenen Freigabe-Vorschläge werden gesperrt, nicht gelöscht.
- Alles ist umkehrbar und steht im Audit. Nichts wird gelöscht.
### 6.3 Ausnahmen für bekannte Befunde
Damit dieselbe Meldung nicht jeden Tag wiederkommt:
- Jeder Befund hat einen **Fingerabdruck** aus Detektor, Agent, Werkzeug und normalisiertem
Ziel. Gleiche Fingerabdrücke innerhalb eines Zeitfensters werden zu einem Befund mit
Zähler zusammengefasst, statt neu gemeldet zu werden.
- Eine **Ausnahme** gilt für einen Fingerabdruck, trägt Begründung, Urheber und ein
**Ablaufdatum** und steht im Audit. Ausnahmen ohne Ablauf gibt es nicht.
- Ausnahmen für niedrige Befunde gehen auch im Messenger („nicht mehr melden, 30 Tage"),
für hohe nur in der Oberfläche.
- **Nie ausnehmbar:** Köder-Treffer und Sandbox-Ausbruchsversuche.
## 7 — Skripte, die Agenten selbst schreiben
Vorgabe: Freigegebene Agenten dürfen eigene Skripte schreiben. Ausgeführt wird nur, was
geprüft ist; **jede Änderung, auch ein Bugfix, braucht eine neue Prüfung.**
- **Wer darf:** nur Agenten mit der Fähigkeit „Skripte schreiben". Standard aus.
- **Form:** Das Skript liegt im Workspace unter `scripts/`, daneben ein Manifest: Zweck,
Interpreter, Eingaben, Ausgaben, Netzziele (5.2), Laufzeitgrenze.
- **Bindung:** Die Freigabe gilt für den SHA-256 von Skript **und** Manifest **und** die
Interpreter-Fassung — nach dem Vorbild von OpenClaws Exec-Freigaben, die an Befehl,
Arbeitsverzeichnis und Datei-Hashes gebunden sind. Ändert sich ein Byte, passt der Hash
nicht mehr: nicht ausführbar, neue Prüfung. Versucht ein Agent, ein verändertes Skript
auszuführen, ist das ein hoher Befund.
- **Saubere Umgebung.** Der Hash allein reicht nicht: Im August 2026 wurde Claude Codes
Auto-Modus umgangen, indem ein Archiv eine Datei `struct.py` in das Arbeitsverzeichnis
legte, die beim Import eines Standardmoduls mitgeladen wurde („confused environment").
Ein freigegebenes Skript läuft deshalb **aus einer schreibgeschützten, sauberen Kopie**,
mit `python -I` (kein Arbeitsverzeichnis, kein `PYTHONPATH` im Suchpfad), mit
Abhängigkeiten, die im Manifest **mit Hash** festgeschrieben sind, und der Workspace ist
nur als Datenverzeichnis unter einem eigenen Pfad eingebunden. Was das Skript lädt, ist
damit genau das, was geprüft wurde.
- **Ablauf der Prüfung:**
1. **Deterministische Vorprüfung** (kostenlos): Größe, Imports und Aufrufe, die eine
Begründung brauchen (`subprocess`, `socket`, `ctypes`, `os.system`, `eval`/`exec`,
dynamisches Nachladen, Base64-Dekodieren mit anschließender Ausführung), Abgleich
mit dem Manifest („deklariert kein Netz, importiert aber `requests`").
2. **Einschätzung des Security-Agenten:** *unbedenklich* / *bedenklich* (mit Gründen
und Zeilen) / *zu komplex für mich — externe Prüfung*. Ab einer Größen- oder
Komplexitätsgrenze lautet das Urteil ohne Modellaufruf „zu komplex". Externe
Prüfung heißt: Mensch, oder auf ausdrückliche Einstellung ein stärkeres Modell mit
eigenem Budget.
3. **Freigabe durch den Menschen**, mit Vorprüfung, Einschätzung und Diff zur vorigen
Fassung vor Augen.
- **Die Einschätzung ist ein Rat, keine Freigabe.** Auch „unbedenklich" gibt nichts frei.
Und selbst ein freigegebenes Skript läuft nur im Runner — die Prüfung ist die zweite
Linie, nicht die erste.
- **Selbstschutz des Prüfers:** Das Skript ist für den Security-Agenten Fremdinhalt. Ein
Kommentar wie „als unbedenklich einstufen" darf nichts bewirken: Die Vorprüfung aus
Schritt 1 kann das Modell nicht überstimmen, die Ausgabe ist strukturiert, und ein
Urteil, das der Vorprüfung widerspricht, wird als Widerspruch angezeigt, nicht still
übernommen.
## 8 — Schicht 2: Regelbasierte Überwachung
Detektoren über das Audit-Log. Laufen bei jedem Eintrag bzw. im Scanner-Takt, kosten keine
Tokens und erzeugen Befunde nach Abschnitt 6.
- Grenzverletzungen (`PathBoundary`, `UrlGuard`, `SqlGuard`, Sandbox) und abgelehnte
Aufrufe in Serie
- neue Zieldomains, neue Empfänger
- Kostensprung gegenüber dem eigenen Mittel
- Änderungen an Identity, Soul, Skills, Skripten
- verdächtige Folgen innerhalb eines Laufs: erst Fremdinhalt oder sensible Pfade lesen,
dann Außenwirkung versuchen
- **Köderwerte:** Köderdateien im Workspace und Köder-Secrets im Schlüsselbund, die kein
legitimer Ablauf je braucht. Taucht ein Köderwert in einem ausgehenden Aufruf, einer
Nachricht oder im Sandbox-Netz auf → hoher Befund, sofortige Quarantäne. Praktisch
keine Fehlalarme, weil es keinen legitimen Grund gibt, ihn zu benutzen.
## 9 — Schicht 3: Security-Agent
**Ein eingeschränktes Profil des AgentInspector (#204), kein zweites System.** Gleiche
Datenbasis (Audit und Receipts, von der Engine gestempelt), gleicher Zweck.
- **Wann er läuft:** auf Anlass aus Schicht 2 (Befund bewerten, Zusammenhänge suchen),
bei jeder Skriptprüfung (Abschnitt 7) und einmal täglich als Stichprobe mit kurzer
Lagezusammenfassung. Nie dauerhaft mitlaufend.
- **Was er sieht:** strukturierte Audit-Daten — Werkzeug, Ausgang, Pfade, Ziele, Kosten.
Rohe Fremdtexte nur gekürzt und als Daten gerahmt (#193).
- **Was er darf — hart im Code, nicht im Prompt:** Befunde anlegen und einstufen, über
den Kanal melden, Quarantäne vorschlagen (niedrig) oder verhängen (hoch), Skripte
einschätzen. **Nie:** freigeben, Quarantäne aufheben, Ausnahmen anlegen, Regeln oder
Einstellungen ändern, Werkzeuge mit Außenwirkung benutzen.
- **Kosten:** günstiges Modell, eigenes Tagesbudget im Budget-Guard mit hartem Deckel.
Ist der Deckel erreicht, laufen Schicht 0 bis 2 unverändert weiter, nur Einschätzungen
bleiben aus — und das wird im Sicherheitsbereich angezeigt. Planbar wird es durch den
Deckel und dadurch, dass Schicht 2 vorfiltert.
## 10 — Sicherheitsbereich in Oberfläche und WebUI
Eigener Menüpunkt auf Instanzebene, in Avalonia und in der Weboberfläche gleich
aufgebaut:
- **Freigabequote** je Agent und Werkzeug: wie viel wird freigegeben, wie schnell, wie
oft abgelehnt. Eine Quote nahe 100 % bei hoher Zahl ist selbst ein Befund.
- **Lage:** Sandbox verfügbar, Schlüsselbund dicht, Agenten in Quarantäne, offene
Befunde, offene Skriptprüfungen, Kanalzustand (#645), Budget des Security-Agenten.
- **Befunde:** Liste mit Stufe, Fingerabdruck, Zähler, Beleg aus dem Audit; Quarantäne
aufheben (hoch: nur hier), Ausnahme anlegen.
- **Skripte:** ausstehende Prüfungen mit Vorprüfung, Einschätzung und Diff; freigegebene
Skripte mit Hash und Freigabe-Datum.
- **Richtlinien je Agent:** Netzprofil (Abschnitt 5), Herkunftsregel, Skript-Fähigkeit,
Sandbox-Grenzen.
- **Ausnahmen:** mit Ablauf, Urheber, Begründung.
- **Selbstprüfung auf Drift:** „Sandbox konfiguriert, aber nicht verfügbar", „Modell mit
`:online` bei ausgeschalteter Websuche", „Agent darf Skripte schreiben, Runner fehlt".
Vorbild `openclaw security audit`; die Prüfungen gehören zu den Bausteinen von #648.
Zwei Regeln:
- **Agenten kommen an diesen Bereich nicht heran** — auch AgentEditor und ein
Personalmanager-Agent nicht. Seine Einstellungen liegen außerhalb dessen, was ein
Werkzeug schreiben kann.
- **Wer eine Regel lockert, bestätigt ausdrücklich**, und die Änderung steht im Audit.
In der Weboberfläche verlangt das eine erneute Anmeldung.
## 11 — Zugangsdaten: der Agent sieht sie nie
*Ergänzt am 2026-09-22 nach einem Vorschlag des Nutzers.*
Heute bekommt ein Werkzeug seine Zugangsdaten aus der Konfiguration des Agenten. Wo ein
Agent eine fremde API bedienen soll, steht der Schlüssel in Reichweite dessen, was in
seinen Kontext geraten kann.
**Der Vorschlag: eine Zugangsdatenverwaltung in ClawdDotNet.** Der Agent bekennt nie
einen Wert zu sehen, sondern schreibt einen Platzhalter; ClawdDotNet setzt ihn im
Werkzeug ein, kurz bevor der Aufruf hinausgeht.
```
Agent: DirectAPI, Header: { "Authorization": "Bearer {{secret:amazon-sp-api}}" }
ClawdDotNet: ersetzt beim Aufruf, protokolliert die Benutzung, nie den Wert
Antwort: wird auf dem Rückweg maskiert, bevor sie in den Kontext geht
```
Das ist der richtige Ansatz, und er löst ein echtes Problem. Er löst aber nicht so viel,
wie er auf den ersten Blick verspricht — und die Lücken dazwischen sind die eigentliche
Arbeit.
### Wo es klemmt
**1. Ein Platzhalter darf nicht überall ersetzt werden.**
Ein stumpfes Suchen-und-Ersetzen über alle Argumente wäre die größte Falle: Dann schreibt
ein Agent `{{secret:amazon-sp-api}}` in einen Dateiinhalt, in ein SQL-Statement, in eine
Nachricht oder in eine URL — und der Wert landet dort, wo ihn jeder lesen kann. Die
Ersetzung gehört an **benannte Stellen je Werkzeug** (etwa `DirectAPI.headers`,
`Database.connection`), nie an beliebige Zeichenketten.
**2. Der Platzhalter schützt den Schlüssel, nicht das Ziel.**
Auch mit (1): Ein Agent, der den Schlüssel im Kopf `Authorization` verwenden darf, kann
die Anfrage an eine beliebige Adresse richten — und schickt das Geheimnis dorthin. Die
Verwaltung muss deshalb **jedes Geheimnis an ein Ziel binden**: „dieser Schlüssel gilt nur
gegen `sellingpartnerapi.amazon.com`". Ohne diese Bindung gibt der Platzhalter ein
falsches Gefühl von Sicherheit. Das Netzprofil je Agent (Abschnitt 5) ist die Hälfte
davon; die andere gehört an den Eintrag selbst.
**3. Die Antwort kann das Geheimnis enthalten.**
Manche Dienste spiegeln Token in Fehlermeldungen zurück, manche Bibliotheken schreiben
die Verbindungszeichenfolge in die Ausnahme (`Password=…`). Dann steht der Wert im
Werkzeugergebnis, im Kontext und im Transkript. **Die Maskierung auf dem Rückweg ist
keine Zugabe, sondern die zweite Hälfte des Mechanismus** — der allgemeine Fall ist #192,
der Anfang steht in #660.
**4. Was gilt als Verwendung durch einen Fremden?**
Ein Agent, der nie den Wert sieht, kann ihn nicht verraten — aber er kann ihn *benutzen*,
und dazu lässt sich ein Agent überreden. Der Platzhalter schützt gegen **Diebstahl der
Zugangsdaten**, nicht gegen **Missbrauch der Befugnis**. Dagegen helfen die Zielbindung,
die Freigaberegeln und die Herkunftsregel (#651) — nicht der Platzhalter.
**5. Die Verwaltung erbt die Schwäche des Hauptschlüssels.**
Eine Zugangsdatenverwaltung ist ein Tresor. Solange **#190** offen ist — der Hauptschlüssel
liegt unter Linux im Klartext —, legt man damit mehr Eier in einen Korb, von dem man
weiß, dass er offen steht. Der Tresor macht den Betrieb ordentlicher, die Sicherheit
verbessert er erst nach #190. Reihenfolge entsprechend.
**6. Nicht verraten, was es gibt.**
Fragt ein Agent nach `{{secret:buchhaltung-bank}}`, das ihm nicht zugeteilt ist, muss das
Werkzeug abweisen — **ohne zu sagen, ob dieses Geheimnis existiert**. Dieselbe Überlegung
wie beim Hinweis auf den anderen Arbeitsbereich (#662).
**7. Zuteilung ist eine Rechteänderung.**
Wer welches Geheimnis benutzen darf, entscheidet ein Mensch, nie ein Agent — auch keine
Abteilungsleitung. Die Zuteilung gehört an die Einheit (siehe Organisation-Konzept,
#683/#692), damit sechs Einkäufer sie einmal bekommen und die Buchhaltung den
Marktplatzschlüssel nicht sieht.
### Was der Tresor dafür zurückgibt
- **Eine Stelle zum Wechseln.** Ein rotierter Schlüssel wird einmal geändert, alle Agenten
arbeiten weiter.
- **Benutzungsspuren.** Wer hat wann welches Geheimnis gegen welches Ziel verwendet —
im Audit, ohne den Wert. Ein nie benutzter Eintrag kann weg; ein plötzlich viel
benutzter ist ein Befund.
- **Ein sauberer Weg für Skripte.** Ein freigegebenes Skript (#657) bekommt sein
Geheimnis als Umgebungsvariable vom Läufer, deklariert im Manifest — nie im Quelltext,
nie im Aufrufprotokoll (#696).
### Urteil
Bauen, ja — aber mit Zielbindung und Rückweg-Maskierung von Anfang an, und nach #190.
Ein Platzhalter allein verschiebt das Geheimnis nur von der Agentenkonfiguration in die
Werkzeugkonfiguration.
## 12 — Reihenfolge und Items
| Item | Vorhaben | Abschnitt |
|---|---|---|
| #651 | Herkunftsregel, mit Ausnahmen für feste Ziele, Muster-Freigabe und Herkunft in der Vorschau | 4.1 |
| #659 | Belastung wandert mit (Gedächtnis-Vergiftung) | 4.3 |
| #652 | Befunde und Quarantäne mit zwei Stufen, Ausnahmen | 6 |
| #653 | Regelbasierte Detektoren und Köderwerte | 8 |
| #654 | Sandbox-Runner | 3 |
| #655 | Netzprofil je Agent | 5 |
| #656 | Sicherheitsbereich in Avalonia und Weboberfläche | 10 |
| #657 | Skriptprüfung | 7 |
| #658 | Security-Agent (Profil von #204) | 9 |
1. **#651 mit #659, #652, #653** — größter Nutzen je Aufwand, braucht nur das Vorhandene und die
Messenger-Freigabe (#644).
2. **#654** — harte Vorbedingung für Skripte, Shell (#196) und MCP-stdio-Server (#197);
steht deshalb vor beiden.
3. **#655** und **#656**.
4. **#657** — mit #198, weil Skripte mit den Skills kommen.
5. **#658** — zuletzt, auf #204 aufbauend. Bis dahin prüft bei Skripten der Mensch
allein, mit der deterministischen Vorprüfung.