Skip to main content

Vault Plugin

Secure encrypted vault for managing secrets and credentials with AES-256-GCM encryption, session management, and dual interface (CLI tool and TokenRing service).

Overview​

The @tokenring-ai/vault package provides a secure, encrypted vault for managing secrets and credentials. It works both as a standalone CLI tool and as a TokenRing service for programmatic access. The vault uses AES-256-GCM encryption with PBKDF2 key derivation for industry-standard security, and includes session management with automatic locking.

Key Features​

  • AES-256-GCM Encryption: Industry-standard encryption for secrets at rest
  • Dual Interface: CLI tool and TokenRing service
  • Environment Variable Injection: Run commands with vault secrets as env vars
  • Secure Password Input: Hidden password entry in terminal with raw mode support
  • Restrictive Permissions: Vault files created with 0o600 (owner-only access)
  • Session Management: Automatic locking and password caching during session
  • Commander CLI: Full featured command-line interface with password masking
  • TokenRing Integration: Seamless integration with TokenRing application framework
  • Service Architecture: Plugin-based service registration and configuration
  • Chat Commands: Integrated chat commands for agent interaction (/vault unlock, lock, list, store, get)
  • Automatic Relocking: Vault automatically locks after 5 minutes of inactivity
  • Zod Configuration: Type-safe configuration with schema validation
  • Comprehensive Testing: Unit and integration tests with Vitest

Core Components​

VaultService​

The main service class for programmatic access to the vault. Implements the TokenRingService interface.

Constructor:

constructor(options: ParsedVaultConfig)

Properties:

  • name: "VaultService" - The service identifier
  • description: "A vault service for storing persisted credentials" - Service description
  • options: ParsedVaultConfig - The configuration options passed to the constructor

Methods:

  • unlockVault(agent: Agent): Promise<Record<string, string>> - Unlocks the vault and returns decrypted data
  • lock(): Promise<void> - Locks the vault and clears cached data
  • getItem(key: string, agent: Agent): Promise<string | undefined> - Retrieves a secret by key
  • setItem(key: string, value: string, agent: Agent): Promise<void> - Stores a secret
  • save(vaultData: Record<string, string>, agent: Agent): Promise<void> - Saves vault data

Vault Core Functions​

Direct vault file manipulation functions exported from vault.ts:

  • initVault(vaultFile: string, password: string): Promise<void> - Initialize a new vault
  • readVault(vaultFile: string, password: string): Promise<Record<string, string>> - Read vault contents
  • writeVault(vaultFile: string, password: string, data: Record<string, string>): Promise<void> - Write vault contents
  • deriveKey(password: string, salt: Buffer): Buffer - Derive encryption key from password
  • encrypt(data: string, password: string): string - Encrypt data using AES-256-GCM
  • decrypt(encryptedData: string, password: string): string - Decrypt data using AES-256-GCM

Chat Commands​

Integrated chat commands for agent interaction, defined in commands.ts and located in commands/vault/:

  • /vault unlock - Unlock the vault with password
  • /vault lock - Lock the vault
  • /vault list - List all credential keys in the vault
  • /vault store <key> - Store a credential in the vault
  • /vault get <key> - Retrieve and display a credential from the vault

CLI Commands​

Standalone command-line interface commands built with Commander:

  • vault init - Initialize a new encrypted vault file
  • vault get <key> - Retrieve a secret value by key
  • vault set <key> <value> - Store a secret value
  • vault list - List all secret keys (not values) stored in the vault
  • vault remove <key> - Remove a secret by key
  • vault change-password - Change the vault encryption password
  • vault run <command> [args...] - Run a command with vault secrets as environment variables

Services​

VaultService​

The main service class for programmatic access to the vault. Implements TokenRingService.

import { VaultService } from '@tokenring-ai/vault';

const vault = new VaultService({
vaultFile: '.vault',
relockTime: 300000 // 5 minutes
});

Properties​

PropertyTypeDescription
namestringService identifier ("VaultService")
descriptionstringService description
optionsParsedVaultConfigConfiguration options passed to constructor

Methods​

unlockVault(agent: Agent): Promise<Record<string, string>>​

Unlocks the vault by prompting for the password (if not already unlocked), decrypts the vault file, and returns the data. If the vault is already unlocked, returns the cached data.

const data = await vault.unlockVault(agent);

Parameters:

  • agent (Agent): The Agent instance used for human interaction prompts, such as password entry via askForText with masked input.

Returns: Promise<Record<string, string>> - The decrypted vault data.

