remove granular tool permissions, add architecture docs
Granulare Tool-Permissions in der Diagnostic UI entfernt — sie hatten keinen Effekt weil Claude Code mit --dangerously-skip-permissions läuft (Alles-oder-Nichts). Ersetzt durch statischen Hinweis-Toggle. Neue Doku in aria-data/docs/tool-permissions.md: alle Erkenntnisse zu OpenClaw Tool-Permissions, 17 gescheiterte Versuche, finale Lösung (CLAUDE_CODE_BUBBLEWRAP=1). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1afb47c49c
commit
62d5d73c74
|
|
@ -0,0 +1,137 @@
|
|||
# OpenClaw Tool-Permissions — Stand 2026-03-15
|
||||
|
||||
## Das Problem (GELÖST)
|
||||
|
||||
ARIA hat ZWEI Tool-Systeme gleichzeitig: Claude Code Tools UND OpenClaw-native Tools.
|
||||
Das Model hat aber nur Zugriff auf **Claude Code Tools** (über den Proxy), nicht auf OpenClaw-native Tools.
|
||||
|
||||
### Root Cause: DREI Probleme gleichzeitig
|
||||
|
||||
```
|
||||
OpenClaw (aria-core) → API Request → claude-max-api-proxy (aria-proxy) → Claude Code CLI (--print Mode)
|
||||
↓
|
||||
Tools: WebFetch, Bash, etc. (Claude Code)
|
||||
NICHT: web_fetch, exec (OpenClaw-nativ)
|
||||
```
|
||||
|
||||
**Problem 1: Proxy benutzt `--print` Modus**
|
||||
- `claude-max-api-proxy` ruft Claude Code CLI mit `--print --output-format stream-json` auf
|
||||
- Der Prompt wird als einziger String übergeben, keine Tool-Definitionen von OpenClaw
|
||||
- Das Model sieht NUR Claude Code's eingebaute Tools (WebFetch, Bash, etc.)
|
||||
- OpenClaw-native Tools (web_fetch, exec) existieren NUR auf Gateway-Ebene, kommen nie beim Model an
|
||||
|
||||
**Problem 2: BOOTSTRAP.md hat die falschen Tools angewiesen**
|
||||
- BOOTSTRAP.md sagte: "NIEMALS WebFetch benutzen, stattdessen web_fetch"
|
||||
- Aber web_fetch existiert nicht im Claude Code CLI Kontext
|
||||
- Und WebFetch war das einzige Tool das funktioniert hätte
|
||||
- → Model hatte keine Tools die es benutzen "durfte"
|
||||
|
||||
**Problem 3: settings.json im Proxy war leer**
|
||||
- `/root/.claude/settings.json` enthielt nur `{}` (keine Permissions)
|
||||
- Claude Code CLI im headless-Modus kann keine Tool-Genehmigungen erteilen
|
||||
- → Selbst wenn das Model WebFetch benutzen wollte, war es nicht vorab genehmigt
|
||||
|
||||
## Die Lösung
|
||||
|
||||
### Fix 1: BOOTSTRAP.md + AGENT.md umgeschrieben
|
||||
|
||||
**Vorher (FALSCH):**
|
||||
- "NIEMALS WebFetch benutzen — hat Permission-Probleme"
|
||||
- "Benutze web_fetch (OpenClaw-nativ)"
|
||||
|
||||
**Nachher (KORREKT):**
|
||||
- "WebFetch — URLs abrufen, Webseiten lesen, APIs aufrufen, Wetter abfragen"
|
||||
- "Bash — Shell-Befehle ausfuehren (curl, ssh, docker, etc.)"
|
||||
- "Niemals sagen 'ich habe keinen Zugriff' — du hast Zugriff auf alles"
|
||||
|
||||
### Fix 2: `CLAUDE_CODE_BUBBLEWRAP=1` + `--dangerously-skip-permissions`
|
||||
|
||||
**Der Schlüssel-Fix.** Zwei Zeilen in `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
# 1. sed-Patch: --dangerously-skip-permissions in manager.js einfügen
|
||||
sed -i 's/"--no-session-persistence",/"--no-session-persistence","--dangerously-skip-permissions",/' $$DIST/subprocess/manager.js &&
|
||||
|
||||
# 2. Environment-Variable: Root-Check umgehen
|
||||
environment:
|
||||
- CLAUDE_CODE_BUBBLEWRAP=1
|
||||
```
|
||||
|
||||
**Warum beides nötig:**
|
||||
- `--dangerously-skip-permissions` umgeht alle Tool-Permission-Checks in Claude Code CLI
|
||||
- Aber: Claude Code CLI blockiert dieses Flag wenn es als root läuft
|
||||
- `CLAUDE_CODE_BUBBLEWRAP=1` überspringt den Root-Check (gefunden im minifizierten `cli.js`)
|
||||
- Proxy-Container (`node:22-alpine`) läuft als root → ohne BUBBLEWRAP geht's nicht
|
||||
|
||||
**Resultierende CLI-Argumente:**
|
||||
```
|
||||
claude --print --output-format stream-json --verbose --include-partial-messages \
|
||||
--model opus --no-session-persistence --dangerously-skip-permissions "prompt"
|
||||
```
|
||||
|
||||
## Wie der Proxy intern funktioniert
|
||||
|
||||
```
|
||||
openai-to-cli.js: OpenAI Messages → einzelner Prompt-String
|
||||
system → <system>...</system>
|
||||
user → direkt
|
||||
assistant → <previous_response>...</previous_response>
|
||||
|
||||
subprocess/manager.js: Spawnt `claude --print ... --dangerously-skip-permissions "{prompt}"`
|
||||
|
||||
cli-to-openai.js: Claude CLI JSON-Stream → OpenAI Chat Completion Chunks
|
||||
```
|
||||
|
||||
Der Proxy leitet KEINE Tool-Definitionen von OpenClaw weiter.
|
||||
Tool-Calls passieren INTERN in der Claude Code CLI und sind für OpenClaw transparent.
|
||||
|
||||
## Permission-Architektur
|
||||
|
||||
**Granulare Tool-Kontrolle ist NICHT möglich.** Es ist Alles-oder-Nichts:
|
||||
- `--dangerously-skip-permissions` AN → ARIA kann alle Claude Code Tools benutzen
|
||||
- `--dangerously-skip-permissions` AUS → ARIA kann keine Tools benutzen
|
||||
|
||||
OpenClaw's eigene Permissions (`tools.allow/deny` in `openclaw.json`) haben **keinen Effekt** auf die
|
||||
Claude Code Tools — die laufen komplett auf Proxy-Seite.
|
||||
|
||||
## Was NICHT funktioniert hat (17 Versuche)
|
||||
|
||||
1. **settings.json in aria-core** — OpenClaw benutzt NICHT Claude Code's settings.json
|
||||
2. **tools.allow mit PascalCase** (WebFetch, Grep) — OpenClaw kennt diese Namen nicht
|
||||
3. **tools.allow mit snake_case** (web_fetch) — Nur exec, read, write, edit erkannt
|
||||
4. **tools.allow mit Wildcard** `["*"]` — Hat nicht geholfen
|
||||
5. **tools.allow leer + tools.profile: "full"** — Nur ohne andere Fehler
|
||||
6. **System-Prompt Anweisung allein** — Reicht nicht wenn Tools blockiert sind
|
||||
7. **exec-approvals Wildcard allein** — Reicht nicht bei Config-Validation-Error
|
||||
8. **`openclaw config unset tools.exec.ask`** — CLI kennt den Pfad nicht
|
||||
9. **BOOTSTRAP.md mit OpenClaw-Tool-Namen** — Tools existieren nur auf Gateway-Ebene
|
||||
10. **settings.json im Proxy ohne BOOTSTRAP.md Fix** — BOOTSTRAP.md verbot die Tools
|
||||
11. **tools.byProvider.proxy.profile full** — Kein Effekt
|
||||
12. **settings.json + BOOTSTRAP.md ohne --dangerously-skip-permissions** — `--print` ignoriert settings.json
|
||||
13. **Manuelles `docker exec sed`** — Wird bei jedem Restart überschrieben
|
||||
14. **`--dangerously-skip-permissions` ohne BUBBLEWRAP** — Root-Check blockiert
|
||||
15. **`--allowedTools`** — Variadisches Argument frisst den Prompt
|
||||
16. **`--permission-mode bypassPermissions`** — Gleicher Root-Check
|
||||
17. **Non-Root User (`su node`)** — Auth-Pfad-Probleme, Credentials unerreichbar
|
||||
|
||||
## Wichtige Pfade
|
||||
|
||||
### aria-core (OpenClaw)
|
||||
- `/home/node/.openclaw/openclaw.json` — OpenClaw Haupt-Config
|
||||
- `/home/node/.openclaw/exec-approvals.json` — Exec Approvals
|
||||
- `/tmp/openclaw/openclaw-YYYY-MM-DD.log` — Tages-Log
|
||||
|
||||
### aria-proxy (Claude Code CLI)
|
||||
- `/root/.claude/.credentials.json` — Auth Credentials (NICHT in /root/.config/claude/)
|
||||
- `/usr/local/lib/node_modules/claude-max-api-proxy/dist/` — Proxy Source
|
||||
- `/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js` — Claude Code CLI (enthält Root-Check)
|
||||
|
||||
## OpenClaw CLI Referenz
|
||||
|
||||
```bash
|
||||
openclaw config get/set/unset <path> # Config verwalten
|
||||
openclaw approvals get # Exec-Approvals anzeigen
|
||||
openclaw approvals allowlist add # Exec-Pattern freigeben
|
||||
openclaw doctor [--fix] # Health Check
|
||||
openclaw gateway status # Gateway-Status
|
||||
```
|
||||
|
|
@ -311,20 +311,23 @@
|
|||
|
||||
<!-- Tool-Berechtigungen -->
|
||||
<div class="settings-section">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<h2>Tool-Berechtigungen</h2>
|
||||
<div style="display:flex;gap:6px;">
|
||||
<button class="btn secondary" onclick="loadPermissions()" style="padding:4px 12px;font-size:11px;">Laden</button>
|
||||
<button class="btn" onclick="savePermissions()" id="btn-save-perms" style="padding:4px 12px;font-size:11px;" disabled>Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:11px;color:#8888AA;margin-bottom:12px;">
|
||||
Hier kannst du festlegen, welche Tools ARIA benutzen darf. Aenderungen werden in der OpenClaw-Konfiguration gespeichert.
|
||||
Claude Code laeuft mit <code>--dangerously-skip-permissions</code> im Proxy — Alles oder Nichts.
|
||||
Granulare Tool-Kontrolle ist in dieser Architektur nicht moeglich.
|
||||
</div>
|
||||
<div class="card" style="max-width:500px;">
|
||||
<div class="perm-item" style="border:none;padding:0;">
|
||||
<div class="perm-info">
|
||||
<div class="perm-name">Claude darf alle Tools benutzen</div>
|
||||
<div class="perm-desc">WebFetch, Bash, Read/Write/Edit, WebSearch, Agent, etc.</div>
|
||||
</div>
|
||||
<label class="toggle"><input type="checkbox" checked disabled><span class="slider"></span></label>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#555570;margin-top:8px;">
|
||||
Deaktivieren = <code>--dangerously-skip-permissions</code> aus docker-compose.yml entfernen.
|
||||
Dann kann ARIA keine Tools mehr benutzen (auch kein WebFetch fuer Wetter etc.).
|
||||
</div>
|
||||
<div id="perms-status" style="font-size:11px;margin-bottom:8px;display:none;"></div>
|
||||
<div id="perms-grid" class="perm-grid">
|
||||
<!-- Wird dynamisch gefuellt -->
|
||||
<div style="color:#555570;padding:12px;">Klicke "Laden" um die aktuellen Berechtigungen abzurufen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -576,25 +579,7 @@
|
|||
}
|
||||
if (msg.type === 'brain_list') { renderBrainList(msg); return; }
|
||||
if (msg.type === 'brain_content') { renderBrainContent(msg); return; }
|
||||
// Settings
|
||||
if (msg.type === 'permissions_list') { renderPermissions(msg); return; }
|
||||
if (msg.type === 'permissions_saved') {
|
||||
const s = document.getElementById('perms-status');
|
||||
s.style.display = 'block';
|
||||
if (msg.ok) {
|
||||
// Nachricht merken und nach Reload wieder anzeigen
|
||||
permsSavedMsg = (msg.info || 'Berechtigungen gespeichert!')
|
||||
+ '<br><span style="color:#FFD60A;font-size:10px;">Session muss neu gestartet werden damit Aenderungen wirksam werden.</span>'
|
||||
+ ' <button class="btn secondary" onclick="restartAriaSession()" style="padding:2px 8px;font-size:10px;margin-left:4px;">Session neu starten</button>';
|
||||
s.style.color = '#34C759';
|
||||
s.innerHTML = permsSavedMsg;
|
||||
loadPermissions();
|
||||
} else {
|
||||
s.style.color = '#FF6B6B';
|
||||
s.textContent = 'Fehler: ' + (msg.error || '?');
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Settings (permissions_list/permissions_saved entfernt — Alles-oder-Nichts via --dangerously-skip-permissions)
|
||||
if (msg.type === 'session_restarted') {
|
||||
const s = document.getElementById('perms-status');
|
||||
s.style.display = 'block';
|
||||
|
|
@ -1206,127 +1191,9 @@
|
|||
}
|
||||
|
||||
// ── Einstellungen: Tool-Berechtigungen ──────────────────
|
||||
|
||||
const TOOL_DEFS = [
|
||||
{ id: 'Bash', name: 'Bash', desc: 'Shell-Befehle ausfuehren', cat: 'System' },
|
||||
{ id: 'Read', name: 'Read', desc: 'Dateien lesen', cat: 'Dateien' },
|
||||
{ id: 'Write', name: 'Write', desc: 'Dateien schreiben/erstellen', cat: 'Dateien' },
|
||||
{ id: 'Edit', name: 'Edit', desc: 'Dateien bearbeiten (Diff)', cat: 'Dateien' },
|
||||
{ id: 'Grep', name: 'Grep', desc: 'In Dateien suchen', cat: 'Dateien' },
|
||||
{ id: 'Glob', name: 'Glob', desc: 'Dateien nach Muster finden', cat: 'Dateien' },
|
||||
{ id: 'WebFetch', name: 'WebFetch', desc: 'Webseiten abrufen (HTTP)', cat: 'Netzwerk' },
|
||||
{ id: 'WebSearch', name: 'WebSearch', desc: 'Web-Suche durchfuehren', cat: 'Netzwerk' },
|
||||
{ id: 'NotebookEdit', name: 'NotebookEdit', desc: 'Jupyter Notebooks bearbeiten', cat: 'Dateien' },
|
||||
{ id: 'TodoWrite', name: 'TodoWrite', desc: 'Aufgaben/Todos verwalten', cat: 'Organisation' },
|
||||
{ id: 'Agent', name: 'Agent', desc: 'Sub-Agenten starten', cat: 'System' },
|
||||
{ id: 'AskUserQuestion', name: 'AskUserQuestion', desc: 'Rueckfragen stellen', cat: 'Kommunikation' },
|
||||
];
|
||||
|
||||
let permState = {}; // { toolId: true/false }
|
||||
let permsDirty = false;
|
||||
let permsSavedMsg = ''; // Gespeicherte Erfolgsmeldung (ueberlebt renderPermissions)
|
||||
|
||||
function loadPermissions() {
|
||||
const grid = document.getElementById('perms-grid');
|
||||
grid.innerHTML = '<div style="color:#8888AA;padding:12px;">Lade Berechtigungen...</div>';
|
||||
send({ action: 'list_permissions' });
|
||||
}
|
||||
|
||||
function renderPermissions(data) {
|
||||
const grid = document.getElementById('perms-grid');
|
||||
const status = document.getElementById('perms-status');
|
||||
|
||||
if (data.error) {
|
||||
status.style.display = 'block';
|
||||
status.style.color = '#FF6B6B';
|
||||
status.textContent = 'Fehler: ' + data.error;
|
||||
} else if (data.info) {
|
||||
status.style.display = 'block';
|
||||
status.style.color = '#8888AA';
|
||||
status.textContent = data.info;
|
||||
} else {
|
||||
status.style.display = 'none';
|
||||
}
|
||||
|
||||
// Merge: Server-Daten als Basis
|
||||
permState = {};
|
||||
const serverPerms = data.permissions || {};
|
||||
const hasExplicitList = Array.isArray(data.allowedTools) && data.allowedTools.length > 0;
|
||||
for (const tool of TOOL_DEFS) {
|
||||
if (hasExplicitList) {
|
||||
permState[tool.id] = data.allowedTools.includes(tool.id);
|
||||
} else if (serverPerms[tool.id] !== undefined) {
|
||||
permState[tool.id] = !!serverPerms[tool.id];
|
||||
} else {
|
||||
permState[tool.id] = true; // Default: an
|
||||
}
|
||||
}
|
||||
// Hinweis wenn kein expliziter Filter gesetzt ist
|
||||
if (!hasExplicitList && !Object.keys(serverPerms).length) {
|
||||
status.style.display = 'block';
|
||||
status.style.color = '#FFD60A';
|
||||
status.textContent = (data.info || '') + ' — Kein Tool-Filter gesetzt: alle Tools sind erlaubt. Speichern um explizit zu setzen.';
|
||||
}
|
||||
// Debug-Info anzeigen (Settings-Pfade)
|
||||
if (data.debug) {
|
||||
const dbg = document.createElement('div');
|
||||
dbg.style.cssText = 'font-size:9px;color:#555570;margin-top:6px;font-family:monospace;white-space:pre-line;';
|
||||
dbg.textContent = (data.debug.allPaths || '?') + '\nKeys: ' + (data.debug.rawKeys || 'keine');
|
||||
if (hasExplicitList) dbg.textContent += '\nallowedTools: [' + data.allowedTools.join(', ') + ']';
|
||||
status.appendChild(dbg);
|
||||
}
|
||||
|
||||
// Gruppieren nach Kategorie
|
||||
const cats = {};
|
||||
for (const tool of TOOL_DEFS) {
|
||||
if (!cats[tool.cat]) cats[tool.cat] = [];
|
||||
cats[tool.cat].push(tool);
|
||||
}
|
||||
|
||||
let html = '';
|
||||
for (const [cat, tools] of Object.entries(cats)) {
|
||||
html += `<div style="grid-column:1/-1;font-size:11px;color:#0096FF;margin-top:8px;text-transform:uppercase;letter-spacing:1px;">${escapeHtml(cat)}</div>`;
|
||||
for (const tool of tools) {
|
||||
const checked = permState[tool.id] ? 'checked' : '';
|
||||
html += `<div class="perm-item">`
|
||||
+ `<div class="perm-info"><div class="perm-name">${escapeHtml(tool.name)}</div><div class="perm-desc">${escapeHtml(tool.desc)}</div></div>`
|
||||
+ `<label class="toggle"><input type="checkbox" ${checked} onchange="togglePerm('${tool.id}', this.checked)"><span class="slider"></span></label>`
|
||||
+ `</div>`;
|
||||
}
|
||||
}
|
||||
grid.innerHTML = html;
|
||||
permsDirty = false;
|
||||
document.getElementById('btn-save-perms').disabled = true;
|
||||
|
||||
// Gespeicherte Erfolgsmeldung nach Reload wieder anzeigen
|
||||
if (permsSavedMsg) {
|
||||
status.style.display = 'block';
|
||||
status.style.color = '#34C759';
|
||||
status.innerHTML = permsSavedMsg;
|
||||
permsSavedMsg = ''; // Nur einmal anzeigen
|
||||
}
|
||||
}
|
||||
|
||||
function togglePerm(toolId, enabled) {
|
||||
permState[toolId] = enabled;
|
||||
permsDirty = true;
|
||||
document.getElementById('btn-save-perms').disabled = false;
|
||||
}
|
||||
|
||||
function savePermissions() {
|
||||
if (!permsDirty) return;
|
||||
const allowedTools = Object.entries(permState).filter(([_, v]) => v).map(([k]) => k);
|
||||
send({ action: 'save_permissions', allowedTools });
|
||||
document.getElementById('btn-save-perms').disabled = true;
|
||||
}
|
||||
|
||||
function restartAriaSession() {
|
||||
if (!confirm('Aktive Session neu starten? Der Kontext bleibt erhalten, Permissions werden neu geladen.')) return;
|
||||
send({ action: 'restart_session' });
|
||||
const s = document.getElementById('perms-status');
|
||||
s.style.color = '#FFD60A';
|
||||
s.textContent = 'Session wird neu gestartet...';
|
||||
}
|
||||
// Granulare Permissions entfernt — Claude Code laeuft mit
|
||||
// --dangerously-skip-permissions (Alles oder Nichts).
|
||||
// Siehe docker-compose.yml: CLAUDE_CODE_BUBBLEWRAP=1
|
||||
|
||||
// ── Einstellungen: Model ────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -990,10 +990,7 @@ wss.on("connection", (ws) => {
|
|||
} else if (msg.action === "read_brain_file") {
|
||||
handleReadBrainFile(ws, msg.filename);
|
||||
// ── Einstellungen ──
|
||||
} else if (msg.action === "list_permissions") {
|
||||
handleListPermissions(ws);
|
||||
} else if (msg.action === "save_permissions") {
|
||||
handleSavePermissions(ws, msg.allowedTools);
|
||||
// list_permissions / save_permissions entfernt — Alles-oder-Nichts via --dangerously-skip-permissions
|
||||
} else if (msg.action === "get_model") {
|
||||
handleGetModel(ws);
|
||||
} else if (msg.action === "set_model") {
|
||||
|
|
@ -1448,171 +1445,9 @@ async function handleReadBrainFile(clientWs, filename) {
|
|||
}
|
||||
|
||||
// ── Einstellungen: Tool-Berechtigungen ──────────────────
|
||||
|
||||
const OPENCLAW_SETTINGS_PATHS = [
|
||||
"/home/node/.openclaw/settings.json",
|
||||
"/home/node/.openclaw/agents/main/agent/settings.json",
|
||||
"/home/node/.openclaw/config.json",
|
||||
"/home/node/.claude/settings.json", // Claude Code User-Level
|
||||
"/home/node/.openclaw/workspace/.claude/settings.json", // Claude Code Projekt-Level
|
||||
];
|
||||
|
||||
async function findSettingsFile() {
|
||||
// Pruefen welche Settings-Dateien existieren
|
||||
try {
|
||||
const raw = await dockerExec("aria-core", `
|
||||
for f in ${OPENCLAW_SETTINGS_PATHS.join(" ")}; do
|
||||
[ -f "$f" ] && echo "FOUND:$f"
|
||||
done
|
||||
`.trim());
|
||||
for (const line of raw.split("\n")) {
|
||||
if (line.startsWith("FOUND:")) return line.slice(6);
|
||||
}
|
||||
} catch {}
|
||||
// Default: erster Pfad (wird erstellt)
|
||||
return OPENCLAW_SETTINGS_PATHS[0];
|
||||
}
|
||||
|
||||
async function handleListPermissions(clientWs) {
|
||||
try {
|
||||
log("info", "server", "Lade Tool-Berechtigungen...");
|
||||
|
||||
// Alle moeglichen Settings-Dateien pruefen (Debug-Info)
|
||||
let allPaths = "";
|
||||
try {
|
||||
allPaths = await dockerExec("aria-core", `
|
||||
for f in ${OPENCLAW_SETTINGS_PATHS.join(" ")}; do
|
||||
if [ -f "$f" ]; then
|
||||
echo "EXISTS: $f ($(wc -c < "$f") bytes)"
|
||||
else
|
||||
echo "MISSING: $f"
|
||||
fi
|
||||
done
|
||||
`.trim());
|
||||
} catch {}
|
||||
|
||||
const settingsPath = await findSettingsFile();
|
||||
let settings = {};
|
||||
let rawContent = "";
|
||||
let info = "";
|
||||
|
||||
try {
|
||||
rawContent = await dockerExec("aria-core", `cat '${settingsPath}' 2>/dev/null || echo '{}'`);
|
||||
settings = JSON.parse(rawContent.trim() || "{}");
|
||||
info = `Geladen aus: ${settingsPath}`;
|
||||
} catch (e) {
|
||||
info = `Settings-Datei nicht lesbar (${settingsPath}) — Default-Berechtigungen`;
|
||||
}
|
||||
|
||||
// OpenClaw/Claude Code Format: allowedTools ist ein Array von Tool-Namen
|
||||
// Wenn leer/nicht vorhanden: alle Tools erlaubt
|
||||
const allowedTools = settings.allowedTools || [];
|
||||
const permissions = settings.permissions || {};
|
||||
|
||||
clientWs.send(JSON.stringify({
|
||||
type: "permissions_list",
|
||||
allowedTools,
|
||||
permissions,
|
||||
settingsPath,
|
||||
info,
|
||||
debug: { allPaths: allPaths.trim(), rawKeys: Object.keys(settings).join(", ") },
|
||||
}));
|
||||
log("info", "server", `Berechtigungen geladen (${allowedTools.length} Tools explizit erlaubt) aus ${settingsPath}`);
|
||||
if (allPaths) log("info", "server", `Settings-Dateien:\n${allPaths}`);
|
||||
} catch (err) {
|
||||
log("error", "server", `Berechtigungen laden fehlgeschlagen: ${err.message}`);
|
||||
clientWs.send(JSON.stringify({ type: "permissions_list", error: err.message, allowedTools: [], permissions: {} }));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSavePermissions(clientWs, allowedTools) {
|
||||
if (!Array.isArray(allowedTools)) {
|
||||
clientWs.send(JSON.stringify({ type: "permissions_saved", ok: false, error: "Ungueltige Daten" }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log("info", "server", `Speichere ${allowedTools.length} Tool-Berechtigungen in alle Settings-Pfade`);
|
||||
|
||||
// In ALLE moeglichen Pfade schreiben, in BEIDEN Formaten:
|
||||
// 1. allowedTools: ["Bash", "Read", ...] — OpenClaw Format
|
||||
// 2. permissions.allow: ["Bash(*)", "Read(*)", ...] — Claude Code Format
|
||||
const script = [
|
||||
'const fs=require("fs");const path=require("path");',
|
||||
`const paths=${JSON.stringify(OPENCLAW_SETTINGS_PATHS)};`,
|
||||
`const tools=${JSON.stringify(allowedTools)};`,
|
||||
// Claude Code Format: Tool-Namen mit (*) Glob-Pattern
|
||||
'const ccAllow=tools.map(t=>t+"(*)");',
|
||||
'const ok=[];const fail=[];',
|
||||
'for(const f of paths){try{',
|
||||
'let s={};try{s=JSON.parse(fs.readFileSync(f,"utf8"));}catch(e){}',
|
||||
's.allowedTools=tools;',
|
||||
'if(!s.permissions)s.permissions={};',
|
||||
's.permissions.allow=ccAllow;',
|
||||
's.permissions.deny=[];',
|
||||
'const dir=path.dirname(f);',
|
||||
'try{fs.mkdirSync(dir,{recursive:true});}catch(e){}',
|
||||
'fs.writeFileSync(f,JSON.stringify(s,null,2));',
|
||||
'ok.push(f);',
|
||||
'}catch(e){fail.push(f+": "+e.message);}}',
|
||||
// Verify: nur erfolgreiche Pfade zuruecklesen
|
||||
'const result={ok:[],fail:fail,verified:{}};',
|
||||
'for(const f of ok){',
|
||||
'try{const d=JSON.parse(fs.readFileSync(f,"utf8"));',
|
||||
'result.verified[f]={allowedTools:d.allowedTools||[],permsAllow:(d.permissions&&d.permissions.allow)||[]};}',
|
||||
'catch(e){result.fail.push(f+": verify-read "+e.message);}',
|
||||
'}',
|
||||
'result.ok=ok;',
|
||||
'process.stdout.write(JSON.stringify(result));',
|
||||
].join("");
|
||||
const b64 = Buffer.from(script).toString("base64");
|
||||
const verifyRaw = await dockerExec("aria-core", `echo ${b64} | base64 -d | node`);
|
||||
|
||||
// Verify: gespeicherte Daten pruefen
|
||||
let verifyResult = {};
|
||||
let verified = false;
|
||||
let verifyDetails = [];
|
||||
try {
|
||||
verifyResult = JSON.parse(verifyRaw.trim());
|
||||
// Mindestens ein Pfad muss erfolgreich sein
|
||||
verified = verifyResult.ok && verifyResult.ok.length > 0;
|
||||
for (const f of (verifyResult.fail || [])) {
|
||||
verifyDetails.push(`FEHLER: ${f}`);
|
||||
}
|
||||
for (const [fpath, data] of Object.entries(verifyResult.verified || {})) {
|
||||
const ok = data.allowedTools.length === allowedTools.length
|
||||
&& allowedTools.every(t => data.allowedTools.includes(t));
|
||||
const ccOk = data.permsAllow.length === allowedTools.length;
|
||||
verifyDetails.push(`${fpath}: allowedTools=${ok?"OK":"FEHLER"} permissions.allow=${ccOk?"OK":"FEHLER"} (${data.allowedTools.length}/${data.permsAllow.length} Tools)`);
|
||||
if (!ok) verified = false;
|
||||
}
|
||||
} catch (e) {
|
||||
verifyDetails.push(`Parse-Fehler: ${e.message} — Raw: ${verifyRaw.substring(0, 200)}`);
|
||||
}
|
||||
log("info", "server", `Verify:\n${verifyDetails.join("\n")}`);
|
||||
|
||||
if (!verified) {
|
||||
clientWs.send(JSON.stringify({
|
||||
type: "permissions_saved", ok: false,
|
||||
error: `Verify fehlgeschlagen:\n${verifyDetails.join("\n")}`,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const okCount = (verifyResult.ok || []).length;
|
||||
const failCount = (verifyResult.fail || []).length;
|
||||
const infoMsg = `${allowedTools.length} Tools gespeichert (${okCount} Pfade OK` + (failCount ? `, ${failCount} fehlgeschlagen` : '') + ')';
|
||||
clientWs.send(JSON.stringify({
|
||||
type: "permissions_saved", ok: true,
|
||||
info: infoMsg,
|
||||
details: verifyDetails,
|
||||
needsRestart: true,
|
||||
}));
|
||||
log("info", "server", infoMsg);
|
||||
} catch (err) {
|
||||
log("error", "server", `Berechtigungen speichern fehlgeschlagen: ${err.message}`);
|
||||
clientWs.send(JSON.stringify({ type: "permissions_saved", ok: false, error: err.message }));
|
||||
}
|
||||
}
|
||||
// ENTFERNT: Granulare Permissions haben nie funktioniert.
|
||||
// Claude Code laeuft mit --dangerously-skip-permissions (Alles oder Nichts).
|
||||
// Root-Check wird via CLAUDE_CODE_BUBBLEWRAP=1 in docker-compose.yml umgangen.
|
||||
|
||||
// ── Einstellungen: Model ────────────────────────────────
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue