Offene Punkte aufgeräumt; Projekt-Config auf .ai-control/ migriert
Erledigtes raus (Umbau .ai-control/UUID, Dialog-Redesign, Sicherheitsrunde II), ACL-Ergebnis dokumentiert, Punkte in 'Entscheidung nötig' und 'Go genügt' gruppiert. ai-control.json ist durch .ai-control/config.json + icon.png ersetzt (App-Migration).
This commit is contained in:
@@ -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"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
+56
-180
@@ -1,196 +1,72 @@
|
|||||||
# Offene Punkte — bei jedem Start prüfen und abhaken
|
# Offene Punkte — bei jedem Start prüfen und abhaken
|
||||||
|
|
||||||
- [x] **„Projekt hinzufügen" verliert `archiveHome` — Ursache gefunden, behoben.**
|
Erledigt und rausgeräumt (2026-07-20, Details in der Git-Historie): Umbau auf
|
||||||
Der Verdacht (Add-Projekt-Pfad) war falsch. Ursache war ein verlustbehafteter
|
`.ai-control/config.json` mit Projekt-UUID + Name (Registry maschinenlokal
|
||||||
Config-Round-Trip: `read_project_config_in` deserialisiert in `ProjectConfig`,
|
inkl. Pool, Icon im Projekt, Migration beim Start, Import-Prüfung);
|
||||||
serde verwirft dabei still jeden unbekannten Key, und `write_project_config_in`
|
Settings-Dialog-Redesign (Gruppen, fester Kopf/Fuß, feste Höhe);
|
||||||
schreibt die Datei komplett neu. Geschrieben hat die Pool-Zuweisung
|
Sicherheitsrunde II: `term_start`-Label-Prüfung, `archive_panel` ohne `dir`,
|
||||||
(`ProjectList.vue:113` → `assign_pool_in`), ausgeführt von einem **älteren
|
`write_panel(path)` mit Allowlist + 2-MB-Limit, `write_atomic` überall,
|
||||||
Build ohne das Feld `archive_home`** — daher der Halbzustand mit erhaltener
|
`write_secret_file` mit `create_new`+0600, PTY-Env ohne ANTHROPIC-Keys,
|
||||||
Berechtigung in `.claude/settings.json` (anderer Schreibpfad).
|
`check_name` in den Panel-Pfaden, Bidi-Filter in den Befehlskacheln.
|
||||||
|
|
||||||
Fix: `#[serde(flatten)] rest` in `ProjectConfig` reicht unbekannte Keys
|
Die ACL-Frage aus der Sicherheitsrunde ist entschieden (gegen tauri 2.11.5
|
||||||
unverändert durch; Regressionstest `pool_zuweisen_erhaelt_fremde_keys`.
|
verifiziert): **Die Fenster-ACL greift für App-Commands nicht** — ohne
|
||||||
Einschränkung: Die Versionsdifferenz ist aus Zeitstempeln erschlossen, das
|
`__app-acl__`-Manifest prüft Tauri eigene Commands bei lokalem Origin gar
|
||||||
alte Binary war schon überschrieben. Der Defekt selbst ist am Code belegt.
|
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):
|
## Entscheidungen nötig
|
||||||
- `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.
|
|
||||||
|
|
||||||
Zu prüfen war: Schreibpfade auf `PROJECT_FILE` in `domain/project.rs`
|
- [ ] **App-ACL-Manifest einführen?** Jedes Fenster darf derzeit jedes
|
||||||
(`write_project_config_in`, project.rs:81) — welcher davon eine frische
|
App-Command rufen (`delete_project`, `set_archive_home_cmd`, …);
|
||||||
Config ohne `archive_home` schreibt.
|
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.**
|
- [ ] **`csp: null` in `tauri.conf.json` ersetzen.** Zweite Schranke hinter
|
||||||
Aufgefallen beim Fix von `commands_delete` (Panik bei Doppelklick, Löschen
|
dem Markdown-Sanitizer; braucht eine Runde Prüfen, was inline nötig ist
|
||||||
der falschen Kachel). Der Fix schickt jetzt den Befehlstext mit und gleicht
|
(Themes, xterm). Schon `default-src 'self'` schnitte auswärtige
|
||||||
serverseitig ab — das verhindert Schaden, ist aber nur ein Notnagel: Zwei
|
Requests und Fremdnavigation ab.
|
||||||
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.**
|
- [ ] **Archiv-Ordner-Handling.** Beim Setzen explizit bestätigen statt still
|
||||||
Aufgefallen beim XSS-Fix: Es gibt keine zweite Schranke. Das Panel liegt im
|
anlegen; Wechsel eines gesetzten Archiv-Homes braucht eine
|
||||||
selben Webview wie das Terminal, dessen `term_write` die PTY über das
|
Migrationsmöglichkeit (Dokumente mitnehmen oder liegen lassen).
|
||||||
Fensterlabel adressiert — eingeschleustes Skript könnte in die laufende
|
Kritisch: echte Nutzerdaten, nichts implizit verschieben.
|
||||||
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.
|
|
||||||
|
|
||||||
## 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
|
- [ ] **macOS: API-Key kurz in der `security`-Kommandozeile sichtbar**
|
||||||
Projektpfad, ungeprüfter Pool-Name als `CLAUDE_CONFIG_DIR`, `check_name` gegen
|
(`macos.rs`, `ps`-sichtbar). Bewusst zurückgestellt, bis eine
|
||||||
Steuerzeichen (`.desktop`-Injektion), Bild-Beacon im Panel. Dazu `cargo audit`
|
macOS-Session zum Testen verfügbar ist.
|
||||||
nachgeholt: `plist` auf 1.10.0, `quick-xml` auf 0.41.0 im Laufzeitpfad.
|
|
||||||
|
|
||||||
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`).**
|
- [ ] **Command-History: stabile ID je Eintrag.** Der cmd-Text-Abgleich in
|
||||||
Jedes Fenster des Terminal-Prozesses kann sich selbst in die Terminals-Map
|
`commands_delete` ist ein Notnagel; ID beim Schreiben der JSONL
|
||||||
eintragen (Schlüssel ist `window.label()`, Zeile 207-210) und danach per
|
vergeben, Panel löscht über die ID.
|
||||||
`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.
|
|
||||||
|
|
||||||
Strukturell dahinter: Die Fenster-ACL scheint für App-Commands nicht zu
|
- [ ] **`write_app_setting` verliert die übrigen App-Settings**
|
||||||
greifen (`gen/schemas/acl-manifests.json` enthält kein App-Manifest) —
|
(`settings.rs`): `read_app_settings` wirft fehlende Datei, Lesefehler
|
||||||
**vor dem Bauen darauf verifizieren**, die beiden Prüfagenten waren sich
|
und kaputtes JSON in denselben `None`; nur eine *fehlende* Datei
|
||||||
in diesem Punkt uneins.
|
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`).**
|
- [ ] **`struct Pool` verliert unbekannte Keys beim Umbenennen**
|
||||||
Keine Prüfung gegen Projektordner oder Archiv-Home. Das Tool steht in
|
(`pool.rs`). Entweder flatten-`rest` wie in `ProjectConfig` — oder
|
||||||
`PANEL_PERMISSIONS` (`pool.rs:185-188`), läuft also prompt-frei — und
|
gleich der gemeinsame read-modify-write-Helfer auf
|
||||||
umgeht damit das Read-Permission-Modell von Claude Code (`~/.ssh/id_rsa`,
|
`serde_json::Value`, der die Fehlerklasse überall schließt.
|
||||||
`.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).
|
|
||||||
|
|
||||||
- [ ] **`archive_panel(dir)` vergibt dauerhaft Schreibrechte (`archive.rs:33-91`).**
|
- [ ] **Archiv-Dateinamen: `create_new` statt `exists()`** (`free_path`,
|
||||||
`dir` ist nicht nur Ablageort: Über `set_project_archive_home` →
|
TOCTOU). Beim Maschinen-Sync können zwei Rechner in derselben Minute
|
||||||
`add_archive_permission` landet `additionalDirectories += dir` **und**
|
mit gleicher Titelzeile schreiben; Kollisionsfreiheit gehört ins
|
||||||
`allow += "Edit(dir/**)"` in der `.claude/settings.json` des Projekts.
|
Dateisystem. Mitzudenken: was der Sync selbst bei Namensgleichheit tut.
|
||||||
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.
|
|
||||||
|
|
||||||
- [ ] **Nicht-atomare Schreibvorgänge auf fremde Dateien.**
|
## Hintergrund (unverändert gültig)
|
||||||
Ü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.
|
|
||||||
|
|
||||||
Kleinere Punkte aus derselben Runde, bewusst zurückgestellt:
|
Pools trennen Konfiguration, nicht Zugriff: Die Keychain-Einträge sind
|
||||||
`panel_file` und Geschwister (`paths.rs:55-84`) ohne `check_name` (zweite
|
bewusst über `/usr/bin/security` bzw. Keyring promptfrei lesbar — jeder
|
||||||
Linie); `write_secret_file` (`unix.rs:48-55`) schreibt erst mit umask-Rechten
|
Prozess des Nutzers kann jeden Pool-Key lesen. So gewollt, kein
|
||||||
und setzt danach 0600, `create_new` mit Modus löst das samt Symlink-Frage;
|
Isolationsversprechen.
|
||||||
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.
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"pool": "c39d96c9-ec88-4953-8d4e-23a2ea3ea388",
|
|
||||||
"archiveHome": "~/claude-projects/claude-ai-control/archive",
|
|
||||||
"terminal": {
|
|
||||||
"theme": "monokai",
|
|
||||||
"icon": "ai-control.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user