Stop Stuffing API Keys into .env: 5 Steps to Wire Hermes v0.19's New External Secret Vault

How many API keys are currently sitting in your ~/.hermes/.env file? OpenAI, Anthropic, GitHub, Telegram, Discord, Cloudflare, AWS — every time you add a new integration, you paste another SOME_KEY=sk-... line. Then you triple-check .gitignore before committing, copy the file to yourself when you switch machines, and paste it into CI secrets all over again. Worse, everyone on the team keeps a local copy, so nobody knows which version is current, who changed what, or whether a key has leaked.
Hermes Agent v0.19.0 turns this mess into a clean abstraction: SecretSource. It lets Hermes read API keys from an external vault at startup, with first-class support for Bitwarden Secrets Manager and 1Password. You keep a single bootstrap token in .env and let the vault hold the rest. Keys no longer have to live in plaintext .env files.
This post gives you a practical 5-step migration. You don’t need to refactor everything at once — you can move the highest-risk keys first and keep a rollback path open.
For the full v0.19.0 overview, see our v0.19.0 release notes and the skill-combo guide.
Why .env Is Not the Long-Term Answer
.env is great for prototypes, but it breaks down once Hermes connects to a dozen tools and services:
- Sprawl risk. Every time you copy
.envto another machine, container, or CI environment, you create another leak surface. GitHub scans millions of accidentally committed secrets every year. - No audit trail.
.envwon’t tell you who changed which key, or when. A key can be rotated and the rest of the team only finds out when something breaks. - Painful rotation. Quarterly token rotation means editing N files and N environment-variable injection points, then hoping nothing was missed.
An external secret vault doesn’t just “hide plaintext.” It turns secrets into controlled, auditable, centrally managed resources. Hermes v0.19.0’s SecretSource connects that idea directly to the agent’s startup path, so the agent reads vault values as if they were ordinary environment variables.
What Hermes v0.19.0’s SecretSource Can Do
According to the v0.19.0 release notes and the official secrets docs, the SecretSource interface provides:
- Multiple vaults at once. Bitwarden Secrets Manager and 1Password can be enabled simultaneously. A generic
commandsource is also available for any vault that printsKEY=VALUElines. - Deterministic precedence. Hermes resolves conflicts with a clear ladder: explicit
env:mappings (1Password, command source) beat bulk project pulls (Bitwarden); within the same shape, the optionalsecrets.sourceslist decides;.env/ shell values win unless a source hasoverride_existing: true. - Conflict warnings. If a later source also claims a variable that an earlier source already supplied, Hermes warns you instead of silently choosing one.
- Variable provenance. Every injected variable records which source supplied it, so status output and startup logs show exactly where a value came from.
- Non-blocking startup. If a vault is unreachable or authentication fails, Hermes prints a one-line remediation warning and continues with whatever credentials
.envalready had.
That means you can write a config like this without putting OPENAI_API_KEY in .env at all:
secrets:
onepassword:
enabled: true
env:
OPENAI_API_KEY: "op://Private/OpenAI/api key"
ANTHROPIC_API_KEY: "op://Private/Anthropic/credential"
override_existing: true
The next five sections show how to make that real.
Step 1: Upgrade to Hermes v0.19.0 and Verify the CLI
SecretSource is a v0.19.0 feature, so start by checking your version:
hermes --version
Then confirm the secrets subcommand is available:
hermes secrets --help
You should see bitwarden, onepassword, and other source helpers listed. If you’re below v0.19.0, run the installer:
# macOS / Linux / WSL2
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
# Windows (PowerShell)
iex (irm https://hermes-agent.nousresearch.com/install.ps1)
Step 2: Pick a Vault and Authenticate
Hermes doesn’t authenticate on your behalf; it relies on the official flow of each vault. You need to set that up on the machine where Hermes runs.
Option A: Bitwarden Secrets Manager
You need a machine account in Bitwarden Secrets Manager, not the consumer Bitwarden password vault. The machine account is designed for non-interactive workloads.
- In the Bitwarden web app, switch to Secrets Manager.
- Create a Project (e.g.,
Hermes keys). - Add your provider keys as secrets. The secret Name becomes the environment variable name — use
OPENAI_API_KEY,ANTHROPIC_API_KEY,TELEGRAM_BOT_TOKEN, etc. - Go to Machine accounts → New machine account, give it read access to the project.
- Under Access tokens, create a token (starts with
0., cannot be retrieved again) and copy it.
Store that token in ~/.hermes/.env as BWS_ACCESS_TOKEN:
BWS_ACCESS_TOKEN=0.xxx...
The bws binary will be auto-downloaded into ~/.hermes/bin/ the first time Hermes needs it — no brew, apt, or sudo required.
Option B: 1Password
Install the official 1Password CLI (op) and verify it works:
op --version
op whoami
For laptops / interactive use, sign in with op signin or enable CLI integration in the 1Password app. Hermes will pass your session variables through.
For servers / CI / cron, create a service account, grant it read access to the relevant vault, and store the token in ~/.hermes/.env:
OP_SERVICE_ACCOUNT_TOKEN=ops_...
Security note: The bootstrap token (
BWS_ACCESS_TOKENorOP_SERVICE_ACCOUNT_TOKEN) is itself a high-value credential. Keep it in~/.hermes/.env, never inconfig.yaml, and never commit.envto a repo.
Step 3: Run the Setup Wizard and Configure the Source
Hermes ships a dedicated CLI for each source. The wizard writes to ~/.hermes/config.yaml (or ~/.hermes/profiles/<profile>/config.yaml if you’re running under a named profile).
Bitwarden
Run the wizard interactively:
hermes secrets bitwarden setup
Or script it non-interactively:
hermes secrets bitwarden setup \
--access-token "$BWS_ACCESS_TOKEN" \
--server-url https://vault.bitwarden.com \
--project-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
The resulting config looks like this:
secrets:
bitwarden:
enabled: true
project_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
server_url: "https://vault.bitwarden.com"
access_token_env: BWS_ACCESS_TOKEN
override_existing: true
cache_ttl_seconds: 300
1Password
Run the wizard:
hermes secrets onepassword setup
Or with a service-account token:
hermes secrets onepassword setup \
--account my.1password.com \
--token-env OP_SERVICE_ACCOUNT_TOKEN \
--token "$OP_SERVICE_ACCOUNT_TOKEN"
Then map each environment variable to an op:// reference:
hermes secrets onepassword set OPENAI_API_KEY "op://Private/OpenAI/api key"
hermes secrets onepassword set ANTHROPIC_API_KEY "op://Private/Anthropic/credential"
hermes secrets onepassword set TELEGRAM_BOT_TOKEN "op://Private/Telegram/token"
The resulting config:
secrets:
onepassword:
enabled: true
env:
OPENAI_API_KEY: "op://Private/OpenAI/api key"
ANTHROPIC_API_KEY: "op://Private/Anthropic/credential"
TELEGRAM_BOT_TOKEN: "op://Private/Telegram/token"
service_account_token_env: OP_SERVICE_ACCOUNT_TOKEN
override_existing: true
cache_ttl_seconds: 300
Using both sources together
You can enable both at once. To control the order, add an explicit sources list:
secrets:
sources: [onepassword, bitwarden]
onepassword:
enabled: true
env:
OPENAI_API_KEY: "op://Private/OpenAI/api key"
bitwarden:
enabled: true
project_id: "..."
Remember that mapped sources (1Password) automatically outrank bulk sources (Bitwarden) for contested variables, regardless of order. Within the same shape, first source wins.
Step 4: Migrate Plaintext API Keys to the Vault
4.1 Inventory the keys in .env
List the secrets Hermes currently uses so nothing is missed. Group them by blast radius:
- Model API keys:
OPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY, etc. - Platform tokens:
TELEGRAM_BOT_TOKEN,DISCORD_BOT_TOKEN,SLACK_BOT_TOKEN - Cloud credentials:
AWS_ACCESS_KEY_ID,CLOUDFLARE_API_TOKEN,GCP_API_KEY - Third-party tools: GitHub PATs, Sentry DSNs, Stripe keys, etc.
4.2 Create the secrets in the vault
For Bitwarden, create one secret per environment variable in the project you selected. The secret name must exactly match the variable Hermes expects, e.g. OPENAI_API_KEY. When you run hermes secrets bitwarden sync, Hermes will list the variables it can resolve.
For 1Password, create items with the fields you referenced in op://vault/item/field. For example, if you set OPENAI_API_KEY to op://Private/OpenAI/api key, create an item named OpenAI in the Private vault with a field named api key.
Migration order suggestion: Move the highest-impact keys first (Stripe, AWS root-equivalent keys, primary model-provider keys), then lower-risk read-only keys.
4.3 Shrink .env and create an example.env
Once a key lives in the vault, delete or comment it out from .env. Keep only the bootstrap token the source needs:
# ~/.hermes/.env
BWS_ACCESS_TOKEN=0.xxx...
# or, for 1Password:
# OP_SERVICE_ACCOUNT_TOKEN=ops_...
Then create an example.env with variable names and comments, but no real values:
# example.env — real values live in your external vault
OPENAI_API_KEY=see-vault
ANTHROPIC_API_KEY=see-vault
TELEGRAM_BOT_TOKEN=see-vault
New team members know which variables are expected and which vault to check, without anyone emailing them a real .env.
Step 5: Verify, Rotate, and Keep a Rollback Path
5.1 Verify the integration is active
For Bitwarden:
hermes secrets bitwarden status
hermes secrets bitwarden sync # dry-run: preview what would be applied
hermes secrets bitwarden sync --apply # export into the current shell
For 1Password:
hermes secrets onepassword status
hermes secrets onepassword sync # dry-run
hermes secrets onepassword sync --apply # export into the current shell
Start a new hermes process (or cron job, or gateway service) to pick up the resolved values. You can confirm provenance in the startup status output or in the source’s status command.
5.2 Set up rotation reminders
Most vaults let you add a rotation_date field or note. Set a 90-day calendar reminder. When a provider key changes, update the value in the vault — nothing else. The next hermes process start will use the new value.
If the bootstrap token itself leaks or expires, rotate it with the dedicated command without re-running the whole wizard:
hermes secrets bitwarden token
hermes secrets onepassword token
Both commands probe the vault before persisting anything, so a bad paste won’t brick your working setup.
5.3 Keep a rollback path
Don’t move every key out at once. A safer sequence:
- Canary migration: Move one non-critical key (e.g., a read-only search API) to the vault and verify.
- Dual-write observation: Keep both the vault and
.envpopulated, but setoverride_existing: trueon the vault source. Watch for a few days. - Clean removal: Once stable, delete the corresponding lines from
.envand leave only the bootstrap token.
If something breaks, the fastest rollback is to disable the source:
hermes secrets bitwarden disable
hermes secrets onepassword disable
Hermes immediately returns to using only the credentials in .env.
Common Pitfalls
-
Putting real secrets in
config.yaml.config.yamlshould contain references (op://...) and project IDs, not actual secret values. Real values belong in the vault. -
Storing the vault session token in a shared
.envand checking it in.BWS_ACCESS_TOKENandOP_SERVICE_ACCOUNT_TOKENare high-value bearer tokens. Keep.envout of version control and restrict file permissions. -
Expecting
.envto lose automatically. By default,.envand shell exports win. If a source hasoverride_existing: falsebut old keys are still in.env, Hermes will keep using the.envvalues. Setoverride_existing: truewhen the vault should be the source of truth. -
Ignoring conflict warnings. When the same variable appears in multiple sources, Hermes warns you. Don’t silence the warning until you’ve confirmed which source should win, or use
secrets.preserve_existingto pin specific variables to.env. -
Using interactive unlock on a server.
op signinandBW_SESSIONsessions are fine for laptops, but cron jobs, gateways, and CI should use service accounts or machine accounts with non-interactive tokens. -
Forgetting to update
example.env. Once secrets are externalized,example.envbecomes the only documentation of which variables are expected. Keep it in sync with the real vault structure.
Pair This With Smarter Approvals
Moving keys out of .env is the static half of the security story. v0.19.0 also enables Smart Approvals by default: when Hermes wants to run a flagged command, an independent LLM reviewer evaluates it instead of asking you to approve every single one. Combined with the external secret vault, you get two layers of protection:
- Static security: secrets don’t land on disk in plaintext, don’t spread across machines, and are auditable.
- Dynamic security: risky operations get a second opinion, so a single overreaching tool call can’t exfiltrate a key.
Summary
Hermes v0.19.0’s SecretSource moves API key management from “copy-paste into .env” to “inject from an external vault on demand.” Five steps:
- Upgrade to v0.19.0 and confirm
hermes secretsis available. - Authenticate with Bitwarden Secrets Manager or 1Password, and store the bootstrap token in
.env. - Run the setup wizard and configure the source in
config.yaml. - Migrate keys from
.envinto the vault, leaving only the bootstrap token behind. - Verify with
status/sync, rotate on the vault side, and keep a rollback path open.
Your .env can shrink from dozens of secret lines to a single bootstrap token, while Hermes still gets every secret it needs at startup. Team onboarding no longer involves passing around secret files, and token rotation no longer means hunting through a dozen local configs.
References: