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:
2026-03-15 19:46:56 +01:00
parent 1afb47c49c
commit 62d5d73c74
3 changed files with 160 additions and 321 deletions
+4 -169
View File
@@ -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 ────────────────────────────────