Passa al contenuto principale

Mascheramento credenziali nella sandbox: token che funzionano, ma restano segreti

Avanzato
What you'll learn
  • Quando preferire mask a deny — e quando deny resta più sicuro
  • Le quattro modalità di masking: intero valore, extract, decode: "jwt" con maskClaims, e awsPairs per SigV4
  • Il JSON esatto per mascherare env var, JWT, credenziali AWS e file come ~/.config/gh/hosts.yml
  • Perché tlsTerminate è obbligatorio — e il pattern di fallimento silenzioso quando manca
  • La regola sulla sorgente delle settings: perché le voci mask vengono ignorate da .claude/settings.json
  • Divisione Linux/WSL vs macOS: il mascheramento file su macOS degrada in deny

La maggior parte dei leak di segreti non avviene perché un token è stato rubato — avviene perché uno script ben intenzionato lo stampa in un log, in un diff, o nella trascrizione di un subagente. Il mascheramento delle credenziali nella sandbox è la soluzione integrata di Claude Code: i comandi nella sandbox vedono un valore sentinella per-sessione al posto del segreto reale, e il proxy della sandbox sostituisce il valore reale sulle richieste in uscita verso gli host che autorizzi. Il comando si autentica lo stesso. Il comando e tutto ciò che logga non contengono mai la credenziale reale.

Questa pagina è la guida operativa: le quattro modalità di masking, il JSON esatto, le insidie e la matrice OS.

Mask vs deny: quale ti serve?

Due modi per tenere una credenziale fuori dalle mani di un comando in sandbox. Sembrano simili. Non lo sono.

"mode": "deny""mode": "mask"
Env varRimossa dall'ambiente della sandboxImpostata su una sentinella per-sessione
FileLettura fallisceLa sandbox vede una copia sentinella (Linux/WSL2) o la lettura fallisce (macOS)
Tool che ha bisogno del segretoSi rompe (gh, npm, aws falliscono senza token)Funziona comunque — il proxy inserisce il valore reale sul filo
Valore reale mai nella sandbox?MaiMai (solo sentinella; il proxy tiene il valore reale)
Richiede tlsTerminateNo — il proxy deve vedere il contenuto della richiesta per sostituire
Onorato dalle settings del repo?No — solo user, managed o --settings

Regola pratica. Usa deny quando il tool non ha bisogno della credenziale e la vuoi eliminare. Usa mask quando il tool deve autenticarsi — vuoi che gh pr view funzioni senza mai lasciare che la trascrizione, un subagente o un errante env contengano il vero GH_TOKEN.

Prerequisito: tlsTerminate

mask funziona sostituendo la sentinella con il valore reale dentro header e body delle richieste HTTP in uscita. Il proxy della sandbox deve vedere quei byte, quindi network.tlsTerminate è obbligatorio. Senza, il masking fallisce nel modo peggiore: il comando vede solo la sentinella, la sentinella raggiunge il server invariata, e l'autenticazione fallisce. Claude Code segnala questa misconfigurazione all'avvio — leggi gli warning.

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

Ogni voce injectHosts che userai dopo deve comparire anche in network.allowedDomains. Se un host non è permesso, il proxy non vede mai la richiesta per sostituire.

Mascheramento di variabili d'ambiente

Il caso base. Una voce envVars per credenziale.

Maschera GH_TOKEN e NPM_TOKEN

