diff --git a/diagnostic/index.html b/diagnostic/index.html index 5d8bb90..428aaec 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -654,6 +654,31 @@ +
+

Datenschutz — was Claude Code von sich aus sendet

+
+
+ Drei Kanäle, über die Claude Code zusätzlich zur eigentlichen + Anfrage Daten verschickt. Gesetzt wird in ~/.claude/settings.json — + greift bei jeder neuen Anfrage, kein Neustart nötig. +

+ Davon unabhängig ist, ob Anthropic mit deinen Unterhaltungen Modelle + trainiert — das steht in deinem Konto unter + Datenschutz → „Help improve our AI models" + und lässt sich hier nicht umstellen. +
+
+ (noch nicht geprüft) +
+
+ + +
+
+
+
+

Lokales LLM (schnelle Antworten)

@@ -2723,6 +2748,14 @@ return; } + if (msg.type === 'privacy_flags') { renderPrivacyFlags(msg); return; } + if (msg.type === 'privacy_flags_saved') { + const st = document.getElementById('privacy-flags-status'); + if (st && msg.error) { st.textContent = 'Speichern fehlgeschlagen: ' + msg.error; st.style.color = '#FF6B6B'; } + // Erfolg: der direkt folgende privacy_flags-Broadcast malt den Stand neu. + return; + } + if (msg.type === 'model_test_result') { const box = document.getElementById('model-test-result'); const btn = document.getElementById('btn-test-model'); @@ -4720,6 +4753,7 @@ loadJellyfinConfig(); refreshVoiceIdStatus(); send({ action: 'get_attachment_limit' }); + send({ action: 'get_privacy_flags' }); } else if (tab === 'brain') { loadBrainStatus(); loadBrainMemoryList(); @@ -8019,6 +8053,68 @@ } } + // ── Datenschutz-Schalter ────────────────────────────────────────── + function loadPrivacyFlags() { + const st = document.getElementById('privacy-flags-status'); + if (st) { st.textContent = 'Lese den Proxy-Container …'; st.style.color = '#8888AA'; } + send({ action: 'get_privacy_flags' }); + } + + function savePrivacyFlags() { + const flags = {}; + document.querySelectorAll('#privacy-flags-list input[type=checkbox][data-key]') + .forEach(cb => { flags[cb.dataset.key] = cb.checked; }); + if (!Object.keys(flags).length) { + const st = document.getElementById('privacy-flags-status'); + if (st) { st.textContent = 'Erst „Prüfen" drücken.'; st.style.color = '#FFD60A'; } + return; + } + send({ action: 'set_privacy_flags', flags }); + } + + function renderPrivacyFlags(msg) { + const box = document.getElementById('privacy-flags-list'); + const st = document.getElementById('privacy-flags-status'); + if (!box) return; + if (msg.error) { + box.innerHTML = ''; + if (st) { st.textContent = 'Fehler: ' + msg.error; st.style.color = '#FF6B6B'; } + return; + } + box.innerHTML = (msg.flags || []).map(f => { + // "container" = per docker-compose gesetzt. Das gewinnt gegen + // settings.json, also waere ein Haken hier wirkungslos — darum sperren + // und ehrlich sagen warum. + const locked = f.source === 'container'; + return ''; + }).join('') || '(keine Angaben)'; + + const offen = (msg.flags || []).filter(f => !f.disabled).length; + if (st) { + if (msg.bulkDisabled) { + st.textContent = 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC ist gesetzt — das schaltet ohnehin alles ab.'; + st.style.color = '#3FFF9F'; + } else if (offen === 0) { + st.textContent = '✅ Alle drei Kanäle sind zu.'; + st.style.color = '#3FFF9F'; + } else { + st.textContent = '⚠ ' + offen + ' von ' + (msg.flags || []).length + ' Kanälen senden noch. ' + + 'Haken setzen und speichern.'; + st.style.color = '#FFD60A'; + } + } + } + /** * Testet ein Modell mit einer echten Mini-Anfrage. * fromFreeText=true nimmt die handgetippte ID, sonst das Dropdown. diff --git a/diagnostic/server.js b/diagnostic/server.js index 9b9103f..221d900 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -3217,6 +3217,10 @@ wss.on("connection", (ws) => { handleSetModel(ws, msg.model); } else if (msg.action === "test_model") { handleTestModel(ws, msg.model); + } else if (msg.action === "get_privacy_flags") { + handleGetPrivacyFlags(ws); + } else if (msg.action === "set_privacy_flags") { + handleSetPrivacyFlags(ws, msg.flags); } else if (msg.action === "get_attachment_limit") { handleGetAttachmentLimit(ws); } else if (msg.action === "set_attachment_limit") { @@ -3643,6 +3647,97 @@ function handleSetModel(clientWs, model) { return; } +// ── Einstellungen: Datenschutz-Schalter von Claude Code ───────────── +// +// Drei Kanaele, ueber die Claude Code von sich aus Daten an Anthropic bzw. an +// einen Fehler-Dienst schickt. Sie liegen als Umgebungsvariablen vor und lassen +// sich in ~/.claude/settings.json unter "env" setzen — die Datei ist in den +// Proxy-Container gemountet, also koennen wir sie von hier aus pflegen, ohne +// die docker-compose.yml anzufassen. +// +// WICHTIG: gesetzt wird in settings.json. Steht dieselbe Variable ZUSAETZLICH +// als echte Container-Umgebungsvariable (docker-compose), gewinnt die — darum +// prueft "Pruefen" beides und sagt, was tatsaechlich im Prozess ankommt. +const PRIVACY_FLAGS = [ + { key: "DISABLE_ERROR_REPORTING", + label: "Fehlerberichte", + info: "Stacktraces an einen Fehler-Dienst. Bei Pro/Max ab CLI 2.1.198 standardmäßig AN." }, + { key: "DISABLE_TELEMETRY", + label: "Nutzungs-Metriken", + info: "Latenz/Zuverlässigkeit. Enthält laut Anthropic nie Code, Prompts oder Pfade." }, + { key: "DISABLE_FEEDBACK_COMMAND", + label: "/feedback-Versand", + info: "Sendet sonst auf Zuruf die ganze Unterhaltung inkl. Code — 5 Jahre Aufbewahrung." }, +]; +const CLAUDE_SETTINGS_PATH = "/root/.claude/settings.json"; + +function _shq(s) { return "'" + String(s).replace(/'/g, "'\\''") + "'"; } + +async function handleGetPrivacyFlags(clientWs) { + try { + // 1) Was kommt im PROZESS wirklich an? Das ist die Wahrheit. + const envOut = await dockerExec("aria-proxy", + "env | grep -E '^(DISABLE_ERROR_REPORTING|DISABLE_TELEMETRY|DISABLE_FEEDBACK_COMMAND|CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC)=' || true"); + const liveEnv = {}; + for (const line of String(envOut || "").split("\n")) { + const i = line.indexOf("="); + if (i > 0) liveEnv[line.slice(0, i).trim()] = line.slice(i + 1).trim(); + } + // 2) Was steht in settings.json? + const settingsRaw = await dockerExec("aria-proxy", + `cat ${CLAUDE_SETTINGS_PATH} 2>/dev/null || echo '{}'`); + let settingsEnv = {}; + try { settingsEnv = (JSON.parse(settingsRaw || "{}").env) || {}; } catch (_) {} + + const isOn = (v) => v === "1" || String(v).toLowerCase() === "true"; + const flags = PRIVACY_FLAGS.map(f => { + const fromEnv = liveEnv[f.key]; + const fromSettings = settingsEnv[f.key]; + // Container-Env gewinnt gegen settings.json. + const effective = fromEnv !== undefined ? isOn(fromEnv) : isOn(fromSettings); + return { + key: f.key, label: f.label, info: f.info, + disabled: effective, + source: fromEnv !== undefined ? "container" : (fromSettings !== undefined ? "settings" : "none"), + }; + }); + const bulk = isOn(liveEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC); + clientWs.send(JSON.stringify({ type: "privacy_flags", flags, bulkDisabled: bulk })); + } catch (err) { + clientWs.send(JSON.stringify({ type: "privacy_flags", error: String((err && err.message) || err) })); + } +} + +async function handleSetPrivacyFlags(clientWs, wanted) { + try { + const keys = PRIVACY_FLAGS.map(f => f.key); + const raw = await dockerExec("aria-proxy", + `cat ${CLAUDE_SETTINGS_PATH} 2>/dev/null || echo '{}'`); + let cfg = {}; + try { cfg = JSON.parse(raw || "{}"); } catch (_) { cfg = {}; } + if (!cfg.env || typeof cfg.env !== "object") cfg.env = {}; + for (const k of keys) { + if (wanted && wanted[k]) cfg.env[k] = "1"; + else delete cfg.env[k]; + } + if (Object.keys(cfg.env).length === 0) delete cfg.env; + + // Atomar schreiben (temp + mv), damit eine laufende CLI nie eine halbe + // Datei liest. + const json = JSON.stringify(cfg, null, 2); + await dockerExec("aria-proxy", + `printf '%s' ${_shq(json)} > ${CLAUDE_SETTINGS_PATH}.tmp && mv ${CLAUDE_SETTINGS_PATH}.tmp ${CLAUDE_SETTINGS_PATH}`); + log("info", "server", `Datenschutz-Schalter gesetzt: ${JSON.stringify(wanted || {})}`); + clientWs.send(JSON.stringify({ + type: "privacy_flags_saved", + info: "Gespeichert. Gilt für JEDE neue Anfrage — ein laufender Turn nutzt noch die alte Einstellung.", + })); + await handleGetPrivacyFlags(clientWs); + } catch (err) { + clientWs.send(JSON.stringify({ type: "privacy_flags_saved", error: String((err && err.message) || err) })); + } +} + // ── Einstellungen: Model-Test ─────────────────────────────────────── // // Schickt eine winzige ECHTE Anfrage durch den kompletten Weg