Aegis for AI Agents β Portable credential management with age encryption, session-scoped leases, and a killswitch.
AI agents need secrets: API keys, tokens, credentials. But giving an agent raw access to your password manager (and therefore all your passwords) is a terrible idea:
- Exfiltration risk β compromised agent leaks all credentials
- No audit trail β who accessed what, when?
- No revocation β can't cut off access without rotating everything
- No rotation β credentials sit forever, waiting to be compromised
agent-secrets fixes this with:
- π Age encryption β secrets never plaintext on disk
- β±οΈ Session-scoped leases β TTL-based access, auto-expires
- π Auto-rotation hooks β
gh auth refresh, custom commands - π¨ Multi-factor killswitch β revoke all + rotate all + wipe
- π Append-only audit log β every access recorded
One-liner (macOS, Linux, WSL):
curl -fsSL https://raw.githubusercontent.com/joelhooks/agent-secrets/main/install.sh | bashOr clone and install manually:
git clone https://github.com/joelhooks/agent-secrets
cd agent-secrets
make build
sudo mv secrets /usr/local/bin/Or with Go:
go install github.com/joelhooks/agent-secrets/cmd/secrets@latestAdd the OpenClaw skill (teaches your agent how to use it):
npx openclaw skills add https://github.com/joelhooks/agent-secretsVerify (self-documenting root command):
secrets# 1) Initialize store and start daemon (one-time setup)
secrets init
secrets serve &
# 2) Add secrets
secrets add github_token --rotate-via "gh auth refresh"
secrets add anthropic_key
echo "sk-ant-..." | secrets add openai_key
# 3) Discover commands and available secrets
secrets
secrets list
# 4) Lease a secret (default output is raw value, ideal for exports)
export GITHUB_TOKEN=$(secrets lease github_token)
# 5) Request JSON envelope when you need metadata + next actions
secrets lease github_token --json
# 6) Inspect state and logs
secrets status
secrets audit --tail 20
# 7) Emergency killswitch
secrets revoke --allNote: The daemon must be running for most commands to work. The install script auto-starts it, but if you see "daemon not running" errors, run
secrets serve &.
v0.5.x keeps compatibility with older scripts while moving to JSON-first behavior.
secrets lease <name>now defaults to raw value output.--rawstill works as a hidden deprecated no-op and prints a warning tostderr. Remove it from scripts beforev0.6.0.--humanand--outputare restored as hidden deprecated global flags. They are no-ops and print warnings tostderr. Remove them beforev0.6.0.- JSON envelopes now include both
okandsuccessfor one version cycle. They carry the same boolean value.
All commands return a JSON response envelope with ok, success, command, result, and next_actions, except secrets lease <name> which returns only the raw secret value by default.
# Root command returns command tree
secrets{
"ok": true,
"command": "secrets",
"result": {
"description": "Portable credential management for AI agents",
"version": "dev",
"commands": [
{"name": "init", "description": "Initialize the secrets store", "usage": "secrets init"},
{"name": "list", "description": "List stored secret names", "usage": "secrets list"},
{"name": "lease", "description": "Get a secret value (raw by default)", "usage": "secrets lease <name> [--ttl 1h] [--client-id agent-x] [--json]"}
]
},
"next_actions": [
{"command": "secrets status", "description": "Check daemon status"}
]
}# Lease envelope (opt in)
secrets lease github_token --json{
"ok": true,
"command": "secrets lease",
"result": {
"lease_id": "lease-123",
"secret_name": "github_token",
"value": "ghp_xxx",
"expires_at": "2026-02-19T12:00:00Z",
"ttl": "1h",
"client_id": "my-agent"
},
"next_actions": [
{"command": "export GITHUB_TOKEN=$(secrets lease github_token)", "description": "Export to environment"},
{"command": "secrets revoke lease-123", "description": "Revoke this lease"}
]
}{
"ok": false,
"command": "secrets lease",
"error": {
"message": "failed to acquire lease: ... secret not found",
"code": "generic_error"
},
"fix": "Check available secrets: secrets status"
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLI (cobra) β
β init | add | lease | revoke | audit | status β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β Unix Socket (JSON-RPC)
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β Daemon Process β
β ~/.agent-secrets/agent-secrets.sock β
βββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββββββββ
β β β β β
βΌ βΌ βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ βββββββββββ βββββββββββββββ
β Store β β Lease β β Audit β βRotation β β Killswitch β
β (age) β β Manager β β Log β β Hooks β β +Heartbeat β
βββββββββββ βββββββββββ βββββββββββ βββββββββββ βββββββββββββββ
Show a self-documenting command tree for agent discovery.
secretsInitialize the encrypted store. Creates:
~/.agent-secrets/identity.ageβ X25519 private key~/.agent-secrets/secrets.ageβ encrypted secrets~/.agent-secrets/config.jsonβ configuration
Add a secret to the store.
# Interactive (prompts for value)
secrets add my_secret
# With rotation hook
secrets add github_token --rotate-via "gh auth refresh"
# Pipe value from stdin
echo "secret-value" | secrets add api_key
cat credentials.txt | secrets add service_accountList stored secret names and lease-related metadata for discovery.
secrets listAcquire a time-bounded lease on a secret.
- Default output: raw secret value only (best for shell exports)
--json: full envelope with lease metadata andnext_actions
# Primary pattern: raw value for shell export
export TOKEN=$(secrets lease github_token)
# Custom TTL
export TOKEN=$(secrets lease github_token --ttl 30m)
# Custom client ID (for audit)
export TOKEN=$(secrets lease github_token --client-id "my-agent")
# JSON envelope for agents
secrets lease github_token --jsonRevoke access.
# Revoke specific lease
secrets revoke abc123
# KILLSWITCH: Revoke ALL active leases
secrets revoke --allView the append-only audit log.
# Last 50 entries (default)
secrets audit
# Last 100 entries
secrets audit --tail 100Show daemon status.
secrets status{
"ok": true,
"command": "secrets status",
"result": {
"running": true,
"secrets_count": 5,
"active_leases": 2,
"started_at": "2026-02-19T10:30:00Z",
"uptime": "1h 15m"
},
"next_actions": [
{"command": "secrets lease <name>", "description": "Get a secret value"}
]
}Generate .env file from .secrets.json config. Perfect for agentic workflows where secrets need to be loaded into a project environment.
# Generate .env from .secrets.json in current directory
secrets env
# Force overwrite existing .env
secrets env --forceHow it works:
- Reads
.secrets.jsonfrom current directory - Acquires leases for each secret listed
- Writes
KEY=valuepairs to.envfile - Sets restrictive permissions (0600)
Example .secrets.json:
{
"secrets": [
{
"name": "github_token",
"env_var": "GITHUB_TOKEN"
},
{
"name": "anthropic_key",
"env_var": "ANTHROPIC_API_KEY"
},
{
"name": "openai_key",
"env_var": "OPENAI_API_KEY",
"ttl": "30m"
}
],
"client_id": "my-project"
}Schema:
secrets(required): Array of secret mappingsname(required): Secret name in agent-secrets storeenv_var(required): Environment variable name for .env filettl(optional): Custom TTL for this secret (default: 1h)
client_id(optional): Custom client ID for audit trail (default: auto-generated)
Run a command with secrets loaded as environment variables. Combines secrets env + command execution + automatic cleanup.
# Run command with secrets loaded
secrets exec -- npm run dev
# Run tests with credentials
secrets exec -- pytest tests/
# Execute shell script
secrets exec -- ./deploy.sh
# Chain multiple commands
secrets exec -- sh -c "npm install && npm test"What it does:
- Generates temporary
.envfile from.secrets.json - Executes command with environment loaded
- Cleans up
.envfile when command exits (even on error)
Remove expired lease environment files. Run this to clean up stale .env files when leases have expired.
# Remove all expired .env files
secrets cleanup
# Check what would be cleaned (dry-run)
secrets cleanup --dry-run- All secrets encrypted at rest using age
- X25519 key pair generated on init
- Identity file permissions:
0600
- Secrets cannot be accessed directly β must acquire a lease
- Leases have mandatory TTL (max 24h by default)
- Expired leases automatically cleaned up
- Background goroutine prunes expired leases
- Every operation logged with timestamp
- Append-only format (JSONL)
- Logs: secret access, lease grants, revocations, rotations, killswitch events
revoke --allimmediately invalidates all active leases- Optional: rotate all secrets with hooks
- Optional: wipe entire store
- Optional: heartbeat monitor β auto-killswitch if remote endpoint goes down
Config stored at ~/.agent-secrets/config.json:
{
"directory": "/home/user/.agent-secrets",
"socket_path": "/home/user/.agent-secrets/agent-secrets.sock",
"default_lease_ttl": "1h",
"max_lease_ttl": "24h",
"rotation_timeout": "30s",
"heartbeat": {
"enabled": false,
"url": "https://your-endpoint.com/heartbeat",
"interval": "1m",
"timeout": "10s",
"fail_action": {
"revoke_all": true,
"rotate_all": false,
"wipe_store": false
}
}
}Once the CLI is installed globally (secrets in PATH), any AI agent with shell access can use it directly. For richer integration, install the skill documentation or platform plugins.
Works out of the box with any agent that can run shell commands.
Option 1: Direct lease (single secret)
# Lease credentials for a task
export GITHUB_TOKEN=$(secrets lease github_token --ttl 1h --client-id "claude-refactor-123")
export ANTHROPIC_API_KEY=$(secrets lease anthropic_key --ttl 1h --client-id "claude-refactor-123")
# Check status
secrets status
# Revoke when done
secrets revoke --allOption 2: Project-based workflow (.secrets.json)
# 1. Create .secrets.json in project root
cat > .secrets.json <<'EOF'
{
"secrets": [
{"name": "github_token", "env_var": "GITHUB_TOKEN"},
{"name": "anthropic_key", "env_var": "ANTHROPIC_API_KEY"},
{"name": "vercel_token", "env_var": "VERCEL_TOKEN", "ttl": "30m"}
],
"client_id": "project-deploy-task"
}
EOF
# 2. Generate .env with all secrets
secrets env
# 3. Work with credentials loaded
source .env
npm run deploy
# 4. Cleanup when done
secrets cleanupOption 3: One-shot execution
# Run command with secrets, auto-cleanup
secrets exec -- npm run deploy
secrets exec -- pytest tests/integration
secrets exec -- ./scripts/sync-prod.shBest practices:
- Use descriptive
--client-idvalues (task name, agent ID) - Match TTL to expected task duration
- Revoke leases when task completes or errors
- Use
secrets execfor one-shot commands (auto-cleanup) - Use
.secrets.jsonfor multi-secret workflows
Install the skill documentation so agents understand capabilities and usage patterns.
Option 1: Global skills (recommended)
# Install skill globally - available to all projects
mkdir -p ~/.claude/skills
cp -r agent-secrets/skills/secret-management ~/.claude/skills/
# For OpenCode
mkdir -p ~/.opencode/skills
cp -r agent-secrets/skills/secret-management ~/.opencode/skills/Option 2: Per-project skills
# Add to a specific project
mkdir -p your-project/.claude/skills
cp -r agent-secrets/skills/secret-management your-project/.claude/skills/Once installed, agents will discover the skill and know how to use the CLI:
secrets status
secrets lease github_token --ttl 1h --client-id "claude-session-123"The repo includes a full OpenClaw plugin with registered tools.
Installation
Add to your OpenClaw config (~/.openclaw/config.json):
{
"plugins": {
"load": {
"paths": ["~/path/to/agent-secrets"]
},
"entries": {
"agent-secrets": {
"enabled": true,
"config": {
"default_ttl": "1h",
"client_id_prefix": "openclaw"
}
}
}
}
}The plugin assumes secrets is in your PATH. Override with "cli_path": "/custom/path/secrets" if needed.
Registered Tools
| Tool | Description | Optional |
|---|---|---|
secrets_lease |
Acquire time-bounded credential | No |
secrets_status |
Check daemon and lease status | No |
secrets_revoke |
Revoke specific lease | No |
secrets_audit |
View audit log | No |
secrets_add |
Add new secret | Yes (allowlist) |
secrets_killswitch |
Emergency revoke all | Yes (allowlist) |
Enabling optional tools (dangerous operations require explicit allowlist):
{
"agents": {
"list": [{
"id": "main",
"tools": {
"allow": ["secrets_add", "secrets_killswitch"]
}
}]
}
}Usage in OpenClaw:
Agent: I need to access the GitHub token for this task.
[Tool: secrets_lease]
{
"name": "github_token",
"ttl": "30m",
"client_id": "openclaw-task-123"
}
β Returns: ghp_xxxxxxxxxxxx
Any agent with shell access can use the CLI directly:
# In your agent's environment setup or task preamble
export GITHUB_TOKEN=$(secrets lease github_token --ttl 1h --client-id "my-agent")
export ANTHROPIC_API_KEY=$(secrets lease anthropic_key --ttl 1h --client-id "my-agent")
# Check what's available
secrets status
# When done or on error, revoke the lease
secrets revoke $LEASE_IDBest practices for agents:
- Use descriptive
--client-idvalues (e.g.,"claude-refactor-auth-module") - Match TTL to expected task duration
- Revoke leases when task completes
- Use
secrets auditto review access patterns
# Run tests
make test
# Run with coverage
make test-cover
# Build
make build
# Install to $GOPATH/bin
make install
# Lint
make lint- filippo.io/age β Modern encryption
- github.com/spf13/cobra β CLI framework
- github.com/google/uuid β Lease IDs
This project was inspired by Alex Hillman's approach to agent credential management β the insight that agents shouldn't be anywhere near ALL your passwords. Scoped, time-bounded, audited access with a killswitch.
MIT β Use it, fork it, ship it.
Built for agents that need secrets but shouldn't keep them.