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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user