Credential Vault — Secure Credential Management for AI Agents
Credential Vault — Secure Credential Management for AI Agents
Section titled “Credential Vault — Secure Credential Management for AI Agents”Every organization using AI agents faces credential sprawl: API keys scattered across repos, agents, and services with no visibility into who’s using what, no rotation, no scoping, and no audit trail.
The BaselineOS Credential Vault solves this at the OS layer.
Quick Start
Section titled “Quick Start”import { CredentialVault, STANDARD_CREDENTIALS } from 'baselineos';// Or standalone: import { CredentialVault } from '@baselineos/vault';
const vault = new CredentialVault({ persistPath: '.baseline/vault', masterKey: process.env.BASELINE_VAULT_KEY!, credentials: STANDARD_CREDENTIALS, // Auto-resolves ANTHROPIC_API_KEY, etc.});await vault.initialize();
// Check what resolvedconst health = vault.getHealth();console.log(`${health.resolvedFromEnv} credentials resolved from environment`);console.log(`${health.missingFromEnv} credentials missing`);Security Model
Section titled “Security Model”Encryption at Rest
Section titled “Encryption at Rest”All credentials are encrypted with AES-256-CBC before storage:
- Random 16-byte IV per credential
- Master key derived via SHA-256 from
BASELINE_VAULT_KEYorBASELINE_MASTER_KEY - Encrypted values stored in SQLite (WAL mode)
- Raw values exist only in memory during active use
Master key is required. The vault refuses to initialize without one — no insecure defaults.
Opaque Credential Grants
Section titled “Opaque Credential Grants”Borrowed from Anthropic Managed Agents: agents can USE credentials but never READ them.
Traditional: Agent calls vault.retrieve() → gets "sk-ant-..." → uses it → keeps it in memory foreverOpaque: Agent calls requestOpaqueCapabilities() → gets handle → runner injects into child process → agent never sees the valueThe agent receives a CapabilityHandle:
{ credentialName: 'vercel-token', leaseId: 'lease-1713100800000-x7k2m1', // Opaque reference expiresAt: 1713100860000, // 60 seconds from now envVar: 'VERCEL_TOKEN', // Where runner will inject}The handle does NOT contain the raw credential value. Only the ProtocolRunner can resolve a handle to the actual value, and only at the moment of child process injection.
Time-Boxed Leases
Section titled “Time-Boxed Leases”Borrowed from HashiCorp Vault: credentials are dynamic and short-lived.
Traditional: VERCEL_TOKEN lives forever until manually rotatedLeased: VERCEL_TOKEN lease valid for 60 seconds, auto-revoked after step completionThe lease lifecycle:
1. Agent requests capability for a step2. Vault issues lease with TTL = step timeout (e.g., 60s)3. Runner resolves lease → injects into child process env4. Command executes5. Runner revokes lease immediately6. Even if agent is compromised: lease is already deadTrust-Scored Access Control
Section titled “Trust-Scored Access Control”Every credential can require a minimum trust score:
vault.store('production-deploy-key', 'secret', { type: 'api-key', minTrustScore: 85, // Only highly trusted agents provider: 'aws',});
// Agent with trust 60 → denied (logged)// Agent with trust 90 → granted (logged)Per-Agent Scoping
Section titled “Per-Agent Scoping”Credentials can be scoped to specific agents:
vault.store('analytics-key', 'secret', { type: 'api-key', scope: 'agent', allowedAccessors: ['analytics-agent', 'reporting-agent'],});
// deploy-agent → denied// analytics-agent → grantedPersistent Audit Trail
Section titled “Persistent Audit Trail”Every credential access is logged to SQLite:
const log = vault.getAccessLog({ credentialName: 'vercel-token' });// [// { accessedBy: 'deploy-agent', granted: true, reason: 'Lease issued: lease-xxx (TTL: 60s)', timestamp: ... },// { accessedBy: 'deploy-agent', granted: true, reason: 'Lease revoked: lease-xxx', timestamp: ... },// { accessedBy: 'untrusted-agent', granted: false, reason: 'Trust score 30 below required 70', timestamp: ... },// ]The audit trail persists across restarts (SQLite-backed).
defineBaseline() Integration
Section titled “defineBaseline() Integration”Define credentials as part of your organization’s baseline:
import { defineBaseline } from 'baselineos';
export default defineBaseline({ organization: { name: 'Acme Corp', industry: 'fintech', }, credentials: { anthropic: { type: 'api-key', envVar: 'ANTHROPIC_API_KEY', provider: 'anthropic', required: true, }, vercel: { type: 'bearer-token', envVar: 'VERCEL_TOKEN', provider: 'vercel', scope: 'workflow', }, database: { type: 'connection-string', envVar: 'DATABASE_URL', provider: 'neon', scope: 'agent', allowedAccessors: ['data-agent', 'migration-agent'], minTrustScore: 70, }, sentry: { type: 'api-key', envVar: 'SENTRY_DSN', provider: 'sentry', required: false, }, },});When the vault initializes with this config, it auto-resolves each credential from the corresponding environment variable.
API Reference
Section titled “API Reference”Constructor
Section titled “Constructor”new CredentialVault({ persistPath: string, // SQLite storage path masterKey?: string, // Encryption key (or from BASELINE_VAULT_KEY env) autoResolveEnv?: boolean, // Auto-resolve from env on init (default: true) credentials?: Record<...>, // Credential definitions to resolve})Core Methods
Section titled “Core Methods”| Method | Description |
|---|---|
store(name, value, options) | Store a credential (encrypted at rest) |
retrieve(name, accessor) | Retrieve a credential (access-controlled, audited) |
rotate(name, newValue) | Re-encrypt with new value |
revoke(name) | Delete a credential |
has(name) | Check if credential exists and is valid |
list() | List all credentials (metadata only, never values) |
Lease Methods
Section titled “Lease Methods”| Method | Description |
|---|---|
issueLease(name, accessor, ttlMs) | Issue a time-boxed lease |
resolveLease(leaseId) | Resolve lease to raw value (runner only) |
revokeLease(leaseId) | Revoke a lease early |
getActiveLeases() | List all active leases |
Health & Audit
Section titled “Health & Audit”| Method | Description |
|---|---|
getHealth() | Vault health summary |
getExpiring(withinMs) | Credentials expiring within a window |
getAccessLog(filter?) | Access audit trail |
Vault Health
Section titled “Vault Health”const health = vault.getHealth();// {// total: 5,// active: 4,// expired: 1,// expiringWithin7Days: 1,// unusedOver30Days: 0,// missingFromEnv: 1,// resolvedFromEnv: 3,// providers: { anthropic: 1, vercel: 1, neon: 1, sentry: 1, openai: 1 },// }Standard Credentials
Section titled “Standard Credentials”BaselineOS includes pre-configured credential definitions for common AI providers:
import { STANDARD_CREDENTIALS } from 'baselineos';
// Includes:// anthropic → ANTHROPIC_API_KEY (required)// openai → OPENAI_API_KEY (optional)// tavily → TAVILY_API_KEY (optional)// baseline-api → BASELINE_API_KEY (optional)// sentry → SENTRY_DSN (optional)Use as a starting point:
const vault = new CredentialVault({ credentials: { ...STANDARD_CREDENTIALS, // Add your own: vercel: { type: 'bearer-token', envVar: 'VERCEL_TOKEN', provider: 'vercel' }, },});How It Wires Into the Pipeline
Section titled “How It Wires Into the Pipeline”When pipeline.produce() calls an LLM, the API key is resolved through the vault:
createAnthropicGenerator({ vault, accessor: { agentId: 'my-agent' } }) ↓resolveApiKey(): vault.retrieve('anthropic', accessor) → audited ↓Falls back: options.apiKey → process.env.ANTHROPIC_API_KEY ↓Anthropic SDK initialized with resolved keyEvery LLM call goes through the vault’s access control and audit trail.
Comparison to Alternatives
Section titled “Comparison to Alternatives”| Feature | BaselineOS Vault | HashiCorp Vault | process.env | 1Password CLI |
|---|---|---|---|---|
| Encryption at rest | AES-256-CBC | AES-256-GCM | Plaintext | 1Password-managed |
| Per-agent scoping | Yes | Role-based | No | No |
| Trust scoring | Yes | No | No | No |
| Time-boxed leases | Yes | Yes (dynamic secrets) | No | No |
| Opaque grants | Yes | No | No | No |
| Persistent audit trail | SQLite | Enterprise audit | No | 1Password audit |
| Agent-aware | Yes | No | No | No |
| Convention-over-config | defineBaseline() | HCL/API | N/A | CLI |
| Zero dependencies | better-sqlite3 only | Full server | None | 1Password app |