Aleph
Concepts

Secret Management

Encrypted vault using AES-256-GCM with per-entry HKDF derivation, plus leak detection, placeholder resolution, and secret-usage approval RPC.

The secrets module provides encrypted storage for sensitive credentials (API keys, tokens, passwords). It uses AES-256-GCM with per-entry HKDF-SHA256 key derivation, with SharedTokenManager as the master key; supports 1Password; and scans outbound text and the {{secret:NAME}} resolution path for accidental secret leaks.

Design Philosophy

  1. Per-entry encryption — Each entry has its own derived key and random nonce / salt
  2. Zeroize on drop — Sensitive data is held by secrecy::SecretString and zeroized on drop
  3. Leak detection — Outbound text and LLM echo text are scanned; detected secrets trigger Block / Redact
  4. Use requires approval — Runtime retrieval goes through an explicit JSON-RPC approval flow

Core Components

SecretVault

File-based encrypted storage:

pub struct SecretVault {
    data: VaultData,
    path: PathBuf,
}

Location: <config_dir>/secrets.vault (default ~/.aleph/secrets.vault).

Atomic write: VaultIo uses a temp file plus an fs2 lock on secrets.vault.lock, then atomic rename — safe under concurrency and crash.

Corruption recovery: SecretVault::open_or_backup renames an unreadable-but-present file to <path>.corrupt-<unix_ts> before opening a fresh vault, so open().unwrap_or_else(empty) cannot overwrite the original on next save().

Encryption

let key = hkdf_derive(master_key, salt, info);
let ciphertext = aes_256_gcm_encrypt(plaintext, key, nonce);

Master key: the 32-byte secret held by SharedTokenManager (the HMAC shared-token secret) acts as the AES-GCM master key.

DecryptedSecret

pub struct DecryptedSecret {
    value: SecretString,
}

SecretString comes from the secrecy crate:

  • Zeroize on drop — memory is cleared when the value is dropped
  • Debug / Display redact the value — debug print and logging never expose plaintext
  • expose(&self) -> &str — explicit access; minimize the surface that touches plaintext

Name Validation

validate_secret_name(name) enforces an [A-Za-z0-9_.-:] character set and a 128-character maximum — unifying the syntax for {{secret:NAME}} placeholders, recoverable keys, and Hub namespaced keys.


Secret Providers

#[async_trait]
pub trait SecretProvider: Send + Sync {
    fn provider_type(&self) -> &str;
    async fn get(&self, reference: &str) -> Result<DecryptedSecret, SecretError>;
    async fn health_check(&self) -> Result<ProviderStatus, SecretError>;
    async fn list(&self) -> Result<Vec<SecretMetadata>, SecretError>;
}

Local Vault

SharedTokenManager exposes store_secret / get_secret / delete_secret / list_secret_names as the local vault provider, with provider_type() returning "local_vault".

1Password

pub struct OnePasswordProvider { /* ... */ }

Retrieves secrets through the 1Password CLI (op). health_check returns classified error states (sign-in required / item missing / other) with fixed, credential-safe messages.


Template Injection and Placeholder Resolution

Templates can reference secrets through placeholder syntax:

pub fn render_with_secrets(
    template: &str,
    resolver: &dyn AsyncSecretResolver,
) -> Result<String>;

pub fn extract_secret_refs(text: &str) -> Result<Vec<SecretRef>, SecretError>;

Placeholder syntax: {{secret:NAME}}

The resolver fetches the named secret from the vault and substitutes it into the template, with this order:

  1. Extractextract_secret_refs runs before any resolution
  2. Resolve — fetch each name through AsyncSecretResolver
  3. Register — resolved secrets are registered into secret_leak_detector
  4. Substitute — longest-first ordering, so {{secret:api_key}} is replaced before {{secret:api}}

Hub-vault namespaced keys (ext.{kind}.{id}.{field}) are produced by src/hub/secrets.rs::field_key, share the same character set, and round-trip through the placeholder parser as {{secret:ext.mcp.…}}.


Leak Detection

src/secrets/leak_detector.rs::LeakDetector scans text for accidental secrets:

pub struct LeakDetector { patterns: Vec<Regex> }

pub enum LeakDecision {
    Clean,
    Warn,
    Redact,
    Block { reason: String, redacted_content: String },
}
  • Clean — no secret detected
  • Warn — record only
  • Redact — replace the hit with a placeholder (used for Bearer tokens and similar)
  • Block { reason, redacted_content } — confirm and block; the returned content has been run through SecretMasker::mask, never raw

