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
+95
View File
@@ -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