User Guide
Credential Vault
The credential vault stores user API keys (GitHub tokens, Slack bot tokens, etc.) encrypted at rest. Credentials are decrypted only at MCP spawn time and immediately zeroed from memory.
Why a credential vault?
Without MCP Ambassador, every developer copies their API keys into local MCP configs — dotfiles, .env files, shell profiles. Keys accumulate on laptops, in CI configs, and in version control.
With MCP Ambassador:
- Users store their keys once in the encrypted vault
- Keys are injected at spawn time as environment variables to the MCP process
- Keys never leave the server unencrypted
- When a user unsubscribes, their key is deleted
- When a user's account is deactivated, their keys are inaccessible
Encryption
Each credential is encrypted using AES-256-GCM:
plaintext credential
│
▼
HKDF-SHA256(master_key, user_id + credential_name)
│ derived_key (per-user, per-credential)
▼
AES-256-GCM encrypt(derived_key, plaintext)
│
▼
{ ciphertext, iv, auth_tag } ← stored in database
Key derivation: HKDF (HMAC-based Key Derivation Function) derives a unique encryption key for each credential using the CREDENTIAL_MASTER_KEY and the user+credential identifier as the info parameter. This means:
- Two users with the same API key produce different ciphertexts
- Rotating the master key (future feature) invalidates all credentials cleanly
- An attacker with the database but not the master key cannot decrypt
Authentication: AES-GCM provides authenticated encryption — tampering with ciphertext is detected.
Master key
The CREDENTIAL_MASTER_KEY is a 32-byte (64-character hex) key. In development mode, it is auto-generated on first boot and saved to ./data/credential_master_key (permissions 0600). In production, set it explicitly:
# Generate
openssl rand -hex 32
# Set in docker-compose.yml environment
CREDENTIAL_MASTER_KEY=your_64_char_hex_key
Back up this key. If you lose it, all stored credentials are unrecoverable — they cannot be decrypted without it.
Memory security
After spawning a downstream MCP process:
- Credentials are retrieved from the vault (decrypted in memory)
- Passed as environment variables to the child process
- Immediately zeroed from the server process's memory using
Buffer.fill(0)
The window in which plaintext credentials exist in the server process is minimized to the spawn operation.
Credential lifecycle
| Event | What happens |
|---|---|
| User subscribes to MCP | Credentials encrypted and stored |
| MCP process spawns | Credentials decrypted, injected as env vars, zeroed |
| User updates credentials | Old ciphertext overwritten |
| User unsubscribes | Credentials deleted from database |
| User account deactivated | Credentials remain encrypted; master key required to recover |
| Admin deletes user | Credentials permanently deleted |
What is stored
Each credential record contains:
user_id— ownermcp_id— which MCP this credential is forname— environment variable name (e.g.,GITHUB_PERSONAL_ACCESS_TOKEN)ciphertext,iv,auth_tag— the encrypted credentialcreated_at,updated_at— timestamps
Credential names (environment variable names) are stored in plaintext — only the values are encrypted.
User access
Users manage their own credentials via:
- User Portal at
https://your-server:9443→ My Subscriptions → select subscription → Edit Credentials - Client API:
PUT /v1/subscriptions/:idwith updated credentials
Admins cannot read or view user credentials — only users with access to their own subscriptions can provide or update them. Admins can see that a credential is configured (name + timestamp) but not the value.
Production recommendations
- Set
CREDENTIAL_MASTER_KEYexplicitly — do not rely on the auto-generated key - Store the master key in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
- Back up
./data/ambassador.dbregularly — it contains all encrypted credentials - Rotate the master key periodically (tooling planned for v2.0)