Skip to Content
GuideCredential Vault
View .md

Credential Vault

The agent-id-vault plugin provides encrypted storage for external service credentials (GitHub, AWS, Slack, etc.). Each credential is encrypted with a key derived from the agent’s Ed25519 private key — credentials are only readable on the same machine as the bound agent.

Why a Vault

AI agents need credentials to interact with external services. Without a vault, humans end up pasting API keys into chat, hardcoding secrets in configs, or giving agents unrestricted access. The vault solves this:

  • Store once, use later — Human provides the credential once; the agent retrieves it in any future session
  • Encrypted at rest — AES-256-GCM encryption, key never leaves the agent’s machine
  • Bound to the agent key — Decryption requires the agent’s private key, so credentials are only readable where that key lives
  • Irrecoverable by design — If the agent’s keypair is deleted, the credentials are gone

Encryption Model

The encryption key is derived deterministically from the agent’s main private key, then used with AES-256-GCM and a fresh IV per write:

Agent's Ed25519 private key (raw PKCS8 DER bytes) ▼ HKDF-SHA256 (salt: "agent-id-vault-v1", info: "vault-encryption") ▼ 256-bit symmetric key ▼ AES-256-GCM (fresh 96-bit IV per write) ▼ Ciphertext + 128-bit authentication tag

The ciphertext, IV, and tag are stored hex-encoded inside an encrypted block of a per-service JSON record (see File Format).

Storing Credentials

Run the vault plugin’s store subcommand. There are several ways to provide the secret; the vault reads them in this fixed order and uses the first one present:

  1. --credential-file <path> — read from a file (most secure; never touches the command line)
  2. --credential-env <VAR> — read from an environment variable
  3. stdin — piped in, e.g. echo 'secret' | ... store ...
  4. --credential <value> — inline flag (fallback; visible in the process list)

Option A: From File (Most Secure)

The secret never appears on the command line or in chat logs:

echo 'ghp_xxx' > /tmp/tok && chmod 600 /tmp/tok node plugins/agent-id-vault/bin/cli.mjs store --service github --type api-key --credential-file /tmp/tok rm /tmp/tok

Option B: From Environment Variable

GITHUB_TOKEN=ghp_xxx node plugins/agent-id-vault/bin/cli.mjs store \ --service github --type api-key --credential-env GITHUB_TOKEN

Option C: Via Stdin Pipe

echo 'ghp_xxx' | node plugins/agent-id-vault/bin/cli.mjs store --service github

A successful store prints:

{ "ok": true, "service": "github", "type": "api-key", "updated": false }

updated is true when an existing credential for the same service was replaced.

Security Guidance

Never accept a secret pasted into chat — transcripts persist. Prefer --credential-file or --credential-env. The --credential flag puts the secret in the process argument list, where it is visible via ps.

MethodSecret in ps?In shell history?In chat log?
--credential-fileNoNoNo
--credential-envNoDepends on shellNo
stdin pipeNoThe echo line, yesNo
--credential flagYesYesNo
Paste in chatNoNoYes

Credential Types

Use --type to tag what kind of credential it is. The flag is a free-form descriptive tag — it is stored verbatim and defaults to api-key if omitted. The values below are conventions, not a validated enum:

TypeDescriptionNotes
api-keyAPI key / personal access tokenDefault
passwordUsername + password pairUse with --username
oauthOAuth access/refresh token
bearerBearer token
customAnything else

--username and --url are optional metadata hints that can be attached to any type.

Store Examples

# GitHub personal access token (from file) echo 'ghp_abc123' > /tmp/cred && chmod 600 /tmp/cred node plugins/agent-id-vault/bin/cli.mjs store --service github --type api-key --credential-file /tmp/cred rm /tmp/cred # AWS credentials (from env) node plugins/agent-id-vault/bin/cli.mjs store --service aws --type api-key \ --credential-env AWS_SECRET_ACCESS_KEY \ --username "$AWS_ACCESS_KEY_ID" \ --url "https://aws.amazon.com" # Service with username + password (piped) echo 'mypassword' | node plugins/agent-id-vault/bin/cli.mjs store --service docker-hub \ --type password --username "myuser" --url "https://hub.docker.com" # OAuth token node plugins/agent-id-vault/bin/cli.mjs store --service slack --type oauth --credential-env SLACK_BOT_TOKEN

Retrieving Credentials

node plugins/agent-id-vault/bin/cli.mjs get --service github

Returns:

{ "ok": true, "service": "github", "type": "api-key", "credential": "ghp_xxx...", "url": null, "username": null }

url and username are always present, defaulting to null when unset.

Use the credential value in API calls:

TOKEN=$(node plugins/agent-id-vault/bin/cli.mjs get --service github | jq -r .credential) curl -H "Authorization: Bearer $TOKEN" https://api.github.com/user

The credential is decrypted in memory and never written to disk in plaintext.

Listing Credentials

node plugins/agent-id-vault/bin/cli.mjs list

Returns service metadata only — the credential values are never decrypted or returned:

{ "ok": true, "credentials": [ { "service": "github", "type": "api-key", "url": null, "username": null, "createdAt": 1700000000000, "updatedAt": 1700000000000 } ] }

Removing Credentials

node plugins/agent-id-vault/bin/cli.mjs remove --service github

Updating Credentials

Run store again with the same --service name. The existing credential is replaced and the response reports "updated": true; the original createdAt timestamp is preserved while updatedAt advances.

File Format

Each credential is stored as its own JSON file under the agent state directory at vault/<service>.json (the vault/ directory is created with mode 0700; each file is written with mode 0600). The file is a metadata record wrapping the encrypted secret:

{ "version": 1, "service": "github", "type": "api-key", "url": null, "username": null, "encrypted": { "iv": "…hex…", "data": "…hex…", "tag": "…hex…" }, "createdAt": 1700000000000, "updatedAt": 1700000000000 }

Only the encrypted block holds the secret; the surrounding metadata is plaintext.

Next Steps

Last updated on