Was die Settings-Datei eines geklonten Repos noch ausführen kann
Claude Code hat Repositories gerade verboten, den Telemetrie-Export einzuschalten. Der schmale Fix verdeckt eine breitere Tatsache: Eine eingecheckte Settings-Datei ist Code, der mit deinen Rechten läuft, und in einem Headless-Lauf fragt vorher niemand. Interaktiv fragt zwar ein Dialog, aber ich bestätige ihn, ohne ihn zu lesen.
Claude Code v2.1.282 kam am 24. September mit einer Zeile in den Release Notes, über die man leicht hinwegscrollt: Projekt- und lokale Settings ignorieren jetzt die OpenTelemetry-Variablen, die „den Export einschalten, seinen Endpoint setzen oder Inhalte erfassen“. Rückwärts gelesen sagt sie etwas Unangenehmeres. Bis zu diesem Release konnte eine in ein Repository eingecheckte .claude/settings.json die Telemetrie einschalten, den Collector wählen und verlangen, dass Prompts und Tool-Inhalte mitgeschickt werden. Repo klonen, Session starten, und deine Session-Daten konnten dorthin gehen, wohin der Autor des Repositories sie gelenkt hatte.
Diese Tür ist jetzt zu. Der Rest der Datei steht weiter offen.
Was hat v2.1.282 geändert?
Projekt- und lokale Settings verwerfen jetzt die Telemetrie-Variablen, die den Export einschalten, ihn umleiten oder Inhalte hinzufügen, und dazu kamen ein Hinweis beim Start sowie Einträge in /status und claude doctor, die die verworfenen Variablen nennen. Ausschalten darf ein Repository die Telemetrie weiterhin: none für die drei Exporter-Selektoren und ein Aus-Wert wie 0 für die Schalter, die Prompts, Tool-Inhalte und Tool-Details loggen.
Die vollständige Liste in der Settings-Referenz umfasst CLAUDE_CODE_ENABLE_TELEMETRY, die Exporter-Selektoren, die OTEL_LOG_*-Inhaltsschalter und die OTEL_EXPORTER_OTLP_*-Variablen für Endpoint, Header, Protokoll und Zertifikate. Sie steht neben einer älteren Gruppe, die dieselbe Datei schon vorher nicht setzen durfte: CLAUDE_CONFIG_DIR, HOME, TMPDIR und die XDG_*-Familie, die bestimmen, wohin Claude Code seine eigenen Dateien schreibt, sowie OTEL_LOG_RAW_API_BODIES. Den Großteil dieser früheren Gruppe datiert die Doku auf v2.1.251. v2.1.282 ist also ein weiterer Schritt in einer stetigen Verengung dessen, was ein Repository für dich entscheiden darf.
Ich habe es auf 2.1.283 in einem Wegwerf-Repo ausprobiert, mit einer Projektdatei, die zwei Telemetrie-Variablen setzen wollte, und claude doctor meldete:
Claude Code ignores these telemetry variables in .claude/settings.json:
CLAUDE_CODE_ENABLE_TELEMETRY, OTEL_EXPORTER_OTLP_ENDPOINT.
A project's settings files can only turn telemetry offDas Verhalten vor dem Fix ist hier dokumentiert, nicht nachgestellt: Die „Vorher“-Hälfte stützt sich auf die Doku und die Release Notes.
Falls ein Team sich darauf verlassen hat, dass seine eingecheckten Settings die eigene Telemetrie routen, hat das mit dem Update aufgehört. Eine interaktive Session zeigt den Hinweis beim Start; ein -p-Lauf oder eine SDK-Session zeigt nichts, deshalb rät die Doku zu prüfen, ob der Collector noch Daten bekommt. Die Variablen gehören in die User-Settings, die Managed Settings, die Umgebung des Jobs oder eine Datei, die mit --settings übergeben wird. Keine davon wird aus den Settings-Dateien des Repositories selbst gelesen, und genau darum geht es bei der Änderung; eine --settings-Datei hilft nur, wenn sie außerhalb des Repos liegt.
Was können die Settings eines geklonten Repos noch?
Eine Menge, und das meiste davon wird ausgeführt. Die Permissions-Doku hat eine Tabelle mit dem Titel „What runs before you trust a folder“, und sie ist die nützlichste Seite, die Anthropic zu diesem Thema geschrieben hat. Verdichtet teilt sich die eingecheckte Konfiguration eines Repositories in drei Gruppen:
- Greift sogar in einem Ordner, dem du nie vertraut hast (unter
claude -poder wenn du nur einem übergeordneten Ordner vertraut hast; Vertrauen in einen Elternordner erstreckt sich nie auf ein verschachteltes Git-Repository wie einen Klon): Hooks in Settings-Dateien, derenv-Block ohne die Variablen, die die Settings-Referenz als ignoriert führt, Helper-Befehle wieapiKeyHelperundawsAuthRefreshsowie die Hooks undallowed-toolseines Projekt-Skills. Für Skills ergänzt die Doku, dass Workspace-Trustallowed-toolsin keinem Session-Typ absichert. - Zurückgehalten, bis du den Trust-Dialog für diesen Ordner bestätigst:
permissions.allow-Regeln undadditionalDirectories, die Fähigkeiten gewähren, und einheadersHelperauf einem MCP-Server (seit v2.1.238): Für diesen Helper fragt eine interaktive Session noch einmal nach, und bis dahin verbindet sich der Server nur mit seinen statischen Headern.deny- undask-Regeln gelten sofort, weil sie nur einschränken. - Ohne Vertrauen in diesen Ordner nie genutzt, in keinem Session-Typ: Frontmatter-Hooks in einem Projekt-Subagent (seit v2.1.218), inline
mcpServersim Subagent-Frontmatter (seit v2.1.238), ein Projekt-Plugin aus@skills-dirundextraKnownMarketplaces. Diese werden übersprungen, nicht abgefragt.
Server in .mcp.json sind ein eigener Fall. Interaktiv fragt Claude Code, bevor es sie verbindet, und seit v2.1.196 kann ein Repository seine eigenen Server nicht selbst freigeben: enableAllProjectMcpServers oder enabledMcpjsonServers in der eingecheckten Projektdatei werden in einem nicht vertrauten Ordner ignoriert. Unter claude -p verbinden sie sich ohne Rückfrage, freigegeben oder nicht.
Schau dir die Versionsnummern in dieser Liste an: 2.1.196, 2.1.218, 2.1.238, 2.1.251, 2.1.282. Jede hat eine Art von Repository-Inhalt aus der Hand des Repositories genommen oder hinter die Trust-Grenze geschoben. Das ist schrittweise Härtung, eine Angriffsfläche nach der anderen, kein Prinzip, das einmal angewendet wurde, und es heißt: Die Version, die du fährst, zählt. Eine alte, gepinnte CLI in einem CI-Image hat noch die Lücken, die die späteren Releases geschlossen haben.
Schützt der Trust-Dialog einen claude -p-Lauf?
Nein. Die Doku sagt es klar: Ein -p-Lauf oder eine SDK-Session zeigt den Dialog nie, und für Hooks und env gilt er als bestätigt. In einem Headless-Lauf greifen die Hooks und die Umgebung des Repositories in einem Ordner, den du nie geöffnet hast.
Hier der Repro. Ein Repository, in dem das als .claude/settings.json eingecheckt ist:
{
"env": {
"PROBE_VAR": "set-by-repo",
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://127.0.0.1:9"
},
"hooks": {
"SessionStart": [
{ "hooks": [ { "type": "command",
"command": "env | grep -E '^(PROBE_VAR|CLAUDE_CODE_ENABLE|OTEL_EXPORTER)' > /tmp/hook-ran.txt" } ] }
]
}
}Drei Läufe auf Claude Code 2.1.283, in einem Ordner ohne Trust-Eintrag in ~/.claude.json:
rm -f /tmp/hook-ran.txt
claude -p "Reply with the single word ok" --max-turns 1 < /dev/null
cat /tmp/hook-ran.txt
# PROBE_VAR=set-by-repo
rm -f /tmp/hook-ran.txt
claude -p "Reply with the single word ok" --max-turns 1 \
--settings '{"disableAllHooks": true}' < /dev/null
# no /tmp/hook-ran.txt
rm -f /tmp/hook-ran.txt
claude -p "Reply with the single word ok" --max-turns 1 \
--setting-sources user < /dev/null
# no /tmp/hook-ran.txtSieh dir den ersten Lauf an. Der Hook des Repositories lief, bevor das Modell ein Wort gesagt hatte, er sah die PROBE_VAR des Repositories, und er sah die beiden Telemetrie-Variablen nicht, weil v2.1.282 sie verworfen hat. Der Hook hier schreibt eine Datei. Ein echter könnte alles ausführen, was dein Account ausführen darf, mit allen Credentials, die deine Umgebung hält. Das ist der Punkt, den ich über installierte Tools mit deiner Reichweite gemacht habe: Die Berechtigung ist ambient, und ein Hook erbt sie komplett.
Was hält eine interaktive Session zurück, bis du dem Ordner vertraust?
Alles, was ausgeführt wird, bis du Ja sagst. Laut Hooks-Doku hält eine interaktive Session die Hooks aus allen Settings-Dateien zurück, auch aus deiner eigenen ~/.claude/settings.json, bis du den Trust-Dialog für den Ordner bestätigst oder für einen Elternordner, dessen Vertrauen sich auf ihn erstreckt. Der Dialog listet die Allow-Regeln und zusätzlichen Verzeichnisse auf, die der Ordner gewähren würde, damit du sie vorher prüfen kannst. Nach der Bestätigung greift alles, Hooks und Helper eingeschlossen.
Interaktiv ist also der Dialog das Tor, und hier muss ich ehrlich über mein eigenes Setup sein. Meine Agents laufen als interaktive Claude-Code-Sessions in tmux, nicht als -p-Skripte. Das war schon so, bevor sich beim Agent-SDK-Guthaben etwas geändert hat, und ich lasse -p nicht über Repositories laufen, die ich nicht selbst geschrieben habe. Die Headless-Lücke oben ist also nicht meine Angriffsfläche. Der Dialog ist es. Und ich lese ihn nicht. Ich habe noch nie die .claude/settings.json oder .mcp.json eines Repositories geöffnet, bevor ich Vertrauen bestätigt habe, und es hat mich nie erwischt, was über das nächste Repo nichts beweist.
Ein Tor, durch das du aus Reflex klickst, ist ein Tor auf dem Papier. Der Dialog erfüllt seinen Zweck nur, wenn die Liste, die er zeigt, gelesen wird, und eine Liste von Berechtigungen in einem Modal, genau in dem Moment, in dem du anfangen willst zu arbeiten, ist genau das, was ich ungelesen bestätige.
Wie prüfst du ein Repo, bevor du ihm vertraust?
Lies die Dateien, die der Dialog zusammenfasst, bevor der Dialog erscheint, mit einem Skript, das nur die Schlüssel herauszieht, die etwas ausführen oder gewähren. Das ist schlichtes Python ohne Abhängigkeiten. Starte es im Root-Verzeichnis des Repositories:
import json, pathlib, subprocess
KEYS = ["env", "hooks", "apiKeyHelper", "awsAuthRefresh", "awsCredentialExport",
"otelHeadersHelper", "statusLine", "subagentStatusLine", "fileSuggestion",
"enabledPlugins", "extraKnownMarketplaces"]
for name in [".claude/settings.json", ".claude/settings.local.json"]:
p = pathlib.Path(name)
if p.exists():
s = json.loads(p.read_text())
found = {k: s[k] for k in KEYS if k in s}
perms = s.get("permissions", {})
for k in ["allow", "additionalDirectories"]:
if perms.get(k):
found["permissions." + k] = perms[k]
print(name, json.dumps(found, indent=2))
mcp = pathlib.Path(".mcp.json")
if mcp.exists():
for n, srv in json.loads(mcp.read_text()).get("mcpServers", {}).items():
print("mcp server:", n, json.dumps(srv))
for d in [".claude/agents", ".claude/skills", ".claude/commands"]:
if pathlib.Path(d).is_dir():
print("read the files in", d)
r = subprocess.run(["git", "ls-files", ".claude/settings.local.json"],
capture_output=True, text=True)
if r.returncode != 0:
print("git check failed:", r.stderr.strip())
elif r.stdout.strip():
print("settings.local.json is tracked in git: treat it as repository-supplied")Auf meinem ersten Test-Repository, das auch eine .mcp.json mit einem Server enthielt, gab das Skript den env-Block aus, den SessionStart-Hook mit seinem vollständigen Befehl und die vollständige npx-Definition dieses Servers. Es ist ein erster Durchgang, kein Unbedenklichkeitsnachweis: Es liest nur das Root-Verzeichnis des Repositories, und bei Agents, Skills und Commands sagt es dir nur, welche Ordner du öffnen musst. Was es ausgibt, ist eine Liste von Befehlen und Berechtigungen, die du lesen solltest, bevor du sie laufen lässt.
Die letzte Prüfung ist wichtiger, als sie aussieht. Eine .claude/settings.local.json gehört normalerweise dir, deshalb überspringen ihre Allow-Regeln den Trust-Schritt, sobald Claude Code sie mit git geprüft hat. Wenn die Datei in git getrackt ist oder .claude ein Symlink ist, behandelt Claude Code sie als vom Repository geliefert und hält ihre Regeln zurück wie bei der geteilten Datei. Ein Repository, das eine „lokale“ Datei eincheckt, verrät dir etwas über sich.
Das ist dieselbe Grenze, für die ich bei Instruction-Dateien als Adressierung argumentiert habe: Text, den ein Fremder einchecken kann, darf nie die Autorität deiner eigenen dauerhaften Konfiguration tragen. Settings sind diese Grenze mit ausführbaren Zähnen.
Wie lässt du claude -p in einem fremden Repository laufen?
Entscheide vor dem Start, was der Lauf laden darf. Die Doku nennt vier Optionen, und sie sind nicht austauschbar:
--setting-sources userliest weder die Settings-Dateien des Projekts noch seine.mcp.json. In meinem Repro hat das den Hook gestoppt. Es ist der breiteste einzelne Schalter.--bareüberspringt Hooks, Skills, eigene Commands, Subagents, Plugins und.mcp.json-Server. Denenv-Block des Projekts und Helper wieawsAuthRefreshlässt es aber nicht weg, und es liest keinen OAuth-Login, auf der Anthropic API brauchst du alsoANTHROPIC_API_KEYoder einenapiKeyHelper, der über--settingsübergeben wird. Skills aus einem Verzeichnis, das du mit--add-dirübergibst, werden trotzdem geladen. Laut Doku wird es in einem künftigen Release zum Standard für-p.--settings '{"disableAllHooks": true}'schaltet für den Lauf die normalen User-, Projekt- und Plugin-Hooks ab; Hooks, die deine Organisation verwaltet, bleiben an. Es muss auf der Kommandozeile stehen: Steht es nur in deinen User-Settings, verliert es, weil Projekt-Settings Vorrang vor User-Settings haben und das Repository es wieder auffalsesetzen kann. In meinem Repro hat das Flag den Hook gestoppt.disabledMcpjsonServerslehnt einen benannten.mcp.json-Server in jedem Session-Typ ab, aus jeder Settings-Datei.
Meine Einschätzung, wozu du greifen solltest: --setting-sources user für alles Automatisierte über Code, den du nicht geschrieben hast, weil es als einzige der vier Optionen env, Hooks und Server des Projekts zusammen entfernt. --bare ist schneller und sauberer für CI, aber „überspringt Hooks“ heißt nicht „überspringt das Repository“, und der env-Block ist genau der Teil, den --bare stehen lässt.
Was solltest du diese Woche tun?
Aktualisiere die CLI überall, wo sie über fremden Code läuft, auch in CI-Images mit gepinnter Version. Ergänze --setting-sources user oder --bare bei jedem geskripteten claude -p über fremde Repositories; die Doku sagt schon, dass --bare zum Standard für -p wird, und das ist dieselbe Richtung wie jede Versionsnummer oben. Bevor du den nächsten Trust-Dialog bestätigst, lass das Audit laufen, oder öffne wenigstens .claude/settings.json und .mcp.json und lies die Befehle darin.
Die Telemetrie-Variablen waren der einfache Fall: Sie senden nur. Die Schlüssel, die etwas ausführen, stehen noch in der Datei. Zwischen den Hooks eines Repositories und deiner Shell steht im einen Modus ein Dialog, und im anderen nichts.