Skip to content

Support arbitrary vault keys for skill API authentication #682

@bug-ops

Description

@bug-ops

Summary

Allow users to store arbitrary key-value pairs in the vault and make them accessible to skills at runtime. Currently, resolve_secrets() only handles a hardcoded set of known keys (ZEPH_CLAUDE_API_KEY, ZEPH_OPENAI_API_KEY, etc.). Skills that need to call external APIs (e.g., GitHub, Jira, weather services, custom webhooks) have no way to securely access credentials through the vault system.

Prior art

OpenClaw (TypeScript)

  • Plaintext JSON store (auth-profiles.json) + macOS Keychain for OAuth
  • Skills declare requires.env in SKILL.md frontmatter — skill is excluded from system prompt if env vars are missing
  • Per-run process.env injection scoped to active skill; restored after execution
  • primaryEnv alias: skills.entries.<name>.apiKey auto-maps to the declared env var

ZeroClaw (Rust)

  • ChaCha20-Poly1305 AEAD encryption at rest with per-value nonce
  • auth login/paste-token/logout/list/status CLI for credential management
  • Skills do NOT declare secret dependencies — no runtime gating
  • Automatic migration from legacy encryption scheme on read

Zeph advantages

  • age encryption already built-in as a library (no external CLI dependency) — AgeVaultProvider handles keygen, encrypt/decrypt, atomic save
  • Proposed ZEPH_SECRET_* prefix with custom: HashMap<String, Secret> is more flexible than both: OpenClaw is tied to env vars, ZeroClaw to hardcoded config fields

Current behavior

  • resolve_secrets() in config/mod.rs maps a fixed list of vault keys to Config.secrets fields
  • Skills are Markdown documents injected into the system prompt — they have no direct access to vault or config
  • To use an API key in a skill, users must manually export env vars or configure MCP server env in TOML
  • No mechanism to declare required secrets in SKILL.md frontmatter

Proposed design

1. Custom secrets storage in Config

Add a custom field to ResolvedSecrets:

pub struct ResolvedSecrets {
    // ... existing fields ...
    pub custom: HashMap<String, Secret>,
}

All vault keys with prefix ZEPH_SECRET_ are resolved into this map during resolve_secrets(). The prefix is stripped for the map key:

  • Vault key ZEPH_SECRET_GITHUB_TOKENcustom["GITHUB_TOKEN"]
  • Vault key ZEPH_SECRET_WEATHER_API_KEYcustom["WEATHER_API_KEY"]

2. SKILL.md frontmatter: requires-secrets

Add an optional requires-secrets field to skill metadata:

---
name: github-pr-review
description: Review GitHub pull requests
requires-secrets: GITHUB_TOKEN
allowed-tools: bash
---

Comma-separated list of custom secret names. Behavior at skill activation time (inspired by OpenClaw):

  • If any required secret is missing, the skill is excluded from the system prompt entirely — not just a warning. This prevents the LLM from attempting to use a skill without working credentials.
  • A tracing::warn! is emitted for observability.

3. Inject secrets into ShellExecutor environment

When a skill with requires-secrets is active, inject the corresponding secrets as environment variables into ShellExecutor calls scoped to that skill's tool invocations:

  • GITHUB_TOKEN from custom["GITHUB_TOKEN"] → set as env var for shell commands
  • Only inject secrets that the active skill explicitly declares (principle of least privilege)
  • Redact these values in logs (already handled by Secret wrapper and RedactLayer)
  • Restore environment after skill execution completes (scoped injection)

4. Vault CLI: vault set / vault get / vault list / vault remove

The AgeVaultProvider already supports arbitrary key-value storage (age encryption is built into the binary as a library, no external CLI required). Extend the CLI:

  • zeph vault list — show all stored key names (not values)
  • zeph vault set <KEY> <VALUE> — already works at provider level, ensure it accepts bare names and auto-prefixes ZEPH_SECRET_ for custom keys
  • zeph vault get <KEY> — print decrypted value (with confirmation prompt)
  • zeph vault remove <KEY> — remove a key

5. TUI: Vault management panel

Add a dedicated vault/secrets panel to the TUI dashboard (feature-gated under tui):

  • List view — table of all stored key names with masked values (****), creation/update timestamps if available
  • Add secret — inline form: key name + value input (value masked during typing)
  • Remove secret — select key, confirm deletion
  • Reveal value — toggle to temporarily show decrypted value for a selected key (auto-hides after timeout or on blur)
  • Skill linkage indicator — show which skills reference each key via requires-secrets
  • Keyboard navigation consistent with existing TUI panels (Tab to switch panels, j/k or arrows to navigate, Enter to select, d to delete, a to add)
  • All mutations go through AgeVaultProvider + trigger resolve_secrets() refresh

6. Interactive config wizard (--init)

Add a step to the wizard for adding custom secrets:

  • Prompt: "Do you want to add custom API keys for skills?"
  • Allow adding key-name + value pairs, stored via vault backend

Implementation plan

  1. Config changes — add custom: HashMap<String, Secret> to ResolvedSecrets, extend resolve_secrets() to scan vault for ZEPH_SECRET_* prefix
  2. SKILL.md parser — add requires-secrets to SkillMeta, validate on load
  3. Skill activation gate — exclude skills with missing required secrets from the system prompt (not just warn)
  4. ShellExecutor env injection — pass resolved custom secrets as scoped env vars to shell commands when the declaring skill is active; restore after execution
  5. Vault CLI — add vault list, vault get, vault remove subcommands
  6. TUI vault panel — list/add/remove/reveal secrets, skill linkage indicator
  7. Config wizard — add custom secrets step to --init
  8. Documentation — update docs/ and skill authoring guide

Acceptance criteria

  • zeph vault set ZEPH_SECRET_MY_KEY value stores the key
  • zeph vault list shows all stored key names
  • zeph vault get ZEPH_SECRET_MY_KEY prints the decrypted value
  • zeph vault remove ZEPH_SECRET_MY_KEY removes the key
  • resolve_secrets() populates config.secrets.custom from ZEPH_SECRET_* vault entries
  • SKILL.md requires-secrets: MY_KEY is parsed and validated
  • Skills with missing required secrets are excluded from the system prompt
  • Active skill's required secrets are injected as scoped env vars into ShellExecutor
  • Environment is restored after skill execution completes
  • Secret values are redacted in all log output
  • TUI vault panel: list keys, add/remove secrets, reveal with auto-hide
  • TUI vault panel: skill linkage indicator shows which skills use each key
  • Unit tests for custom secret resolution, SKILL.md parsing, and activation gating

Metadata

Metadata

Assignees

No one assigned

    Labels

    skillsSKILL.md system

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions