Skip to content

Apra-Labs/blindfold

Repository files navigation

blindfold

Secure credential vault for AI agents. Blindfold keeps secrets out of LLM context windows by collecting them through an out-of-band (OOB) side-channel, encrypting them with AES-256-GCM, and resolving them only at the last moment — right before a shell command runs. The LLM only ever sees a {{secure.NAME}} token; the plaintext never touches the model.


Quick start

npm install -g blindfold
blindfold install        # registers the MCP server with Claude Desktop and Claude Code
# Restart your AI client

Once registered, Claude will have four new MCP tools for managing credentials.


Library usage

For most use cases, store credentials through the MCP tool (credential_store_set) rather than calling the lower-level API directly. The MCP tool handles the full OOB flow. If you need to drive the flow programmatically:

import { initBlindfold, collectOobApiKey, decryptPassword, resolveSecureTokens, redactOutput } from 'blindfold';

initBlindfold({ dataDir: '/var/lib/myapp/blindfold' });

// Collect a secret from the user via OOB side-channel (terminal popup / GUI prompt).
// Returns { password?: string; fallback?: string; persist?: boolean }
// `password` is encrypted — call decryptPassword() to get the plaintext.
const result = await collectOobApiKey('MY_API_KEY', 'credential_store_set', {
  prompt: 'Enter your API key',
});
if (result.password) {
  const plaintext = decryptPassword(result.password);
  // use plaintext...
} else if (result.fallback) {
  // User could not open a terminal — handle gracefully
}

// Later: resolve {{secure.MY_API_KEY}} tokens inside a command string.
// Returns { resolved: string; credentials: ResolvedCredential[] } | { error: string }
const result2 = resolveSecureTokens('curl -H "Authorization: Bearer {{secure.MY_API_KEY}}" https://api.example.com');
if ('error' in result2) throw new Error(result2.error);
const { resolved, credentials } = result2;
// Run `resolved` as a shell command, then scrub secrets from the output:
// const safeOutput = redactOutput(rawOutput, credentials);

The MCP server entrypoint is importable separately:

import { startMcpServer } from 'blindfold/mcp';
await startMcpServer();

MCP tool reference

Tool Description
credential_store_set Collect a new secret from the user via OOB side-channel and store it
credential_store_update Update an existing credential (rotate secret, change TTL, adjust policy)
credential_store_delete Delete a stored credential by name
credential_store_list List stored credentials (names and metadata only -- no plaintext)

Standalone vs host-integrated usage

Blindfold's MCP surface covers vault management only: credential_store_set, credential_store_list, credential_store_update, and credential_store_delete. There is no resolve_secure MCP tool. This is intentional.

Resolving a {{secure.NAME}} token means producing the plaintext credential. If that resolution happened inside an MCP tool response, the plaintext would land directly in the LLM's context window -- defeating the entire purpose of the token model.

To use stored credentials inside an agentic workflow, you need a host integration: an application that imports blindfold as a library and calls resolveSecureTokens (or resolveSecureField) immediately before executing a command or API call -- keeping the plaintext inside application memory and out of the LLM stream.

apra-fleet is the reference host integration. Its execute_command tool resolves {{secure.NAME}} tokens just before spawning the subprocess, then redacts the output with redactOutput before returning results to the model.

Without a host integration your LLM can store and list credentials but cannot resolve them into plaintext. That restriction is deliberate: it is the whole point of the design.


{{secure.NAME}} token syntax

Pass {{secure.NAME}} anywhere you would normally put a secret (command arguments, environment values, API call parameters). Blindfold resolves it just before execution:

# In a shell command:
docker login -u myuser -p {{secure.DOCKER_TOKEN}} registry.example.com

# In a URL parameter passed to a tool:
curl https://api.example.com/data?key={{secure.API_KEY}}

Token names must match [a-zA-Z0-9_-]{1,64}. Unresolved tokens cause an error rather than silently passing an empty value.


CLI reference

Command Description
blindfold Start the MCP server (stdio transport)
blindfold serve Alias for starting the MCP server
blindfold install Register blindfold with Claude Desktop and Claude Code
blindfold install --for claude Register with Claude Desktop only
blindfold secret --set NAME Store a secret interactively
blindfold secret --set NAME --persist Store and persist the secret to disk (encrypted)
blindfold secret --set NAME -y Read secret value from stdin (non-interactive)
blindfold secret --list List stored credentials (names and metadata only)
blindfold secret --update NAME Rotate or update a stored credential
blindfold secret --update NAME --members LIST Restrict credential to comma-separated member list
blindfold secret --update NAME --ttl SECONDS Set credential expiry (TTL in seconds from now)
blindfold secret --update NAME --allow Set network policy to allow
blindfold secret --update NAME --deny Set network policy to deny
blindfold secret --delete NAME Delete a named credential
blindfold secret --delete --all Delete all stored credentials (prompts for confirmation)
blindfold auth --confirm Confirm a pending OOB authentication request
blindfold --version Print version
blindfold --help Print usage

Security model

Secrets are collected through a Unix Domain Socket (UDS) side-channel that is inaccessible to the LLM. When a credential is needed, the agent calls credential_store_set; blindfold opens a separate terminal or GUI prompt on the user's desktop, collects the secret there, and delivers it back through the UDS — never through the MCP stdio stream that the LLM reads. Persisted credentials are encrypted with AES-256-GCM using a randomly generated key stored in a file with owner-only (0600) permissions. In-memory (session) credentials are held in a process-local map and never written to disk. Token resolution applies shell escaping by default, preventing injection through crafted credential values.


Requirements

  • Node.js 20+ (the MCP SDK requires Node 18+; Node 20 LTS is recommended)
  • Platforms: Linux (primary), macOS (supported), Windows (supported, UDS requires Windows 10 1903+)
  • Peer dependency: @modelcontextprotocol/sdk ^1.27.0 (required when using blindfold as an MCP server; optional for library-only use)

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors