Zum Hauptinhalt springen

Sandbox Credential Masking: Tokens funktionsfähig halten und geheim halten

Experte
What you'll learn
  • Wann mask gegenüber deny zu bevorzugen ist — und wann deny noch sicherer ist
  • Die vier Masking-Modi: whole-value, extract, decode: "jwt" mit maskClaims und awsPairs für SigV4
  • Das exakte JSON, um Umgebungsvariablen, JWTs, AWS-Anmeldedaten und Dateien wie ~/.config/gh/hosts.yml zu maskieren
  • Warum tlsTerminate zwingend erforderlich ist — und das Silent-Fail-Muster, wenn es fehlt
  • Die Settings-Source-Regel: warum mask-Einträge aus .claude/settings.json ignoriert werden
  • Der Linux/WSL- vs. macOS-Split: Datei-Masking wird auf macOS zu deny herabgestuft

Die meisten Geheimnis-Leaks passieren nicht, weil ein Token gestohlen wurde — sondern weil ein gut gemeintes Skript eins in ein Log, ein Diff oder das Transcript eines Subagenten ausgedruckt hat. Sandbox Credential Masking ist der eingebaute Fix von Claude Code: sandboxed Befehle sehen einen Session-spezifischen Sentinel-Wert statt des echten Geheimnisses, und der Sandbox-Proxy ersetzt den echten Wert bei ausgehenden Anfragen an Hosts, die du erlaubst. Der Befehl authentifiziert sich weiterhin. Der Befehl und alles, was er loggt, halten nie die echten Anmeldedaten.

Diese Seite ist der Praktikerleitfaden: die vier Masking-Modi, das exakte JSON, die Fallstricke und die OS-Matrix.

Mask vs. deny: welches willst du?

Zwei Wege, um Anmeldedaten aus den Händen eines sandboxed Befehls zu halten. Sie sehen ähnlich aus. Sie sind es nicht.

"mode": "deny""mode": "mask"
UmgebungsvariableAus der Sandbox-Umgebung entferntAuf einen Session-spezifischen Sentinel gesetzt
DateiLesen schlägt fehlSandbox sieht eine Sentinel-Kopie (Linux/WSL2) oder Lesen schlägt fehl (macOS)
Tool, das das Geheimnis benötigtBricht (gh, npm, aws schlagen ohne Token fehl)Funktioniert weiter — Proxy tauscht den echten Wert auf der Leitung ein
Echter Wert je in der Sandbox?NieNie (nur Sentinel; Proxy hält den echten Wert)
Erfordert tlsTerminateNeinJa — Proxy muss den Request-Inhalt sehen, um zu ersetzen
Aus Repo-Einstellungen berücksichtigt?JaNein — nur user, managed oder --settings

Faustregel. Verwende deny, wenn das Tool die Anmeldedaten nicht braucht und du sie loswerden willst. Verwende mask, wenn das Tool sich authentifizieren muss — du willst, dass gh pr view funktioniert, ohne dass das Transcript, ein Subagent oder ein irrtümlicher env-Dump je das echte GH_TOKEN hält.

Voraussetzung: tlsTerminate

mask funktioniert, indem der Sentinel durch den echten Wert in den ausgehenden HTTP-Request-Headers und -Bodies ersetzt wird. Der Sandbox-Proxy muss diese Bytes sehen, also ist network.tlsTerminate zwingend erforderlich. Ohne es schlägt Masking auf die schlimmste Art fehl: der Befehl sieht weiterhin nur den Sentinel, der Sentinel erreicht den Server unverändert und die Authentifizierung schlägt fehl. Claude Code meldet diese Fehlkonfiguration beim Start — lies die Warnungen.

{
"sandbox": {
"network": {
"tlsTerminate": {},
"allowedDomains": ["api.github.com", "registry.npmjs.org"]
}
}
}

Jeder injectHosts-Eintrag, den du später verwendest, muss auch in network.allowedDomains erscheinen. Wenn ein Host nicht erlaubt ist, sieht der Proxy die Anfrage nie, um zu ersetzen.

Masking von Umgebungsvariablen

Der Grundfall. Ein envVars-Eintrag pro Anmeldedaten.

GH_TOKEN und NPM_TOKEN maskieren