The RuntimeSecurityGuard orchestrates the calls: outbound runs leak detection between secret resolution and substitution, inbound runs it before the LLM's response reaches the host. Every hit enters the AuditEventType::LeakWarning / ExecBlocked audit stream. SecretMasker is applied a second time on the display side (approval card, Guardian payload, log).


Safety Properties

  • Lock recoveryunwrap_or_else(|e| e.into_inner()) for every Mutex
  • No stderr leakage — error messages use fixed safe text, raw stderr logged through tracing::debug
  • JSON parse failures loggedserde_json errors are debug-logged, never silently swallowed
  • ZeroizeSecretString clears memory on drop
  • Atomic writeVaultIo file lock + temp file + rename

secrets.* RPC

src/gateway/handlers/secrets.rs provides vault CRUD:

RPCParamsBehavior
secrets.listreturns the registered secret names (sorted; never values)
secrets.set{ key, value }stores a new entry
secrets.delete{ key }deletes an entry; not found ⇒ -32004
secrets.verify{ key }reports { key, bytes, present } (length, never plaintext)
secrets.providersreports the configured providers as { key, type, account? }

secrets.list and secrets.verify return only names and byte lengths — plaintext never crosses the RPC boundary. The local aleph-server secret CLI (LockOnly path) is the surface that retrieves the raw value.


Secret-Usage Approval

src/gateway/handlers/secret_approvals.rs::SecretApprovalManager provides:

RPCPurpose
secret.approval.requestAgent requests permission to use a named secret
secret.approval.resolveClient approves or denies the request
secret.approvals.pendingList pending approval requests

SecretApprovalRequest carries secret_name / usage / agent_id / session_key / created_at / timeout_ms. SecretApprovalManager holds pending entries in a HashMap<id, ApprovalRecord> keyed by id, with timeout on the request.


Startup Secret Migration

src/gateway/handlers/secret_migration.rs::migrate_all_secrets_to_vault runs at server startup, moves any plaintext key in config.toml into the vault, and strips the original field. Coverage:

Source sectionVault key prefix
providers.{name}.api_keyai:{name}
generation.{type}_providers.{name}.api_keygen:{name}
memory.embedding.providers[].api_keyembed:{id}
memory.rerank.api_keyrerank:{provider}
search.backends.{name}.api_keysearch:{name}
channels.{id}.bot_token and similarchannel:{id}:{field}
voice.streaming.api_keyvoice_streaming:api_key

Only the affected sections get re-saved; the file is touched only when at least one migration occurred.


Guardian Judge Masks Secrets

GuardianApprovalRequester runs its verdict payload through src/exec/masker.rs::SecretMasker — both summary and per-command-segment raw text are masked before being concatenated, so bearer tokens, URL basic-auth credentials, and generic password assignments never enter the LLM-context payload or the audit stream.


Code Location

  • src/secrets/mod.rs — module entry and validate_secret_name
  • src/secrets/vault.rsSecretVault (with open_or_backup)
  • src/secrets/crypto.rs — HKDF + AES-256-GCM
  • src/secrets/types.rsDecryptedSecret / EncryptedEntry
  • src/secrets/injection.rs — template resolution + AsyncSecretResolver
  • src/secrets/placeholder.rs{{secret:NAME}} extraction
  • src/secrets/leak_detector.rs — outbound and inbound leak scanning
  • src/secrets/vendor_patterns.rs — vendor formats (cloud-provider keys, etc.)
  • src/secrets/vault_resolver.rs — wraps the local vault as an AsyncSecretResolver
  • src/secrets/virtual_key_resolver.rs — reference resolution
  • src/secrets/provider/mod.rsSecretProvider trait
  • src/secrets/provider/onepassword.rs — 1Password CLI integration
  • src/utils/vault_io.rsVaultIo (file lock + atomic write)
  • src/gateway/security/shared_token.rs — master-key holder
  • src/gateway/handlers/secrets.rssecrets.* RPC
  • src/gateway/handlers/secret_approvals.rsSecretApprovalManager + secret.approval.* RPC
  • src/gateway/handlers/secret_migration.rs — startup plaintext migration
  • src/hub/secrets.rs — Hub namespacing
  • src/exec/masker.rsSecretMasker (display-side secret masking)

See Also

On this page