feat(diagnostic): Datenschutz-Schalter von Claude Code pruefen und setzen

Drei Kanaele, ueber die Claude Code zusaetzlich zur eigentlichen Anfrage Daten
verschickt — bisher nur per Hand in der docker-compose.yml abschaltbar, und
niemand konnte sehen, ob sie an sind:

  DISABLE_ERROR_REPORTING   Stacktraces an einen Fehler-Dienst. Bei Pro/Max ab
                            CLI 2.1.198 standardmaessig AN.
  DISABLE_TELEMETRY         Nutzungs-Metriken.
  DISABLE_FEEDBACK_COMMAND  /feedback sendet sonst die ganze Unterhaltung
                            inkl. Code — 5 Jahre Aufbewahrung.

Gesetzt wird in ~/.claude/settings.json unter "env". Die Datei ist in den
Proxy-Container gemountet, die Diagnostic erreicht sie ueber den vorhandenen
dockerExec — also kein Anfassen der docker-compose.yml und KEIN Neustart: die
CLI liest die Datei pro Aufruf.

Geprueft wird beides: was in settings.json steht UND was im Prozess wirklich
ankommt. Eine echte Container-Umgebungsvariable gewinnt gegen settings.json —
so ein Eintrag wird darum als "per docker-compose gesetzt" angezeigt und der
Haken gesperrt, statt eine Einstellung anzubieten, die nichts tut.

Geschrieben wird atomar (temp + mv) und nur der env-Block angefasst; bestehende
Einstellungen (tui, theme) bleiben. Sind alle drei abgewaehlt, verschwindet der
env-Block wieder ganz.

Der Hinweis im Panel sagt ausdruecklich, dass das NICHT die Trainings-
Einstellung ist — die steht im Anthropic-Konto und laesst sich hier nicht
umstellen. Link dorthin ist daneben.

12 Pruefungen gegen den echten Proxy-Container: lesen, setzen, Teilmengen,
JSON-Integritaet, Erhalt bestehender Werte, CLI laeuft damit, Abwaehlen raeumt
auf, Ausgangszustand wiederhergestellt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 11:21:47 +02:00
co-authored by Claude Opus 5
parent 2c60a19ed6
commit d5eea920a6
2 changed files with 191 additions and 0 deletions
+96
View File
@@ -654,6 +654,31 @@
</div>
</div>
<div class="settings-section">
<h2>Datenschutz — was Claude Code von sich aus sendet</h2>
<div class="card" style="max-width:620px;">
<div style="font-size:11px;color:#8888AA;margin-bottom:12px;line-height:1.55;">
Drei Kanäle, über die Claude Code <strong>zusätzlich</strong> zur eigentlichen
Anfrage Daten verschickt. Gesetzt wird in <code>~/.claude/settings.json</code> —
greift bei jeder neuen Anfrage, <strong>kein Neustart nötig</strong>.
<br><br>
Davon unabhängig ist, ob Anthropic mit deinen Unterhaltungen Modelle
trainiert — das steht in deinem Konto unter
<a href="https://claude.ai/settings/data-privacy-controls" target="_blank"
style="color:#0096FF;">Datenschutz → „Help improve our AI models"</a>
und lässt sich hier nicht umstellen.
</div>
<div id="privacy-flags-list" style="display:flex;flex-direction:column;gap:10px;">
<span style="color:#555570;font-size:11px;">(noch nicht geprüft)</span>
</div>
<div style="display:flex;gap:8px;margin-top:14px;flex-wrap:wrap;">
<button class="btn secondary" onclick="loadPrivacyFlags()" style="padding:5px 12px;font-size:11px;">🔍 Prüfen</button>
<button class="btn" onclick="savePrivacyFlags()" style="padding:5px 12px;font-size:11px;">Speichern</button>
</div>
<div id="privacy-flags-status" style="margin-top:10px;font-size:11px;color:#8888AA;white-space:pre-wrap;"></div>
</div>
</div>
<div class="settings-section">
<h2>Lokales LLM (schnelle Antworten) <button class="info-btn" onclick="showInfo('local-llm')" title="Wie funktioniert das?">ℹ</button></h2>
<div class="card" style="max-width:540px;">
@@ -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 '<label style="display:flex;gap:10px;align-items:flex-start;cursor:' + (locked ? 'default' : 'pointer') + ';">'
+ '<input type="checkbox" data-key="' + f.key + '"' + (f.disabled ? ' checked' : '')
+ (locked ? ' disabled' : '') + ' style="margin-top:3px;">'
+ '<span style="flex:1;">'
+ '<span style="color:' + (f.disabled ? '#3FFF9F' : '#FFD60A') + ';font-size:12px;">'
+ (f.disabled ? '✓ ' : '⚠ ') + escapeHtml(f.label) + '</span>'
+ '<span style="color:#555570;font-size:10px;"> — ' + escapeHtml(f.key) + '</span>'
+ '<br><span style="color:#8888AA;font-size:10px;">' + escapeHtml(f.info) + '</span>'
+ (locked ? '<br><span style="color:#FF9F0A;font-size:10px;">Per docker-compose gesetzt — '
+ 'das gewinnt gegen diese Einstellung und lässt sich hier nicht ändern.</span>' : '')
+ '</span></label>';
}).join('') || '<span style="color:#555570;font-size:11px;">(keine Angaben)</span>';
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.