{
  "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 restringe la sostituzione a host specifici. GH_TOKEN non raggiungerà mai nulla oltre api.github.com.
  • Ometti injectHosts e il valore reale viene sostituito sulle richieste verso ogni host in network.allowedDomains. Va bene per NPM_TOKEN dove il token è già limitato al registry.
  • Per confermare che il mask sia attivo: chiedi a Claude di eseguire echo "$GH_TOKEN" in un comando sandbox. L'output deve essere una sentinella per-sessione, non il token reale.

Extract: maschera un solo campo dentro un valore strutturato

Molte "credenziali" non sono un segreto puro — sono una connection string con la password sepolta dentro. extract maschera solo il capture group della tua regex, lasciando leggibile il resto così i parser continuano a funzionare.

Maschera la password dentro DATABASE_URL, mantieni il resto parsabile

{
  "name": "DATABASE_URL",
  "mode": "mask",
  "extract": "://[^:]+:([^@]+)@"
}
  • Il pattern deve contenere almeno un capture group; viene sostituito solo il testo del gruppo 1.
  • onExtractNoMatch controlla cosa succede quando il pattern non matcha nulla: warn (default — passa non mascherato con warning), deny (fail closed), o error (fa fallire la sandbox). Usa deny quando il segreto deve essere sempre presente.

Mascheramento JWT con decode e maskClaims

Per access token a forma di JWT (header.payload.signature), il masking del valore intero rompe qualsiasi codice dentro la sandbox che decodifica il token per guardare i claim. decode: "jwt" risolve: Claude Code verifica che il valore sia un JWT valido e ci sostituisce un token fake strutturalmente valido, così jwt.decode(...) dentro la sandbox restituisce comunque un payload ben formato.

Maschera un JWT di sessione mantenendone la forma decodificabile

{
  "name": "SESSION_JWT",
  "mode": "mask",
  "decode": "jwt",
  "maskClaims": ["sub", "email"]
}
  • Senza maskClaims, l'intero token fake sostituisce quello reale — al codice che legge solo iss o aud non interessa, ma quello che legge sub ottiene un valore fake.
  • Con maskClaims, gli altri claim restano leggibili; solo quelli che elenchi vengono sostituiti individualmente. Utile quando l'app ha bisogno di iat/exp/iss per il routing ma non deve mai vedere sub/email.
  • decode non può essere combinato con extract sulla stessa voce. Scegli.
  • Se il valore non si verifica come JWT (o nessun claim elencato matcha), Claude Code lo lascia passare non mascherato con un warning. Usa onExtractNoMatch: "deny" per fail closed.

Richiede Claude Code v2.1.224 o successivo.

AWS SigV4: maschera le chiavi insieme con awsPairs

AWS è il caso complicato. Le richieste SigV4 portano una firma HMAC sul contenuto della richiesta, calcolata dalla chiave secret. Se maschera il secret ma non l'access key ID, il proxy non ha modo di riconoscere quale richiesta sia AWS — la richiesta esce firmata con la sentinella, AWS la rifiuta, e ottieni fallimenti confusi. Maschera sempre l'access key ID e il secret insieme.

Buona notizia: per i nomi convenzionali AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN, Claude Code li collega automaticamente quando tutti e tre sono voci mask a valore intero. Il proxy riconosce una richiesta SigV4 dalla sentinella dell'access key e la ri-firma dopo aver sostituito i valori reali.

Se le tue credenziali AWS vivono in nomi di variabile non convenzionali, raggruppale a mano con awsPairs.

Raggruppa variabili AWS non standard per la ri-firma SigV4

{
  "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"
        }
      ]
    }
  }
}
  • Ogni variabile nominata deve essere una voce mask che maschera l'intero valore — no extract, no decode.
  • sessionTokenVar è opzionale; quando impostato, il proxy invia il token reale come x-amz-security-token sulle richieste ri-firmate.
  • Richiede Claude Code v2.1.224 o successivo.

Quando il proxy non può ri-firmare: credentials.sigv4

Tre forme di richiesta AWS portano firme che il proxy non può ricalcolare — chunked payload signing, presigned URL e firme asimmetriche SigV4A. Di default il proxy fa fallire queste richieste piuttosto che inoltrare una firma rotta. Se uno specifico tool si basa su una di esse e preferisci vedere il rifiuto di AWS piuttosto che un errore del proxy, rilassa quella forma con credentials.sigv4:

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

Impostare una forma su passthrough inoltra invariata la richiesta firmata col placeholder così il tool chiamante riceve la risposta di AWS. Riguarda solo le richieste firmate col placeholder di una coppia mascherata — le richieste firmate con credenziali non mascherate non vengono mai toccate. Anche questo v2.1.224+ e ristretto per sorgente settings.

Mascheramento file: mascherare una credenziale su disco

Alcuni tool tengono il token in un file di config, non in una env var (gh in ~/.config/gh/hosts.yml, docker in ~/.docker/config.json, un SDK in ~/.netrc). Il mascheramento file dà al processo in sandbox una copia sentinella del file su Linux e WSL2. Su macOS il file masking degrada a deny — il file è illeggibile dentro la sandbox.

Maschera la riga oauth_token dentro ~/.config/gh/hosts.yml

