Aus Richards Ideensammlung, nach Sichtung des vorhandenen Codes: - FileRW-Papierkorb + Cleanup-Job: delete verschiebt nach .trash statt endgueltig zu loeschen, Aufraeumen ueber IToolJobProvider/ToolJobScheduler. Macht Loeschen reversibel und erlaubt damit, FileRW.delete im Staging von Approve auf Auto herunterzustufen. - AgentInspector: lesende Aufsicht ueber andere Agenten. Beleg-basiert (Audit/Receipts/Taskboard) statt datei-basiert; harte Allowlist, damit AgentSettings.json mit den API-Keys nicht in einen LLM-Kontext geraet. - AgentEditor-Haertung: Identity/Soul-Aenderungen ueber die vorhandene StagingPolicy freigabepflichtig machen, Selbstbearbeitung sperren, Audit + restore ergaenzen. Das Tool selbst existiert bereits. - WebSearch: Suche als eigenes Tool, Lesen bleibt bei WebFetch hinter der Domain-Whitelist. Kein agent-reach (Klartext-Cookies, Fremdprozess). Trennung Rechercheagent / handelnder Agent gegen Prompt Injection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.9 KiB
Umsetzungsplan: FileRW-Papierkorb + Cleanup-Job
Stand: 2026-08-05 Ziel:
FileRW.deletelöscht nicht mehr endgültig, sondern verschiebt in einen Papierkorb je Workspace. Ein Cron-Job räumt den Papierkorb nach X Tagen auf. Reihenfolge: Zuerst umsetzen — kleinster Eingriff, entschärft ein reales Risiko und ist Voraussetzung dafür,FileRW.deleteim Staging vonApproveaufAutoherunterzustufen (siehe Abschnitt 6).
1. Ausgangslage
HandleDeleteAsync in src/ClawdDotNet.Tools.FileRW/FileRWTool.cs:318 löscht
sofort und endgültig:
if (File.Exists(path)) { File.Delete(path); ... }
else if (Directory.Exists(path)) { Directory.Delete(path, true); ... }
Ein einziger Tool-Call mit path: "." räumt damit den kompletten Workspace
eines Agenten ab — ohne Rückweg. Gleichzeitig fehlt dem Agenten jede Möglichkeit,
gefahrlos aufzuräumen: Jede Aufräumaktion ist irreversibel.
Vorhandene Schutzmechanismen, die erhalten bleiben müssen:
| Mechanismus | Ort | Verhalten |
|---|---|---|
| Workspace-Einsperrung | WorkspacePath.Resolve |
absolute Pfade, .., ADS (:) verboten |
| Zugriffsstufen | IsActionAllowed |
delete im Shared nur bei sharedAccessLevel = Admin |
| Geschützte Pfade | IsPathProtected |
protectedPaths sind append-only, kein Löschen |
| Datei-Locks | GetFileLock |
pro Pfad ein SemaphoreSlim |
2. Zielverhalten
delete → verschiebt nach .trash/<yyyyMMdd_HHmmss>/<originalpfad>
list_trash → listet Papierkorb-Einträge mit Originalpfad und Löschzeitpunkt
restore → holt einen Eintrag an seinen Originalpfad zurück
purge → löscht endgültig (nur Admin, ausdrücklicher Opt-in)
Papierkorb-Layout (im jeweiligen Workspace-Root):
.trash/
├── 20260805_142233/
│ ├── _meta.json ← Originalpfad, Zeitpunkt, Agent, Typ
│ └── notizen.md ← der gelöschte Inhalt (Datei oder Verzeichnis)
└── 20260805_151002/
├── _meta.json
└── alte-recherche/
_meta.json:
{
"originalPath": "recherche/notizen.md",
"workspace": "personal",
"deletedAt": "2026-08-05T14:22:33Z",
"deletedBy": "senior_developer",
"type": "file"
}
Der Zeitstempel ist der Ordnername — damit braucht der Cleanup-Job keine
Metadatei zu lesen, um das Alter zu bestimmen (_meta.json ist Komfort,
keine Voraussetzung). Bei Kollision wird _1, _2 … angehängt, analog
HandleStockAddAsync.
3. Sicherheitsanforderungen
Diese Punkte sind nicht optional — ohne sie öffnet der Papierkorb neue Lücken:
-
.trashist für alle direkten Aktionen gesperrt.read,write,append,delete,copymit einem Pfad in.trash/werden abgelehnt. Sonst wäre der Papierkorb ein Ablageort, über den die Endungsprüfung (personalAllowedExtensions) umgangen werden kann: eine.exe„löschen" und aus dem Papierkorb an beliebiger Stelle wieder herausholen. Zugriff ausschließlich überlist_trash/restore/purge. -
listblendet.trashaus. Sonst verschmutzt der Papierkorb jede Verzeichnisauflistung und damit den Kontext des Agenten. -
Geschützte Pfade bleiben unantastbar. Die bestehende Prüfung in
HandleDeleteAsyncgreift vor dem Verschieben — einprotectedPathwandert auch nicht in den Papierkorb. -
restoreprüft das Ziel erneut vollständig:WorkspacePath.Resolveauf den Originalpfad, Endungsprüfung,IsPathProtected,IsActionAllowed. Der Originalpfad aus_meta.jsonist Eingabedatum, keine Wahrheit — eine manipulierte Metadatei darf keinen Ausbruch ermöglichen. -
Shared Workspace: ein Papierkorb je Agent —
.trash/<agentId>/<zeitstempel>/. Sonst sieht und restauriert Agent A die gelöschten Dateien von Agent B, undpurgeeines Agenten trifft alle. Im Personal Workspace entfällt die Ebene (dort ist nur ein Agent). -
purgenur beisharedAccessLevel = Admin(Shared) bzw. explizit im Personal Workspace. Der Regelweg zum endgültigen Löschen ist der Cleanup-Job, nicht der Agent. -
Kein Verschieben über Laufwerksgrenzen annehmen.
Directory.Movescheitert, wenn Workspace und.trashauf verschiedenen Volumes lägen — das ist per Konstruktion nicht der Fall (.trashliegt im Workspace-Root), aber der Fehlerfall wird sauber gemeldet statt zu einer Teilkopie zu führen.
4. Cleanup-Job
FileRWTool implementiert zusätzlich IToolJobProvider
(src/ClawdDotNet.Core/Tools/IToolJobProvider.cs). Die Infrastruktur ist
vollständig vorhanden — ToolJobScheduler kennt Cron, RunOnStart und
manuelles Auslösen; es braucht keinen neuen Scheduler.
public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() =>
[
new("filerw_trash_cleanup", "Papierkorb aufräumen",
"Löscht Papierkorb-Einträge, die älter als retentionDays sind")
];
ExecuteJobAsync bekommt workspacePath und agentId bereits übergeben
(siehe Signatur des Interfaces). Der Job:
- liest
retentionDaysaustoolConfig(Default 14, Minimum 1), - durchläuft
.trash/*in Personal- und Shared-Workspace, - löscht Ordner, deren Zeitstempel älter als die Aufbewahrungsfrist ist,
- gibt immer
ToolJobResult.NoAction(logSummary)zurück — der Agent wird nie geweckt. Aufräumen ist kein Ereignis, das ein LLM-Run wert wäre (und kein Token kosten soll).
Der Shared-Papierkorb wird nur bereinigt, wenn der Job-Agent dort Admin-Rechte hat; andernfalls nur der eigene Unterordner. Damit räumt nicht jeder Agent bei jedem Tick fremde Einträge ab.
Beispielkonfiguration im Agenten:
"toolJobs": [
{
"jobId": "trash-daily",
"toolName": "FileRW",
"jobTypeId": "filerw_trash_cleanup",
"cron": "0 3 * * *",
"enabled": true,
"runOnStart": false
}
]
Achtung Zeitzone: ToolJobScheduler.RunToolJobAsync rechnet mit
DateTime.Now (lokal), die Papierkorb-Zeitstempel oben sind UTC. Das
Altersvergleich im Job daher konsequent in UTC durchführen
(DateTime.UtcNow), nicht mischen. Dieselbe Falle ist bereits aus der
Watchdog-Integration bekannt.
5. Umsetzungsschritte
Slice 1 — Papierkorb im Tool (Kern)
TrashPath-Helfer analogWorkspacePath: baut und validiert.trash-Pfade, kapselt die Agent-Ebene im Shared WorkspaceHandleDeleteAsyncauf Verschieben umstellen (Datei und Verzeichnis).trash-Sperre inGetAndValidatePath(greift für alle direkten Aktionen).trashausHandleListAsyncfiltern_meta.jsonschreiben (überAtomicFile, wie beim Stock-Index)- Tests:
tests/ClawdDotNet.Tools.Tests/FileRW/TrashTests.cs
Slice 2 — list_trash / restore / purge
- Drei Aktionen im
InputSchemaund imswitchergänzen restoremit vollständiger Zielprüfung (siehe 3.4)purgemit Admin-PrüfungDescriptiondes Tools ergänzen, damit das LLM den Papierkorb kennt- Tests: Restore an geschützten Pfad, Restore mit manipulierter
_meta.json, Restore mit inzwischen belegtem Zielpfad
Slice 3 — Cleanup-Job
IToolJobProvideranFileRWToolretentionDaysinFileRWToolSettings(Models/ToolSettingsViewModels.cs:68) + Designer-Property- Job-Definition in der UI auswählbar (läuft über die bestehende Job-Verwaltung, kein neuer Dialog)
- Tests mit
FakeTimeProvider(tests/ClawdDotNet.Core.Tests/Infrastructure/FakeTimeProvider.cs)
Slice 4 — Dokumentation
docs/ToolDevelopmentGuide.md: FileRW-Abschnitt (Zeile ~221) auf die neuen Aktionen aktualisieren- Prompt-Hinweis für Agenten: „Löschen ist reversibel, der Papierkorb wird nach X Tagen geleert" — sonst bleibt der Agent unnötig vorsichtig
6. Folgeentscheidung: Staging herunterstufen
StagingPolicy.DefaultRules (src/ClawdDotNet.Core/Staging/StagingPolicy.cs)
führt FileRW.delete heute als Approve — jede Aufräumaktion braucht eine
menschliche Freigabe. Das ist richtig, solange Löschen endgültig ist.
Mit dem Papierkorb ist es das nicht mehr. Empfehlung nach Slice 2:
["FileRW.delete"] = StagingDecision.Auto, // reversibel über .trash
["FileRW.purge"] = StagingDecision.Approve // endgültig → Freigabe
Damit kann der Agent selbstständig aufräumen (genau der Wunsch aus der Ideensammlung), ohne dass irreversible Aktionen ungefragt durchgehen.
7. Abnahmekriterien
- Ein gelöschter Ordner liegt vollständig im Papierkorb und ist per
restorewiederherstellbar. read/writeauf einen.trash-Pfad wird abgelehnt.restoreeiner.exein einen Workspace mitallowedExtensionsohne.exewird abgelehnt.- Ein
protectedPathlässt sich weiterhin nicht löschen. - Agent B sieht im Shared-Papierkorb nicht die Einträge von Agent A.
- Nach Ablauf von
retentionDaysist der Eintrag beim nächsten Job-Tick weg, ohne dass ein LLM-Run stattgefunden hat.