Skip to content

Secret Obfuscation

Prevents sensitive values (API keys, tokens, passwords) from being sent to LLM providers. When enabled, secrets are replaced with deterministic placeholders before leaving the process, and restored in tool call arguments returned by the model.

Enabled by default. Toggle via /settings UI or directly in config.yml:

secrets:
enabled: false
  1. On session startup, secrets are collected from two sources:

    • Environment variables matching common secret patterns (*_KEY, *_SECRET, *_TOKEN, *_PASSWORD, etc.) with values >= 8 characters
    • secrets.yml files (see below)
  2. Outbound messages to the LLM have all secret values replaced with placeholders like <<$env:S0>>, <<$env:S1>>, etc.

  3. Tool call arguments returned by the model are deep-walked and placeholders are restored to original values before execution.

Two modes control what happens to each secret:

ModeBehaviorReversible
obfuscate (default)Replaced with indexed placeholder <<$env:SN>>Yes (deobfuscated in tool args)
replaceReplaced with deterministic same-length stringNo (one-way)

Define custom secret entries in YAML. Two locations are checked:

LevelPathPurpose
Global~/.xcsh/agent/secrets.ymlSecrets across all projects
Project<cwd>/.xcsh/secrets.ymlProject-specific secrets

Project entries override global entries with matching content.

Each entry in the array has these fields:

FieldTypeRequiredDescription
type"plain" or "regex"YesMatch strategy
contentstringYesThe secret value (plain) or regex pattern (regex)
mode"obfuscate" or "replace"NoDefault: "obfuscate"
replacementstringNoCustom replacement (replace mode only)
flagsstringNoRegex flags (regex type only)
# Obfuscate a specific API key (default mode)
- type: plain
content: sk-proj-abc123def456
# Replace a database password with a fixed string
- type: plain
content: hunter2
mode: replace
replacement: "********"
# Obfuscate any AWS-style key
- type: regex
content: "AKIA[0-9A-Z]{16}"
# Case-insensitive match with explicit flags
- type: regex
content: "api[_-]?key\\s*=\\s*\\w+"
flags: "i"
# Regex literal syntax (pattern and flags in one string)
- type: regex
content: "/bearer\\s+[a-zA-Z0-9._~+\\/=-]+/i"

Regex entries always scan globally (the g flag is enforced automatically). The regex literal syntax /pattern/flags is supported as an alternative to separate content + flags fields. Escaped slashes within the pattern (\\/) are handled correctly.

# One-way replace connection strings (not reversible)
- type: regex
content: "postgres://[^\\s]+"
mode: replace
replacement: "postgres://***"

Environment variables are always collected first. File-defined entries are appended after, so file entries can cover secrets that don’t live in env vars (config files, hardcoded values, etc.). If the same value appears in both, the file entry’s mode takes precedence.

  • src/secrets/index.ts — loading, merging, env var collection
  • src/secrets/obfuscator.tsSecretObfuscator class, placeholder generation, message obfuscation
  • src/secrets/regex.ts — regex literal parsing and compilation
  • src/config/settings-schema.tssecrets.enabled setting definition