Skip to content

Vault & Secret Broker

UGENT Vault is a local-first encrypted secret store and provider egress gateway. API keys live in your OS keychain or an encrypted file vault — never in config files, agent context, or logs.

Overview

The Vault solves a simple problem: your API keys should never appear in plaintext. Not in config files, not in environment variables that any process can read, not in agent prompts, not in logs.

Two layers work together:

  1. Secret Store — encrypted storage for API keys and tokens
  2. Secret Broker — a local daemon that injects credentials before forwarding API requests

CLI Commands

The ugent secret CLI path boots only the vault — no agent, LLM, tools, MCP, or plugins are initialized.

Initialize

bash
ugent secret init

Creates the metadata database and (for file-vault) the encrypted store directory.

Add a Secret

bash
echo "sk-..." | ugent secret add @openai_api_key --provider openai --kind api_key --stdin

Values are only accepted via --stdin. There is no --value flag — this prevents shell history leaks.

List and Inspect

bash
ugent secret list
ugent secret inspect @openai_api_key

These commands never print the secret value — only metadata (provider, kind, fingerprint, timestamps).

Rotate

bash
echo "sk-new..." | ugent secret rotate @openai_api_key --stdin

Audit

bash
ugent secret audit
ugent secret audit @openai_api_key

Shows the full audit trail — every add, rotate, lease issuance, and denial.

Storage Backends

OS Keychain (Default)

Uses the keyring crate:

  • macOS: Keychain
  • Windows: Credential Manager
  • Linux: Secret Service (e.g. GNOME Keyring)

No passphrase needed — the OS manages encryption.

Encrypted File Vault

bash
UGENT_SECRETS_BACKEND=file-vault ugent secret init

Uses XChaCha20-Poly1305 authenticated encryption with an Argon2id-derived master key. Set the passphrase via UGENT_SECRETS_PASSPHRASE.

Referencing Secrets in Config

Instead of plaintext values, use @secret_ref handles:

toml
[llm.instances.openai]
type = "openai"
api_key_ref = "@openai_api_key"
default_model = "gpt-5.4-mini"

When api_key_ref is set, it takes priority over api_key or $ENV_VAR. The decrypted value is resolved only at the moment of use and never written to any file.

Environment Projection

Run any command with secrets injected only into that process's environment:

bash
ugent secret exec \
  --env SERVICE_TOKENS=@context_engine_service_tokens \
  --env OPENAI_API_KEY=@openai_api_key \
  -- ugent-context-engine run --config ~/.ugent/context-engine/server.toml

Works for any language, any tool — zero code changes needed.

Secret Broker

Start the broker daemon:

bash
ugent secret broker start

Or use the standalone binary:

bash
secret-broker

Transports

  1. Unix socket: ~/.ugent/instances/<workspace_hash>/secret-broker.sock (no auth needed — protected by directory permissions)
  2. Loopback HTTP: 127.0.0.1:18443 (requires bootstrap bearer token from ~/.ugent/secrets/broker.token)

Provider Egress Gateway

Call provider APIs without ever holding the provider key:

bash
TOKEN=$(cat ~/.ugent/secrets/broker.token)
curl -N http://127.0.0.1:18443/proxy/openai/v1/chat/completions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-5.4-mini", "stream": true, "messages": [{"role": "user", "content": "hello"}]}'

Supported proxy routes:

ProviderRoutes
OpenAI/proxy/openai/v1/chat/completions, /proxy/openai/v1/responses, /proxy/openai/v1/embeddings
Google/proxy/google/v1beta/models/{model}:generateContent, :streamGenerateContent
Jina/proxy/jina/v1/embeddings, /proxy/jina/v1/rerank
Voyage/proxy/voyage/v1/embeddings, /proxy/voyage/v1/rerank

Streaming responses pass through without buffering. Request and response bodies are never logged.

Any OpenAI-Compatible SDK

Point any SDK's base URL at the broker and use the bootstrap token as the API key:

python
import openai

client = openai.OpenAI(
    base_url="http://127.0.0.1:18443/proxy/openai/v1",
    api_key=open(Path.home() / ".ugent/secrets/broker.token").read_text().strip(),
)

The broker validates the token and swaps in the real credential. Your code never sees the provider key.

Grants and Leases

Access is deny by default. Configure grants in ~/.ugent/secrets.grants.toml:

toml
[[grants]]
secret_ref = "openai_api_key"
consumer = "ugent-core"
purposes = ["llm.chat", "llm.embeddings"]
delivery = ["native", "http_header_inject"]
allowed_hosts = ["api.openai.com"]
ttl_secs = 300

A missing file denies everything. Every issue and denial is audited.

Sibling Service Integration

Context Engine

Point the context engine's embedder at the broker gateway:

toml
# server.toml
openai_compatible_endpoint = "http://127.0.0.1:18443/proxy/openai/v1"
openai_compatible_api_key = "$BROKER_TOKEN"

The provider key never enters the engine's process.

Plugin Secret Injection

Map environment variables to secret handles in plugins.toml:

toml
[[plugins]]
id = "my-plugin"
secret_env_refs = { "MY_API_TOKEN" = "@my_service_token" }

The supervisor resolves and injects the value only into the plugin child process at spawn time.

Threat Model

Protected against:

  • Provider keys in config files, agent context, or logs
  • Prompt injection revealing keys
  • Other local processes calling the loopback API without the token

Not protected against:

  • Root access
  • Same-user malware that can read the bootstrap token or OS keychain
  • Kernel-level attacks

Released under the Private Beta License.