{
  "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"]
        }
      ]
    }
  }
}
  • Il pattern extract è ciò che mantiene leggibile il resto di hosts.yml. Senza, Claude Code sostituisce l'intero contenuto del file con una sentinella — va bene per un file che contiene solo un segreto e nient'altro, ma rompe qualunque parser che si aspetti struttura.
  • Per un file che contiene un JWT, aggiungi decode: "jwt" (con maskClaims opzionale) per mantenere la forma del token decodificabile dentro la sandbox.
  • maskDuplicates: true sostituisce anche copie letterali del valore mascherato trovate fuori dagli span matchati. Riservalo a segreti lunghi ad alta entropia — un valore corto verrebbe sostituito ovunque appaia.
  • Elenca ogni file di credenziali individualmente. mask degrada a deny per un path di directory, un glob pattern, un file più grande di 8 MiB, o un file non UTF-8.

Matrice OS

FeatureLinuxWSL2macOS
Env var mask
File mask — copia sentinellaNo (degrada a deny)
extract / decode / maskClaims per fileSolo quando l'isolamento del filesystem è disattivato

Su macOS, le voci mask sui file vengono applicate come deny prima che il pattern venga eseguito, ogni volta che l'isolamento del filesystem è attivo. Per ottenere il comportamento extract/decode su macOS devi disattivare l'isolamento del filesystem — un tradeoff più grande di quanto la maggior parte dei team voglia accettare.

La regola della sorgente delle settings (questa frega tutti)

Le voci mask autorizzano il proxy della sandbox a inviare la tua credenziale reale agli host che elenchi. È una delega di fiducia. Claude Code la impone onorando le seguenti chiavi solo dalle scope di settings che tu o il tuo amministratore controllate — user settings, managed settings, o flag CLI --settings. Vengono silenziosamente ignorate dal .claude/settings.json o .claude/settings.local.json di un repository:

  • voci mode: "mask" (env var e file)
  • network.tlsTerminate
  • credentials.allowPlaintextInject (permette al proxy di iniettare in richieste non cifrate)
  • awsPairs
  • sigv4

Impatto pratico. Non puoi spedire un .claude/settings.json in un repo condiviso che attivi il masking per i compagni di squadra. Ognuno deve mettere le voci mask nelle proprie user settings, oppure un admin deve spingerle via managed settings. È voluto — un repo che hai clonato non dovrebbe poter comandare la sandbox di spedire il tuo GH_TOKEN a evil.example.com.

Insidie comuni

Watch out
  • Nessun tlsTerminate → mask fallisce silenziosamente. La sandbox vede la sentinella; la sentinella va al server; l'auth fallisce. Controlla gli warning di startup.
  • injectHosts deve comparire in network.allowedDomains, altrimenti il proxy non vede mai la richiesta per sostituire.
  • AWS: mascherare solo il secret (non l'access key ID) fa sì che il proxy non riconosca la richiesta. Mascherali insieme, o usa awsPairs.
  • Il .claude/settings.json a livello repo è IGNORATO per mask/tlsTerminate/awsPairs/sigv4. Mettili in user o managed settings.
  • File mask su macOS diventa deny. Se la tua app deve leggere il file, o disattivi l'isolamento filesystem o lavori su Linux/WSL2.
  • extract senza capture group è un errore di config — il pattern deve contenere il gruppo 1.
  • decode: "jwt" ed extract non possono combinarsi sulla stessa voce — scegli.
  • File mask degrada a deny per: path di directory, glob pattern, file > 8 MiB, o file non-UTF-8. Spezza le directory in voci per-file.

Configurazione raccomandata

Un ragionevole punto di partenza "cintura e bretelle" per un laptop di sviluppo che esegue sessioni Claude Code contro GitHub, npm e AWS:

{
"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" }
]
}
}
}

Note sulla forma:

  • GH_TOKEN e NPM_TOKEN sono mascherati e limitati con injectHosts.
  • Il trio AWS convenzionale è mascherato; Claude Code li collega automaticamente per la ri-firma SigV4, nessun awsPairs serve.
  • Le API key degli LLM sono deny — nessun processo in sandbox dovrebbe averne bisogno, e se le lasciassi accessibili un subagente fuori controllo potrebbe bruciarti il budget.
  • ~/.aws/credentials e ~/.ssh sono deny-listati come directory (per questo sono deny, non mask — il masking non gestisce directory).
  • Va nel tuo settings.json user, non nel repo.

Check yourself

0/3
  1. Il tuo team spedisce un .claude/settings.json nel repo con voci mask per GH_TOKEN. I compagni clonano ed eseguono Claude Code. Cosa succede?
  2. Maschera AWS_SECRET_ACCESS_KEY ma non AWS_ACCESS_KEY_ID. Cosa si rompe?
  3. Aggiungi una voce mask ma dimentichi network.tlsTerminate. Cosa succede davvero a runtime?

Prossimi