Throws:

  • Error if password is empty or cancelled
  • Error if decryption fails (invalid password or corrupted vault file)

Implementation Details:

  • Uses agent.askForText() with masked: true for secure password input
  • Caches session password to avoid repeated prompts
  • Automatically initializes new vault if file doesn't exist
  • Schedules automatic relock after configured timeout
lock(): Promise<void>​

Locks the vault, clearing all cached data and session password.

await vault.lock();

Returns: Promise<void>

Implementation Details:

  • Clears vaultData cache
  • Clears sessionPassword
  • Cancels any pending relock timer
getItem(key: string, agent: Agent): Promise<string | undefined>​

Retrieves a secret value by key. Automatically unlocks the vault if needed.

const apiKey = await vault.getItem('API_KEY', agent);

Parameters:

  • key (string): The secret key to retrieve.
  • agent (Agent): Agent instance for interaction.

Returns: Promise<string | undefined> - The secret value or undefined if not found.

Implementation Details:

  • Calls unlockVault() internally to ensure vault is unlocked
  • Returns undefined if key doesn't exist (no error thrown)
setItem(key: string, value: string, agent: Agent): Promise<void>​

Updates the vault with a new key-value pair, saving the changes. Automatically unlocks the vault if needed.

await vault.setItem('API_KEY', 'sk-1234567890', agent);

Parameters:

  • key (string): The secret key to set.
  • value (string): The value to store for the key.
  • agent (Agent): Agent instance for interaction.

Returns: Promise<void>

Implementation Details:

  • Calls unlockVault() internally to ensure vault is unlocked
  • Updates the vault data in memory
  • Calls save() to persist changes
  • Re-encrypts vault file with session password
save(vaultData: Record<string, string>, agent: Agent): Promise<void>​

Writes the updated vault data to the vault file, re-encrypting it with the current session password.

await vault.save({ API_KEY: 'new-key' }, agent);

Parameters:

  • vaultData (Record<string, string>): The updated vault data to save.
  • agent (Agent): Agent instance for interaction.

Returns: Promise<void>

Throws: Error if vault is not unlocked (no session password).

Implementation Details:

  • Requires vault to be unlocked (session password must exist)
  • Encrypts data using AES-256-GCM
  • Writes file with 0o600 permissions (owner read/write only)
  • Updates internal vaultData cache

Provider Documentation​

This package does not use a provider-based architecture. The VaultService is directly instantiated and registered with the TokenRing application framework.

RPC Endpoints​

This package does not define any RPC endpoints.

Chat Commands​

The vault package provides integrated chat commands for managing credentials within the agent interface. These commands are automatically registered when the vault plugin is installed and the AgentCommandService is available.

/vault unlock​

Unlock the vault with a password prompt.

Example:

/vault unlock

Response: "Vault unlocked successfully"

/vault lock​

Lock the vault, clearing all cached data and session password.

Example:

/vault lock

Response: "Vault locked"

/vault list​

List all credential keys stored in the vault.

Example:

/vault list

Response:

  • If empty: "Vault is empty"
  • If has keys: "Vault credentials:\n- key1\n- key2\n- key3"

Implementation: Uses markdownList utility from @tokenring-ai/utility to format the output.

/vault store <key>​

Store a credential in the vault. Prompts securely for the value using masked input.

Example:

/vault store api_key

Response:

  • Success: "Stored credential: api_key"
  • Cancelled: "Store cancelled"

Implementation: Uses agent.askForText() with masked: true for secure password entry.

/vault get <key>​

Retrieve and display a credential from the vault.

Example:

/vault get api_key

Response:

  • Found: "api_key: <value>"
  • Not found: Error "Credential 'api_key' not found in vault"

Implementation: Throws CommandFailedError if key is not found or if no key is provided.

Complete example usage:

/vault unlock
/vault list
/vault store api_key
/vault get api_key
/vault lock

Configuration​

Plugin Configuration​

The plugin configuration options are defined in schema.ts and plugin.ts.

import vaultPlugin from '@tokenring-ai/vault';
import TokenRingApp from '@tokenring-ai/app';

const app = new TokenRingApp();
app.usePlugin(vaultPlugin, {
vault: {
vaultFile: '.vault',
relockTime: 300000
}
});

Plugin Configuration Schema​

The plugin uses Zod for type-safe configuration validation:

const packageConfigSchema = z.object({
vault: VaultConfigSchema.optional()
});
OptionTypeDefaultDescription
vaultVaultConfigundefinedOptional vault configuration object

VaultConfig Schema​