{
  "sandbox": {
    "network": {
      "tlsTerminate": {},
      "allowedDomains": ["api.github.com", "registry.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "GH_TOKEN", "mode": "mask", "injectHosts": ["api.github.com"] },
        { "name": "NPM_TOKEN", "mode": "mask" }
      ]
    }
  }
}
  • injectHosts beschränkt die Ersetzung auf bestimmte Hosts. GH_TOKEN erreicht nie etwas anderes als api.github.com.
  • Lässt du injectHosts weg, wird der echte Wert bei Anfragen an jeden Host in network.allowedDomains ersetzt. Für NPM_TOKEN in Ordnung, wo das Token auf die Registry beschränkt ist.
  • Um zu bestätigen, dass die Maske aktiv ist: bitte Claude, echo "$GH_TOKEN" in einem sandboxed Befehl auszuführen. Die Ausgabe sollte ein Session-spezifischer Sentinel sein, nicht das echte Token.

Extract: ein Feld innerhalb eines strukturierten Wertes maskieren

Viele "Anmeldedaten" sind kein blankes Geheimnis — sie sind ein Verbindungsstring mit einem darin vergrabenen Passwort. extract maskiert nur die Capture-Gruppe deines Regex und lässt den Rest lesbar, damit Parser weiter funktionieren.

Das Passwort innerhalb von DATABASE_URL maskieren, den Rest parsebar halten

{
  "name": "DATABASE_URL",
  "mode": "mask",
  "extract": "://[^:]+:([^@]+)@"
}
  • Das Muster muss mindestens eine Capture-Gruppe enthalten; nur der Text von Gruppe 1 wird ersetzt.
  • onExtractNoMatch steuert, was passiert, wenn das Muster nichts matcht: warn (Standard — unmaskiert durchgereicht mit Warnung), deny (fail closed) oder error (Sandbox fehlschlagen lassen). Verwende deny, wenn das Geheimnis immer vorhanden sein sollte.

JWT-Masking mit decode und maskClaims

Für Access-Tokens in JWT-Form (header.payload.signature) bricht Whole-Value-Masking jeden Code innerhalb der Sandbox, der das Token dekodiert, um Claims anzusehen. decode: "jwt" behebt das: Claude Code verifiziert, dass der Wert ein gültiges JWT ist, und tauscht ein strukturell gültiges Fake-Token ein, sodass jwt.decode(...) innerhalb der Sandbox weiterhin einen wohlgeformten Payload zurückgibt.

Ein Session-JWT maskieren, aber die Form dekodierbar halten

{
  "name": "SESSION_JWT",
  "mode": "mask",
  "decode": "jwt",
  "maskClaims": ["sub", "email"]
}
  • Ohne maskClaims ersetzt das komplette Fake-Token das echte — Code, der nur iss oder aud braucht, wird es egal sein, aber Code, der sub liest, bekommt einen Fake-Wert.
  • Mit maskClaims bleiben die anderen Claims lesbar; nur die aufgelisteten werden einzeln ersetzt. Nützlich, wenn die App iat/exp/iss fürs Routing braucht, aber sub/email nie sehen darf.
  • decode kann nicht mit extract auf demselben Eintrag kombiniert werden. Wähle eins.
  • Wenn der Wert nicht als JWT verifiziert (oder kein gelisteter Claim matcht), reicht Claude Code ihn unmaskiert mit Warnung durch. Verwende onExtractNoMatch: "deny", um fail closed zu erzwingen.

Erfordert Claude Code v2.1.224 oder neuer.

AWS SigV4: Schlüssel gemeinsam mit awsPairs maskieren

AWS ist der knifflige Fall. SigV4-Anfragen tragen eine HMAC-Signatur über den Request-Inhalt, berechnet aus dem Secret Key. Wenn du den Secret maskierst, aber nicht die Access Key ID, hat der Proxy keine Möglichkeit zu erkennen, welche Anfrage AWS ist — die Anfrage geht mit dem Sentinel signiert raus, AWS lehnt sie ab und du bekommst verwirrende Fehler. Maskiere die Access Key ID und den Secret immer zusammen.

Die gute Nachricht: für die konventionellen Variablennamen AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN verknüpft Claude Code sie automatisch, wenn alle drei Whole-Value-mask-Einträge sind. Der Proxy erkennt eine SigV4-Anfrage am Sentinel des Access Keys und signiert sie nach dem Ersetzen der echten Werte neu.

Wenn deine AWS-Anmeldedaten in nicht-konventionellen Variablennamen leben, gruppiere sie selbst mit awsPairs.

Nicht-Standard AWS-Variablen für SigV4-Re-Signing gruppieren

{
  "sandbox": {
    "credentials": {
      "envVars": [
        { "name": "MY_KEY_ID", "mode": "mask" },
        { "name": "MY_SECRET_KEY", "mode": "mask" },
        { "name": "MY_SESSION_TOKEN", "mode": "mask" }
      ],
      "awsPairs": [
        {
          "accessKeyIdVar": "MY_KEY_ID",
          "secretAccessKeyVar": "MY_SECRET_KEY",
          "sessionTokenVar": "MY_SESSION_TOKEN"
        }
      ]
    }
  }
}
  • Jede benannte Variable muss ein mask-Eintrag sein, der ihren gesamten Wert maskiert — kein extract, kein decode.
  • sessionTokenVar ist optional; wenn gesetzt, sendet der Proxy das echte Token als x-amz-security-token bei re-signierten Anfragen.
  • Erfordert Claude Code v2.1.224 oder neuer.

