diff --git a/.ai-control/config.json b/.ai-control/config.json new file mode 100644 index 0000000..efe5acb --- /dev/null +++ b/.ai-control/config.json @@ -0,0 +1,9 @@ +{ + "id": "c816fc10-cd19-4e93-b680-5b8d3dfeaa7c", + "name": "ai-control", + "terminal": { + "theme": "monokai", + "icon": "icon.png" + }, + "archiveHome": "~/claude-projects/claude-ai-control/archive" +} diff --git a/.ai-control/icon.png b/.ai-control/icon.png new file mode 100644 index 0000000..4c62dc3 Binary files /dev/null and b/.ai-control/icon.png differ diff --git a/OFFENE-PUNKTE.md b/OFFENE-PUNKTE.md index a6f6a22..80d214b 100644 --- a/OFFENE-PUNKTE.md +++ b/OFFENE-PUNKTE.md @@ -1,196 +1,72 @@ # Offene Punkte — bei jedem Start prüfen und abhaken -- [x] **„Projekt hinzufügen" verliert `archiveHome` — Ursache gefunden, behoben.** - Der Verdacht (Add-Projekt-Pfad) war falsch. Ursache war ein verlustbehafteter - Config-Round-Trip: `read_project_config_in` deserialisiert in `ProjectConfig`, - serde verwirft dabei still jeden unbekannten Key, und `write_project_config_in` - schreibt die Datei komplett neu. Geschrieben hat die Pool-Zuweisung - (`ProjectList.vue:113` → `assign_pool_in`), ausgeführt von einem **älteren - Build ohne das Feld `archive_home`** — daher der Halbzustand mit erhaltener - Berechtigung in `.claude/settings.json` (anderer Schreibpfad). +Erledigt und rausgeräumt (2026-07-20, Details in der Git-Historie): Umbau auf +`.ai-control/config.json` mit Projekt-UUID + Name (Registry maschinenlokal +inkl. Pool, Icon im Projekt, Migration beim Start, Import-Prüfung); +Settings-Dialog-Redesign (Gruppen, fester Kopf/Fuß, feste Höhe); +Sicherheitsrunde II: `term_start`-Label-Prüfung, `archive_panel` ohne `dir`, +`write_panel(path)` mit Allowlist + 2-MB-Limit, `write_atomic` überall, +`write_secret_file` mit `create_new`+0600, PTY-Env ohne ANTHROPIC-Keys, +`check_name` in den Panel-Pfaden, Bidi-Filter in den Befehlskacheln. - Fix: `#[serde(flatten)] rest` in `ProjectConfig` reicht unbekannte Keys - unverändert durch; Regressionstest `pool_zuweisen_erhaelt_fremde_keys`. - Einschränkung: Die Versionsdifferenz ist aus Zeitstempeln erschlossen, das - alte Binary war schon überschrieben. Der Defekt selbst ist am Code belegt. +Die ACL-Frage aus der Sicherheitsrunde ist entschieden (gegen tauri 2.11.5 +verifiziert): **Die Fenster-ACL greift für App-Commands nicht** — ohne +`__app-acl__`-Manifest prüft Tauri eigene Commands bei lokalem Origin gar +nicht; die `windows`-Liste in den Capabilities beschränkt nur die +Plugin-Permissions. Daraus folgt der erste Punkt unten. - Ursprünglicher Befund 2026-07-19 (claude-ai-control): - - `archive/` existiert seit 21:03 samt Testdokument, war also gesetzt. - - `.claude/settings.json` hat den Eintrag noch: - `additionalDirectories: ~/claude-projects/claude-ai-control/archive` - (geschrieben von `add_archive_permission`, archive.rs:56). - - `ai-control.json` hat **kein** `archiveHome` mehr, mtime 21:18. - - Halbzustand Recht-ohne-Config kann nicht vom Abwählen-Button kommen: - `clear_project_archive_home` (archive.rs:96-104) nimmt beides zurück. - - Folge: `panel-wiring.ts:45` blendet Wiki- und Suche-Tab aus. +## Entscheidungen nötig - Zu prüfen war: Schreibpfade auf `PROJECT_FILE` in `domain/project.rs` - (`write_project_config_in`, project.rs:81) — welcher davon eine frische - Config ohne `archive_home` schreibt. +- [ ] **App-ACL-Manifest einführen?** Jedes Fenster darf derzeit jedes + App-Command rufen (`delete_project`, `set_archive_home_cmd`, …); + abgesichert ist nur der PTY-Pfad (Label-Prüfung in `term_start`). + Ein App-Manifest (eigene Permissions pro Command, in den Capabilities + pro Fenster zugeteilt) wäre die strukturelle Antwort — spürbarer + Umbau, Umfang festlegen. -- [ ] **Command-History braucht eine Identität statt Indizes.** - Aufgefallen beim Fix von `commands_delete` (Panik bei Doppelklick, Löschen - der falschen Kachel). Der Fix schickt jetzt den Befehlstext mit und gleicht - serverseitig ab — das verhindert Schaden, ist aber nur ein Notnagel: Zwei - identische Befehle im selben Record sind nicht unterscheidbar, und die - Panel-Indizes hinken der Datei weiter um bis zu ein Watcher-Intervall - (200 ms) hinterher. Sauber wäre eine ID je Eintrag beim Schreiben. +- [ ] **`csp: null` in `tauri.conf.json` ersetzen.** Zweite Schranke hinter + dem Markdown-Sanitizer; braucht eine Runde Prüfen, was inline nötig ist + (Themes, xterm). Schon `default-src 'self'` schnitte auswärtige + Requests und Fremdnavigation ab. -- [ ] **`csp: null` in `tauri.conf.json` ersetzen.** - Aufgefallen beim XSS-Fix: Es gibt keine zweite Schranke. Das Panel liegt im - selben Webview wie das Terminal, dessen `term_write` die PTY über das - Fensterlabel adressiert — eingeschleustes Skript könnte in die laufende - Shell schreiben. Das Markdown-Rendering ist jetzt entschärft - (`src/markdown.ts`), aber eine echte CSP wäre die Schranke dahinter. - Braucht eine Runde Prüfen, was inline benötigt wird (Themes, xterm). - Schon ein `default-src 'self'` würde auswärtige Requests und die - Fremdnavigation unabhängig vom Sanitizer abschneiden. +- [ ] **Archiv-Ordner-Handling.** Beim Setzen explizit bestätigen statt still + anlegen; Wechsel eines gesetzten Archiv-Homes braucht eine + Migrationsmöglichkeit (Dokumente mitnehmen oder liegen lassen). + Kritisch: echte Nutzerdaten, nichts implizit verschieben. -## Aus der Sicherheitsrunde 2026-07-19 (vier Angriffsflächen geprüft) +- [ ] **Projekt sauber löschen.** Alle Ressourcen des Projekts entfernen + (Config, Icon, Archiv-Rechte in `.claude/settings.json`, + Registry-Eintrag) — mit Vorschau, was entfernt wird. Umfang festlegen. -Behoben und gepusht (6d44f77): Befehlsinjektion im SessionStart-Hook über den -Projektpfad, ungeprüfter Pool-Name als `CLAUDE_CONFIG_DIR`, `check_name` gegen -Steuerzeichen (`.desktop`-Injektion), Bild-Beacon im Panel. Dazu `cargo audit` -nachgeholt: `plist` auf 1.10.0, `quick-xml` auf 0.41.0 im Laufzeitpfad. +- [ ] **macOS: API-Key kurz in der `security`-Kommandozeile sichtbar** + (`macos.rs`, `ps`-sichtbar). Bewusst zurückgestellt, bis eine + macOS-Session zum Testen verfügbar ist. -Offen sind die vier Punkte, die eine Designentscheidung brauchen: +## Go genügt (Weg ist klar) -- [ ] **`term_start` prüft das Fenster-Label nicht (`terminal.rs:96-211`).** - Jedes Fenster des Terminal-Prozesses kann sich selbst in die Terminals-Map - eintragen (Schlüssel ist `window.label()`, Zeile 207-210) und danach per - `term_write` in eine frisch gestartete Shell schreiben. Damit ist auch das - abgelöste Panel-Fenster gefährlich, nicht nur das angedockte — und jedes - Skript im Webview wird zu Codeausführung, auch ohne XSS. - Zu entscheiden: Soll das Panel-Fenster je ein eigenes Terminal starten - dürfen? Wenn nein, ist die Prüfung auf `term-*` ein enger Fix. +- [ ] **Command-History: stabile ID je Eintrag.** Der cmd-Text-Abgleich in + `commands_delete` ist ein Notnagel; ID beim Schreiben der JSONL + vergeben, Panel löscht über die ID. - Strukturell dahinter: Die Fenster-ACL scheint für App-Commands nicht zu - greifen (`gen/schemas/acl-manifests.json` enthält kein App-Manifest) — - **vor dem Bauen darauf verifizieren**, die beiden Prüfagenten waren sich - in diesem Punkt uneins. +- [ ] **`write_app_setting` verliert die übrigen App-Settings** + (`settings.rs`): `read_app_settings` wirft fehlende Datei, Lesefehler + und kaputtes JSON in denselben `None`; nur eine *fehlende* Datei + rechtfertigt ein frisches Objekt (Fix wie in `pool.rs` + `register_mcp_server`). -- [ ] **`write_panel(path)` liest jede Datei, ohne Rückfrage (`mcp.rs:256-259`).** - Keine Prüfung gegen Projektordner oder Archiv-Home. Das Tool steht in - `PANEL_PERMISSIONS` (`pool.rs:185-188`), läuft also prompt-frei — und - umgeht damit das Read-Permission-Modell von Claude Code (`~/.ssh/id_rsa`, - `.credentials.json`, fremde `.env`). Rückkanal zum Modell besteht über - `write_panel` → `archive_panel` → `search_archive`. - Zu entscheiden: Allowlist (Projektordner + Archiv-Home, nach - `canonicalize`) oder `path` aus der prompt-freien Freigabe nehmen. - Zusätzlich fehlt ein Größenlimit (`path=/dev/zero` hängt den Prozess). +- [ ] **`struct Pool` verliert unbekannte Keys beim Umbenennen** + (`pool.rs`). Entweder flatten-`rest` wie in `ProjectConfig` — oder + gleich der gemeinsame read-modify-write-Helfer auf + `serde_json::Value`, der die Fehlerklasse überall schließt. -- [ ] **`archive_panel(dir)` vergibt dauerhaft Schreibrechte (`archive.rs:33-91`).** - `dir` ist nicht nur Ablageort: Über `set_project_archive_home` → - `add_archive_permission` landet `additionalDirectories += dir` **und** - `allow += "Edit(dir/**)"` in der `.claude/settings.json` des Projekts. - Der einzige Guard (archive.rs:41) lehnt nur Vorfahren des Home ab — - `~/.claude` passiert, womit die nächste Session eigene **Hooks** schreiben - darf. `create_dir_all` legt das Ziel vorher an. - Vorschlag: `dir` aus dem MCP-Tool entfernen, Archiv-Home nur über die UI. +- [ ] **Archiv-Dateinamen: `create_new` statt `exists()`** (`free_path`, + TOCTOU). Beim Maschinen-Sync können zwei Rechner in derselben Minute + mit gleicher Titelzeile schreiben; Kollisionsfreiheit gehört ins + Dateisystem. Mitzudenken: was der Sync selbst bei Namensgleichheit tut. -- [ ] **Nicht-atomare Schreibvorgänge auf fremde Dateien.** - Überall `fs::write` (truncate + write), nirgends temp + `rename`. - Betrifft u. a. die `.claude/settings.json` der Projekte, Claudes - `.claude.json` in den Pools (`pool.rs:289`), die Registry und die - App-Settings. Die Leseseite haben wir gehärtet, die Schreibseite nicht: - Absturz oder volle Platte hinterlässt abgeschnittenes JSON in Dateien, - die der App nicht gehören. +## Hintergrund (unverändert gültig) -Kleinere Punkte aus derselben Runde, bewusst zurückgestellt: -`panel_file` und Geschwister (`paths.rs:55-84`) ohne `check_name` (zweite -Linie); `write_secret_file` (`unix.rs:48-55`) schreibt erst mit umask-Rechten -und setzt danach 0600, `create_new` mit Modus löst das samt Symlink-Frage; -API-Key steht unter macOS kurz in der Prozess-Kommandozeile -(`macos.rs:101-103`, `ps`-sichtbar, Linux/Windows nicht betroffen); die PTY-Umgebung -wird nicht bereinigt (`terminal.rs:134-144`, kein `env_clear` — ein -`ANTHROPIC_API_KEY` aus dem Shell-Profil sticht den Pool-`apiKeyHelper`); -Bidi-/Zero-Width-Zeichen in den Befehlskacheln (`commands-view.ts:123`), was -Anzeige und Zwischenablage auseinanderlaufen lässt. - -Ausdrücklich **kein** Handlungsbedarf (Stand 2026-07-19): `sync_all_desktops` -räumt `aicontrol-*.desktop` auf — der Präfix gehört uns. Falls es je stört, -wäre `art.lintus.`-Präfix die saubere Variante. - -Nicht als Sicherheitsgrenze missverstehen: Pools trennen Konfiguration, nicht -Zugriff. Die Keychain-Einträge sind bewusst über `/usr/bin/security` angelegt, -damit der `apiKeyHelper` promptfrei liest — womit jeder Prozess des Nutzers -jeden Pool-Key lesen kann. Beim Design so gewollt, aber nichts, worauf man -Isolationsversprechen bauen sollte. - -- [ ] **`write_app_setting` verliert die übrigen App-Settings (`settings.rs:71`).** - Dieselbe Fehlerklasse wie die `.claude.json` in `pool.rs`, nur eine Datei - weiter — beim Simplify-Review gefunden, bewusst nicht mitgefixt. - `read_app_settings` (settings.rs:11-14) wirft mit zweimal `.ok()?` fehlende - Datei, Lesefehler und kaputtes JSON in denselben `None`; `write_app_setting` - macht daraus `json!({})` und schreibt die Datei mit einem Key neu. - Konkret: halb geschriebene `~/.config/ai-control/settings.json`, dann einmal - Schriftgröße verstellen — `claudeCommand`, `poolSyncDir`, `syncOnSessionEnd` - und `spellcheckLang` sind endgültig weg. Fix wie in `pool.rs`: nur eine - *fehlende* Datei rechtfertigt ein frisches Objekt. - -- [ ] **`struct Pool` verliert unbekannte Keys beim Umbenennen (`pool.rs:20`).** - Dritte Stelle derselben Klasse. `rename_pool_in` (pool.rs:402-408) liest - typisiert in `Pool` (nur `name` + `credentialType`) und schreibt die ganze - `pool.json` über `write_pool_json` neu — jeder künftige Key stirbt beim - Umbenennen. `ProjectConfig` und `TerminalConfig` haben dafür inzwischen - `#[serde(flatten)] rest`. - - Grundsätzlicher: `flatten` bildet pro Struct nach, was read-modify-write auf - `serde_json::Value` von Natur aus kann (so macht es `add_archive_permission`, - archive.rs:56-90). Statt es an jedem Struct einzeln zu wiederholen, wäre ein - gemeinsamer Helfer die Lösung, die die Klasse wirklich schließt. - -- [ ] **Archiv-Dateinamen: `create_new` statt `exists()` (`archive.rs`, `free_path`).** - Die Kollisionsprüfung schaut vorab nach (`exists()`), die Garantie liegt aber - nicht im Schreibvorgang — klassisches TOCTOU. Als Einbenutzer-App folgenlos, - **aber nicht mehr beim Maschinen-Sync**: Zwei Rechner können in derselben - Minute mit derselben Titelzeile ins synchronisierte Archiv schreiben, und - dann greift der Vorabblick zu spät. `OpenOptions::new().create_new(true)` in - der Schleife gibt dieselbe Struktur, holt die Kollisionsfreiheit aber vom - Dateisystem. Mitzudenken: Was der Sync-Mechanismus selbst bei zwei gleichen - Namen macht (Konfliktkopien?) — der Fix deckt nur die lokale Seite. - -- [ ] **Projekt-Artefakte im Projektordner bündeln (`.ai-control/`?).** - `ai-control.json` und das Projekt-Icon liegen heute lose im Projektordner. - Idee: beides in ein Unterverzeichnis (z. B. `.ai-control/`) zusammenziehen, - damit der Projektordner sauber bleibt und alles an einem Ort steht. - Offen: Migrationspfad für bestehende Projekte, Rückwärtskompatibilität - beim Lesen (altes Layout weiter erkennen). - -- [ ] **Migrations-Assistent: Projekt sauber löschen.** - Löschen soll alle Ressourcen des Projekts aus dem Ordner entfernen - (Config, Icon, ggf. Archiv-Rechte in `.claude/settings.json`, - Registry-Eintrag in `projects.json`) — nicht nur den Registry-Eintrag. - Vorher anzeigen, was entfernt wird. - -- [ ] **Archiv-Ordner-Handling sauber aufsetzen.** - Beim Setzen eines Archiv-Homes explizit abfragen statt still anlegen: - Zielordner bestätigen, bestehenden Inhalt erkennen, klarmachen was passiert. - Wechsel eines bereits gesetzten Archiv-Homes braucht eine - Migrationsmöglichkeit (Dokumente mitnehmen oder liegen lassen) — - als kritisch einstufen: hier hängen echte Nutzerdaten dran, also - nichts implizit verschieben oder überschreiben. - -- [ ] **`pool` gehört nicht in die projektbezogene `ai-control.json`.** - Der Pool ist rechnerspezifisch: gleicher Claude-Account, aber pro Maschine - ein anderer Pool. Versioniert im Projekt trägt die Datei die Zuordnung - über Rechnergrenzen falsch weiter (aktuell steht dort die UUID - `c39d96c9-…`, gepusht mit 5081efc). - - Zu klären: Trennung projektbezogen (archiveHome, Theme, Icon) vs. - maschinenbezogen (pool). Optionen — projekt-lokale, nicht versionierte - Datei, oder Pool pro Rechner zentral in der `projects.json`-Registry - unter `~/.config/ai-control`. Nachdenken, noch keine Entscheidung. - -- [ ] **Settings-Dialog (⚙) neu gestalten — dringend, Plan liegt vor (2026-07-20).** - Ist-Zustand: sieben Felder untereinander, drei Zeilen-Layouts, rohe - Pfade, zwei gemischte Interaktionsmodelle (Ordner/Archiv schreiben - sofort, Titel/Theme/Icon/Todo erst beim Speichern). - Abgestimmter Plan: (1) drei Gruppen mit Eyebrows — Darstellung - (Titel/Theme/Dock-Icon), Ordner (Projekt/Arbeitsordner/Archiv), - Session (Todoliste); (2) einheitliche Zeile Label|Wert|Aktionen, - Pfade ~-kontrahiert mit Hover-Pop; (3) Sofort-Schreiber optisch als - eigene Gruppe, Speichern bezieht sich erkennbar nur auf Darstellung + - Session, „gilt ab nächstem Start"-Hinweis an die betroffene Gruppe; - (4) festes Dialog-Grid, Theme-Variablen des Hauptfensters. - Nur ProjectList.vue (Template + Styles), keine Backend-Änderung. +Pools trennen Konfiguration, nicht Zugriff: Die Keychain-Einträge sind +bewusst über `/usr/bin/security` bzw. Keyring promptfrei lesbar — jeder +Prozess des Nutzers kann jeden Pool-Key lesen. So gewollt, kein +Isolationsversprechen. diff --git a/ai-control.json b/ai-control.json deleted file mode 100644 index 725c8b9..0000000 --- a/ai-control.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "pool": "c39d96c9-ec88-4953-8d4e-23a2ea3ea388", - "archiveHome": "~/claude-projects/claude-ai-control/archive", - "terminal": { - "theme": "monokai", - "icon": "ai-control.png" - } -}