const VaultConfigSchema = z.object({
vaultFile: z.string().min(1),
relockTime: z.number().positive().default(300 * 1000),
});
OptionTypeDefaultDescription
vaultFilestringRequiredPath to the vault file
relockTimenumber300000Time in milliseconds before the vault automatically locks (default: 5 minutes)

Service Configuration​

The VaultService accepts configuration options through its constructor, validated by the VaultConfigSchema.

OptionTypeDefaultDescription
vaultFilestringRequiredPath to the vault file
relockTimenumber300000Time in milliseconds before the vault automatically locks

Example:

new VaultService({
vaultFile: '.vault',
relockTime: 300000 // 5 minutes
});

Integration​

TokenRing Application Integration​

Install the vault plugin with configuration:

import { TokenRingApp } from '@tokenring-ai/app';
import vaultPlugin from '@tokenring-ai/vault';

const app = new TokenRingApp();
app.usePlugin(vaultPlugin, {
vault: {
vaultFile: '.vault',
relockTime: 300000
}
});

// Access the service after plugin installation
const vaultService = app.getService<VaultService>('VaultService');

Note: The plugin only installs the VaultService and registers chat commands if the vault configuration is provided.

Agent Integration​

Use the vault service directly with an Agent:

import { Agent } from '@tokenring-ai/agent';
import { VaultService } from '@tokenring-ai/vault';

const agent = new Agent({
services: [
new VaultService({
vaultFile: '.vault',
relockTime: 300000
})
]
});

// Access vault through agent
const vault = agent.getService<VaultService>('VaultService');
const apiKey = await vault.getItem('API_KEY', agent);

Chat Command Integration​

Chat commands are automatically registered when:

  1. The vault plugin is installed with configuration
  2. The AgentCommandService is available

The plugin uses app.waitForService() to wait for the AgentCommandService before registering commands:

app.waitForService(AgentCommandService, commandService => {
commandService.addAgentCommands(agentCommands)
})

Commands are defined in commands.ts and import individual command modules from commands/vault/:

  • get.ts - /vault get command
  • list.ts - /vault list command
  • lock.ts - /vault lock command
  • store.ts - /vault store command
  • unlock.ts - /vault unlock command

Service Registration Pattern​

The plugin registers the VaultService using the standard TokenRing service registration:

app.addServices(new VaultService(config.vault))

Commands use agent.requireServiceByType(VaultService) to access the service:

const value = await agent.requireServiceByType(VaultService).getItem(key, agent);

Usage Examples​

CLI Workflow​

# Initialize vault
vault init -f .production.vault

# Store production secrets
vault -f .production.vault set DATABASE_URL postgres://...
vault -f .production.vault set API_KEY sk-prod-...
vault -f .production.vault set JWT_SECRET random-secret

# List stored keys
vault -f .production.vault list

# Run application with secrets
vault -f .production.vault run -- node server.js

TokenRing Service Usage​

import { Agent } from '@tokenring-ai/agent';
import { VaultService } from '@tokenring-ai/vault';

const agent = new Agent({
services: [
new VaultService({
vaultFile: '.vault',
relockTime: 300000
})
]
});

// Access vault through agent
const vault = agent.getService<VaultService>('VaultService');
const apiKey = await vault.getItem('API_KEY', agent);

// Or use chat commands
// /vault unlock
// /vault get API_KEY

Direct Vault Access​

import { readVault, writeVault, initVault, deriveKey, encrypt, decrypt } from '@tokenring-ai/vault/vault';
import crypto from 'crypto';

// Initialize new vault
await initVault('.vault', 'myPassword');

// Read vault contents
const data = await readVault('.vault', 'myPassword');

// Write vault contents
await writeVault('.vault', 'myPassword', { API_KEY: 'value' });

// Derive encryption key
const salt = crypto.randomBytes(16);
const key = deriveKey('myPassword', salt);

// Encrypt data
const encrypted = encrypt('sensitive data', 'myPassword');

// Decrypt data
const decrypted = decrypt(encrypted, 'myPassword');

Environment Variable Pattern​

# Store all environment variables
vault set NODE_ENV production
vault set PORT 3000
vault set DATABASE_URL postgres://localhost/mydb
vault set REDIS_URL redis://localhost:6379

# Run with all secrets injected
vault run -- bun start

Error Handling​

try {
const apiKey = await vault.getItem('API_KEY', agent);
console.log(`API Key: ${apiKey}`);
} catch (error) {
if (error.message.includes('Invalid password')) {
console.error('Wrong password provided');
} else if (error.message.includes('Vault must be unlocked')) {
console.error('Session expired, please unlock again');
} else if (error.message.includes('not found')) {
console.error('Secret not found in vault');
}
}