Wenn der Proxy nicht re-signieren kann: credentials.sigv4

Drei AWS-Request-Formen tragen Signaturen, die der Proxy nicht neu berechnen kann — Chunked Payload Signing, Presigned URLs und SigV4A asymmetrische Signaturen. Standardmäßig lässt der Proxy diese fehlschlagen, statt eine kaputte Signatur weiterzuleiten. Wenn ein bestimmtes Tool auf einer davon basiert und du lieber die AWS-eigene Ablehnung als einen Proxy-Fehler sehen willst, lockere diese Form mit credentials.sigv4:

{
"sandbox": {
"credentials": {
"sigv4": {
"presignedUrl": "passthrough",
"chunkedPayload": "passthrough",
"sigv4a": "passthrough"
}
}
}
}

Wird eine Form auf passthrough gesetzt, wird die placeholder-signierte Anfrage unverändert weitergeleitet, damit das aufrufende Tool die AWS-Antwort erhält. Betrifft nur Anfragen, die mit dem Platzhalter eines maskierten Paars signiert sind — Anfragen mit unmaskierten Anmeldedaten werden nie angerührt. Ebenfalls v2.1.224+ und settings-source-beschränkt.

Datei-Masking: Anmeldedaten auf der Platte maskieren

Manche Tools speichern ihr Token in einer Config-Datei, nicht in einer Umgebungsvariablen (gh in ~/.config/gh/hosts.yml, docker in ~/.docker/config.json, ein SDK in ~/.netrc). Datei-Masking gibt dem sandboxed Prozess auf Linux und WSL2 eine Sentinel-Kopie der Datei. Auf macOS fällt Datei-Masking auf deny zurück — die Datei ist innerhalb der Sandbox nicht lesbar.

Die oauth_token-Zeile innerhalb von ~/.config/gh/hosts.yml maskieren

{
  "sandbox": {
    "network": {
      "tlsTerminate": {},
      "allowedDomains": ["api.github.com"]
    },
    "credentials": {
      "files": [
        {
          "path": "~/.config/gh/hosts.yml",
          "mode": "mask",
          "extract": "oauth_token:\\s*(\\S+)",
          "injectHosts": ["api.github.com"]
        }
      ]
    }
  }
}
  • Das extract-Muster hält den Rest von hosts.yml lesbar. Ohne es ersetzt Claude Code den gesamten Dateiinhalt durch einen Sentinel — okay für eine Datei, die nur ein blankes Geheimnis enthält, aber bricht jeden Parser, der Struktur erwartet.
  • Für eine Datei mit einem JWT füge decode: "jwt" hinzu (mit optionalem maskClaims), um die Token-Form innerhalb der Sandbox dekodierbar zu halten.
  • maskDuplicates: true ersetzt außerdem wortwörtliche Kopien des maskierten Werts, die außerhalb der gematchten Spans gefunden werden. Reserviere das für lange, hoch-entropische Geheimnisse — ein kurzer Wert würde überall ersetzt, wo er erscheint.
  • Liste jede Anmeldedaten-Datei einzeln auf. mask fällt auf deny zurück für einen Verzeichnispfad, ein Glob-Muster, eine Datei größer als 8 MiB oder eine Datei, die nicht UTF-8-Text ist.

OS-Matrix

FeatureLinuxWSL2macOS
Env-Var maskJaJaJa
Datei-mask — Sentinel-KopieJaJaNein (fällt auf deny zurück)
extract / decode / maskClaims für DateienJaJaNur wenn Dateisystem-Isolation aus ist

Auf macOS werden mask-Datei-Einträge als deny angewendet, bevor das Muster läuft, sobald Dateisystem-Isolation aktiv ist. Um das Extract/Decode-Verhalten auf macOS zu bekommen, musst du Dateisystem-Isolation deaktivieren — was ein größerer Tradeoff ist, als die meisten Teams eingehen wollen.

Die Settings-Source-Regel (das erwischt alle)

mask-Einträge autorisieren den Sandbox-Proxy, deine echten Anmeldedaten an die von dir gelisteten Hosts zu senden. Das ist eine Delegation von Vertrauen. Claude Code erzwingt das, indem folgende Keys nur aus Settings-Scopes berücksichtigt werden, die du oder dein Administrator kontrollieren — User Settings, Managed Settings oder --settings CLI-Flag. Sie werden aus .claude/settings.json oder .claude/settings.local.json eines Repositories still ignoriert:

  • mode: "mask"-Einträge (Env-Vars und Dateien)
  • network.tlsTerminate
  • credentials.allowPlaintextInject (erlaubt dem Proxy, in unverschlüsselte Anfragen zu injizieren)
  • awsPairs
  • sigv4

Praktische Auswirkung. Du kannst keine .claude/settings.json in einem geteilten Repo ausliefern, die Masking für Teamkollegen aktiviert. Jeder Teamkollege muss die Mask-Einträge in seine eigenen User Settings stellen, oder ein Admin muss sie über Managed Settings pushen. Das ist by design — ein Repo, das du geklont hast, sollte nicht der Sandbox befehlen können, dein GH_TOKEN an evil.example.com zu mailen.

Häufige Fallstricke

Watch out
  • Kein tlsTerminate → mask schlägt still fehl. Sandbox sieht Sentinel; Sentinel geht an den Server; Auth schlägt fehl. Prüfe die Start-Warnungen.
  • injectHosts muss in network.allowedDomains erscheinen, sonst sieht der Proxy die Anfrage nie, um zu ersetzen.
  • AWS: nur den Secret zu maskieren (nicht die Access Key ID) bedeutet, dass der Proxy die Anfrage nicht erkennen kann. Maskiere beide zusammen oder verwende awsPairs.
  • Repo-Level .claude/settings.json wird für mask/tlsTerminate/awsPairs/sigv4 IGNORIERT. Setze sie in User oder Managed Settings.
  • Datei-mask auf macOS wird zu deny. Wenn deine App die Datei lesen muss, deaktiviere Dateisystem-Isolation oder laufe auf Linux/WSL2.
  • extract ohne Capture-Gruppe ist ein Config-Fehler — das Muster muss Gruppe 1 enthalten.
  • decode: "jwt" und extract können nicht auf demselben Eintrag kombiniert werden — wähle eins.
  • Datei-mask fällt auf deny zurück bei: Verzeichnispfaden, Glob-Mustern, Dateien > 8 MiB oder Nicht-UTF-8-Dateien. Zerlege Verzeichnisse in Einträge pro Datei.

Empfohlene Konfiguration

Ein vernünftiger "Belt-and-Braces"-Ausgangspunkt für einen Entwickler-Laptop, der Claude Code Sessions gegen GitHub, npm und AWS laufen lässt:

{
"sandbox": {
"network": {
"tlsTerminate": {},
"allowedDomains": [
"api.github.com",
"registry.npmjs.org",
"*.amazonaws.com"
]
},
"credentials": {
"envVars": [
{ "name": "GH_TOKEN", "mode": "mask", "injectHosts": ["api.github.com"] },
{ "name": "NPM_TOKEN", "mode": "mask", "injectHosts": ["registry.npmjs.org"] },
{ "name": "AWS_ACCESS_KEY_ID", "mode": "mask" },
{ "name": "AWS_SECRET_ACCESS_KEY", "mode": "mask" },
{ "name": "AWS_SESSION_TOKEN", "mode": "mask" },
{ "name": "ANTHROPIC_API_KEY", "mode": "deny" },
{ "name": "OPENAI_API_KEY", "mode": "deny" }
],
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
]
}
}
}

Anmerkungen zur Form:

  • GH_TOKEN und NPM_TOKEN sind maskiert und mit injectHosts beschränkt.
  • Das konventionelle AWS-Trio ist maskiert; Claude Code verknüpft sie automatisch für SigV4-Re-Signing, kein awsPairs nötig.
  • LLM-API-Keys sind deny-ed: kein sandboxed Prozess sollte sie je brauchen, und wenn du sie zugänglich lässt, könnte ein außer Kontrolle geratener Subagent dein Budget verbrennen.
  • ~/.aws/credentials und ~/.ssh sind als Verzeichnisse deny-gelistet (deshalb sind sie deny, nicht mask — Masking behandelt keine Verzeichnisse).
  • Gehört in deine User settings.json, nicht ins Repo.

Check yourself

0/3
  1. Dein Team liefert eine .claude/settings.json im Repo mit mask-Einträgen für GH_TOKEN aus. Teamkollegen klonen und starten Claude Code. Was passiert?
  2. Du maskierst AWS_SECRET_ACCESS_KEY aber nicht AWS_ACCESS_KEY_ID. Was bricht?
  3. Du fügst einen mask-Eintrag hinzu, vergisst aber network.tlsTerminate. Was passiert tatsächlich zur Laufzeit?

Weiter