Command Error Handling​

Chat commands throw CommandFailedError for invalid usage:

import { CommandFailedError } from '@tokenring-ai/agent/AgentError';

// In /vault get command:
if (!key) throw new CommandFailedError("Usage: /vault get <key>");
if (value === undefined) throw new CommandFailedError(`Credential "${key}" not found in vault`);

Best Practices​

Security Best Practices​

  • Use strong, unique passwords for vault encryption
  • Store vault files in secure locations
  • Don't commit vault files to version control
  • Use .gitignore to exclude vault files
  • Rotate secrets regularly
  • Use different vaults for different environments
  • Use the auto-lock feature to prevent unauthorized access
  • Use chat commands for secure credential management in agents
  • Never log or expose vault passwords in error messages

Session Management​

  • The vault automatically locks after the configured timeout (default: 5 minutes)
  • Password is cached during the session but cleared on lock
  • Each vault access resets the relock timer
  • Consider relockTime based on security requirements
  • Call lock() explicitly when done with sensitive operations

Error Handling​

  • Handle invalid password errors when decrypting vaults
  • Check for file existence before operations
  • Handle empty password cancellations
  • Use proper error boundaries when integrating with agents
  • Catch and handle CommandFailedError for chat command errors

Configuration​

  • Validate configuration using the provided Zod schemas
  • Use appropriate relockTime values for your use case
  • Store vault files in secure, non-public directories
  • Use environment-specific vault files for different deployments

Testing and Development​

Testing​

The package includes Vitest configuration for testing:

// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
include: ["**/*.test.ts"],
environment: "node",
globals: true,
isolate: true,
},
});

Run tests:

bun run test
bun run test:watch
bun run test:coverage

Test Structure​

The package includes comprehensive tests using Vitest:

  • Unit Tests:

    • test/vault.unit.test.ts - Core encryption/decryption functions (deriveKey, encrypt, decrypt, readVault, writeVault, initVault)
    • test/vault-service.unit.test.ts - VaultService methods and session management
    • test/plugin.unit.test.ts - Plugin installation and configuration validation
    • test/test-utils.ts - Test utilities including createTempFile() for isolated test vaults
  • Integration Tests:

    • test/cli.integration.test.ts - CLI command integration tests using spawn to run actual CLI commands

Package Structure​

pkg/vault/
├── cli.ts # CLI implementation with Commander
├── index.ts # Package exports
├── plugin.ts # TokenRing plugin integration
├── VaultService.ts # TokenRing service implementation
├── vault.ts # Core encryption and file operations
├── schema.ts # Zod configuration schema
├── commands.ts # Chat command router
├── commands/
│ └── vault/
│ ├── get.ts # /vault get command
│ ├── list.ts # /vault list command
│ ├── lock.ts # /vault lock command
│ ├── store.ts # /vault store command
│ └── unlock.ts # /vault unlock command
├── test/
│ ├── cli.integration.test.ts
│ ├── plugin.unit.test.ts
│ ├── test-utils.ts
│ ├── vault-service.unit.test.ts
│ └── vault.unit.test.ts
├── vitest.config.ts # Vitest configuration
├── LICENSE # MIT license
├── package.json # Package configuration
└── README.md # Package documentation

Development Commands​

bun run build # Type-check with TypeScript
bun run test # Run tests once
bun run test:watch # Run tests in watch mode
bun run test:coverage # Run tests with coverage report

Test Utilities​

The test-utils.ts file provides helper functions for testing:

// createTempFile() - Creates a temporary directory for isolated test vaults
const tempDir = createTempFile();
const tempVaultFile = `${tempDir}/test.vault`;

// Clean up after tests
await fs.remove(tempDir);

Dependencies​

Production Dependencies​

PackageVersionDescription
@tokenring-ai/app0.2.0TokenRing application framework
@tokenring-ai/agent0.2.0Agent orchestration system
@tokenring-ai/utility0.2.0Shared utilities
@types/fs-extra^11.0.4Type definitions for fs-extra
commander^14.0.3CLI framework
fs-extra^11.3.4Enhanced file system operations
zod^4.3.6Schema validation

Development Dependencies​

PackageVersionDescription
vitest^4.1.1Testing framework
typescript^6.0.2TypeScript compiler
  • @tokenring-ai/agent - Agent system that integrates with VaultService
  • @tokenring-ai/app - Application framework with plugin system
  • @tokenring-ai/utility - Utility functions including markdownList for command output

License​

MIT License - see LICENSE file for details.