Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# Bitwarden Secrets Manager
|
||||
|
||||
Pull API keys from [Bitwarden Secrets Manager](https://bitwarden.com/products/secrets-manager/) at process startup instead of storing them in plaintext inside `~/.hermes/.env`. One bootstrap secret (a machine-account access token) replaces N per-provider keys, and rotating a credential becomes a single change in the Bitwarden web app.
|
||||
|
||||
## How it works
|
||||
|
||||
1. You create a **machine account** in Bitwarden Secrets Manager, give it read access to a project, and generate an **access token**.
|
||||
2. Hermes stores that single token in `~/.hermes/.env` as `BWS_ACCESS_TOKEN`.
|
||||
3. Every time `hermes` (or the gateway, or a cron job) starts, after `~/.hermes/.env` has loaded, Hermes calls `bws secret list <project_id>` and sets the returned keys into `os.environ`.
|
||||
4. By default Hermes **overrides** values already in your environment, so Bitwarden is the source of truth — rotate a key once in the web app and every Hermes process picks it up on next start. Flip `override_existing: false` in config if you want `.env` to win instead.
|
||||
|
||||
The `bws` binary is auto-downloaded into `~/.hermes/bin/` on first use — no `apt`, no `brew`, no `sudo`.
|
||||
|
||||
## Why machine accounts (and why no 2FA prompt)
|
||||
|
||||
Bitwarden Secrets Manager is designed for non-interactive workloads: machine accounts can't be 2FA-gated because there's no human in the loop. The access token is the credential. Anyone with it can read every secret the machine account has access to, so treat it like a high-value bearer token — store it in `.env` (not `config.yaml`), and revoke + regenerate from the Bitwarden web app if it ever leaks.
|
||||
|
||||
You set up the machine account *in the web app*, where your normal 2FA applies. After that the token is autonomous.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create a machine account and access token
|
||||
|
||||
In the [Bitwarden web app](https://vault.bitwarden.com) (or [vault.bitwarden.eu](https://vault.bitwarden.eu) for EU accounts):
|
||||
|
||||
1. Switch to **Secrets Manager** from the product switcher.
|
||||
2. Create or pick a **Project** (e.g. "Hermes keys").
|
||||
3. Add your provider keys as secrets. The secret **Name** becomes the environment variable name — use `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`, etc.
|
||||
4. **Machine accounts → New machine account → My Hermes machine** → **Projects** tab → grant Read access to your project.
|
||||
5. **Access tokens** tab → **Create access token** → **Never** expires (or pick a date) → copy the token (starts with `0.`). Bitwarden cannot retrieve it again — keep the copy.
|
||||
|
||||
Secrets Manager is included on the Bitwarden free tier with limits; no paid plan needed to try this.
|
||||
|
||||
### 2. Run the wizard
|
||||
|
||||
```bash
|
||||
hermes secrets bitwarden setup
|
||||
```
|
||||
|
||||
It will:
|
||||
|
||||
1. Download and verify `bws v2.0.0` into `~/.hermes/bin/bws`.
|
||||
2. Prompt you for the access token (input is hidden). Stored in `~/.hermes/.env` as `BWS_ACCESS_TOKEN`.
|
||||
3. Ask which Bitwarden region your machine account belongs to — **US Cloud**, **EU Cloud**, or **self-hosted / custom URL**. Stored in `config.yaml` as `secrets.bitwarden.server_url` and passed to `bws` as `BWS_SERVER_URL`.
|
||||
4. List the projects the machine account can see; pick one. Stored in `config.yaml` as `secrets.bitwarden.project_id`.
|
||||
5. Test-fetch the project's secrets and show you which env vars will resolve.
|
||||
6. Flip `secrets.bitwarden.enabled: true`.
|
||||
|
||||
Non-interactive setup is also supported via flags:
|
||||
|
||||
```bash
|
||||
hermes secrets bitwarden setup \
|
||||
--access-token "$BWS_ACCESS_TOKEN" \
|
||||
--server-url https://vault.bitwarden.eu \
|
||||
--project-id <project-uuid>
|
||||
```
|
||||
|
||||
### 3. Confirm
|
||||
|
||||
```bash
|
||||
hermes secrets bitwarden status
|
||||
```
|
||||
|
||||
From now on, every `hermes` invocation pulls fresh secrets at startup. You'll see a one-line summary in stderr the first time secrets are applied in a process.
|
||||
|
||||
## CLI
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `hermes secrets bitwarden setup` | Interactive wizard (install binary, prompt for token, pick project, test fetch) |
|
||||
| `hermes secrets bitwarden status` | Show config + binary version + token presence/validation |
|
||||
| `hermes secrets bitwarden token` | Rotate the access token: validate the new token against Bitwarden, then store it in `.env` |
|
||||
| `hermes secrets bitwarden sync` | Dry-run: pull secrets now and show what would be applied |
|
||||
| `hermes secrets bitwarden sync --apply` | Pull and export into the current shell's environment |
|
||||
| `hermes secrets bitwarden install` | Just download the pinned `bws` binary (no auth required) |
|
||||
| `hermes secrets bitwarden disable` | Flip `enabled: false`; leaves token + project id in place |
|
||||
|
||||
## Rotating an expired or revoked token
|
||||
|
||||
When the machine-account token expires, gets revoked, or the account is deleted, startup shows:
|
||||
|
||||
```
|
||||
Bitwarden Secrets Manager: Bitwarden rejected the machine-account access token (BWS_ACCESS_TOKEN) — it was likely revoked, expired, or belongs to another region. (...)
|
||||
Bitwarden Secrets Manager: → Run `hermes secrets bitwarden token` to paste a fresh access token ...
|
||||
```
|
||||
|
||||
Fix it without re-running the whole wizard:
|
||||
|
||||
```bash
|
||||
hermes secrets bitwarden token # masked prompt
|
||||
hermes secrets bitwarden token --access-token 0.… # non-interactive
|
||||
```
|
||||
|
||||
The command probes Bitwarden with the new token **before** writing anything — a rejected token leaves your current `.env` untouched. On success it stores the token, clears the fetch caches, and warns if the configured project is not visible to the new machine account.
|
||||
|
||||
## Configuration
|
||||
|
||||
Defaults in `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
secrets:
|
||||
bitwarden:
|
||||
enabled: false
|
||||
access_token_env: BWS_ACCESS_TOKEN
|
||||
project_id: ""
|
||||
server_url: ""
|
||||
cache_ttl_seconds: 300
|
||||
encrypted_cache:
|
||||
enabled: false
|
||||
max_stale_seconds: 0
|
||||
override_existing: true
|
||||
auto_install: true
|
||||
```
|
||||
|
||||
| Key | Default | What it does |
|
||||
|---|---|---|
|
||||
| `enabled` | `false` | Master switch. When false, Bitwarden is never contacted. |
|
||||
| `access_token_env` | `BWS_ACCESS_TOKEN` | Env var name that holds the bootstrap token. Change this if you already use `BWS_ACCESS_TOKEN` for something else. |
|
||||
| `project_id` | `""` | UUID of the project to sync from. |
|
||||
| `server_url` | `""` | Bitwarden region or self-hosted endpoint. Empty = `bws` default (US Cloud, `https://vault.bitwarden.com`). Set to `https://vault.bitwarden.eu` for EU Cloud, or your own URL for self-hosted. Plumbed into the `bws` subprocess as `BWS_SERVER_URL`. |
|
||||
| `cache_ttl_seconds` | `300` | How long an in-process or disk fetch result is reused. Set to `0` to disable fresh-cache reuse. |
|
||||
| `encrypted_cache.enabled` | `false` | Store the last successful fetch in an AES-GCM encrypted cache at `~/.hermes/cache/bws_cache.enc.json`. |
|
||||
| `encrypted_cache.max_stale_seconds` | `0` | When encrypted caching is enabled, allow that cache to be used only after network/timeout failures, up to this age. Authentication failures never use stale secrets. A successful encrypted write removes the legacy plaintext `cache/bws_cache.json`. |
|
||||
| `override_existing` | `true` | When true, Bitwarden values overwrite anything already in env (so rotation in the web app actually takes effect). Flip to `false` if you want `.env` / shell exports to win locally. |
|
||||
| `auto_install` | `true` | When true, `bws` is auto-downloaded into `~/.hermes/bin/` on first use. |
|
||||
|
||||
## Failure modes
|
||||
|
||||
Bitwarden never blocks Hermes startup. If anything goes wrong, you'll see a one-line warning in stderr and Hermes continues with whatever credentials `.env` already had:
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---|---|---|
|
||||
| `BWS_ACCESS_TOKEN is not set` | Enabled in config but token cleared from `.env` | Re-run `hermes secrets bitwarden setup` |
|
||||
| `Bitwarden rejected the machine-account access token … invalid_client` | Token revoked, expired, machine account deleted — or the token belongs to another region (e.g. EU token hitting the US identity endpoint) | Run `hermes secrets bitwarden token` to paste a fresh token; for region mismatches re-run setup and pick EU/self-hosted (or set `secrets.bitwarden.server_url`) |
|
||||
| `bws exited 1: invalid access token` | Token revoked or wrong | Run `hermes secrets bitwarden token` with a new token |
|
||||
| `bws timed out` | Network blocked or Bitwarden API slow | Check connectivity to `api.bitwarden.com` (or your `server_url`) |
|
||||
| `bws binary not available` | `auto_install: false` and `bws` not on PATH | Install manually from [github.com/bitwarden/sdk-sm/releases](https://github.com/bitwarden/sdk-sm/releases) or flip `auto_install` back on |
|
||||
| `Checksum mismatch` | Download corrupted or tampered | Re-run, will retry; if it persists, file an issue |
|
||||
|
||||
Startup warnings now include a `→` remediation line telling you exactly which command fixes the failure.
|
||||
|
||||
## Security notes
|
||||
|
||||
- The bootstrap token (`BWS_ACCESS_TOKEN`) is itself sensitive — anyone with it can read every secret the machine account has access to. Treat it the same as any other API key.
|
||||
- Hermes will refuse to let Bitwarden overwrite the bootstrap token itself, even with `override_existing: true`. If you store `BWS_ACCESS_TOKEN` as a secret inside the project, it's silently skipped during apply.
|
||||
- The `bws` binary download is verified against the published SHA-256 checksum from the same GitHub release. Mismatch aborts the install.
|
||||
- The pinned version (`bws v2.0.0` at time of writing) is updated through PRs to this repo — Hermes does not auto-upgrade `bws` to "latest" because upstream release shapes can change.
|
||||
|
||||
## When NOT to use this
|
||||
|
||||
- **Single-machine personal setups** where `~/.hermes/.env` is fine. You're trading one credential for another and adding a network dependency at startup.
|
||||
- **Air-gapped environments** that can't reach `api.bitwarden.com`.
|
||||
- **CI/CD** where the existing secrets-injection mechanism (GitHub Actions secrets, Vault, etc.) is already set up — pick one path, not two.
|
||||
|
||||
The good case for this is multi-machine fleets, shared dev boxes, gateway VPSes, or any setup where you want centralized rotation and revocation across multiple Hermes installations.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Command Helper Secret Source
|
||||
|
||||
Resolve credentials by running your own helper command at startup — any secret store with a CLI works: `keepassxc-cli`, `secret-tool` (GNOME Keyring), `pass`, `gpg`, Vaultwarden's CLI, or a script that cats a tmpfs env file. The helper prints `KEY=VALUE` lines on stdout; Hermes applies them through the same orchestrator as [Bitwarden](./bitwarden) and [1Password](./onepassword), so you can enable any combination of sources simultaneously.
|
||||
|
||||
## How it works
|
||||
|
||||
1. You configure a helper command in `config.yaml` (never in `.env` — the command is configuration, `.env` holds values).
|
||||
2. At startup, after `.env` loads, Hermes runs the helper ONCE via `/bin/sh -c` and parses its stdout as a dotenv blob.
|
||||
3. The parsed keys flow through the standard precedence ladder: `.env`/shell win unless `override_existing: true`; mapped sources beat this bulk source on contested vars; first claim wins.
|
||||
|
||||
```yaml
|
||||
secrets:
|
||||
command:
|
||||
enabled: true
|
||||
command: "cat /run/user/1000/hermes-secrets.env"
|
||||
# or any vault CLI that dumps KEY=VALUE lines:
|
||||
# command: "pass show hermes/env"
|
||||
# command: "secret-tool lookup service hermes-env"
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
| Key | Default | What it does |
|
||||
|---|---|---|
|
||||
| `enabled` | `false` | Master switch. |
|
||||
| `command` | `""` | Helper run via `/bin/sh -c`; must print `KEY=VALUE` lines on stdout. |
|
||||
| `helper_timeout_seconds` | `3` | Hard timeout for one helper run. Deliberately tight — the helper must be fast and NON-interactive (no unlock prompts, no touch/PIN). |
|
||||
| `override_existing` | `false` | Helper values overwrite `.env`/shell values. Off by default (unlike Bitwarden/1Password) since a local helper is not a central rotation authority. |
|
||||
|
||||
## Security model
|
||||
|
||||
- The helper command string is YOUR configuration — same trust level as the `.env` file you control.
|
||||
- Output is hard-capped at 1 MiB; a runaway helper can't wedge startup (process group killed on timeout).
|
||||
- The helper's **stderr is discarded** — vault CLI diagnostics can carry secret material, so they never reach Hermes' output. Failures log structured fields only (exit code / signal / errno), never the command string.
|
||||
- Whitespace-only values are treated as "no value" — a placeholder entry never flows into an Authorization header.
|
||||
- POSIX-only (needs `/bin/sh`). On Windows the source reports itself unconfigured and startup continues.
|
||||
|
||||
## Failure modes
|
||||
|
||||
Startup is never blocked. Errors print one line plus a `→` remediation hint:
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---|---|---|
|
||||
| `secrets.command.command is empty` | Enabled without a command | Set `secrets.command.command` in config.yaml |
|
||||
| `helper command failed` | Non-zero exit, timeout, spawn failure | Run the helper manually in a shell to see its real error (Hermes discards its stderr on purpose) |
|
||||
| `helper output was not a KEY=VALUE map` | Helper printed a bare value or garbage | Make the helper emit dotenv-shaped lines |
|
||||
|
||||
## When to use this vs a plugin
|
||||
|
||||
The command source is the escape hatch for vaults without a bundled integration. If you find yourself wrapping a complex CLI dance in a long script, consider a proper [secret-source plugin](/developer-guide/secret-source-plugin) instead — plugins get caching, provenance labels, and typed config.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Secrets
|
||||
|
||||
Hermes can pull API keys from external secret managers at process startup instead of storing them in `~/.hermes/.env`. The bootstrap token for the secret manager lives in `.env`; every other provider key (OpenAI, Anthropic, OpenRouter, etc.) can stay in the manager and rotate centrally.
|
||||
|
||||
Supported:
|
||||
|
||||
- [Bitwarden Secrets Manager](./bitwarden) — `bws` CLI, lazy-installed, free tier works.
|
||||
- [1Password](./onepassword) — `op://` references via the official `op` CLI; service-account or desktop session auth.
|
||||
- [Command helper](./command) — any CLI vault (`keepassxc-cli`, `secret-tool`, `pass`, custom scripts) via a user-configured helper that prints `KEY=VALUE` lines.
|
||||
|
||||
## Multiple sources at once
|
||||
|
||||
You can enable more than one secret source at the same time — for example a team Bitwarden project alongside a personal vault plugin. Sources compose per env var with a deterministic precedence ladder:
|
||||
|
||||
1. **Your `.env` / shell wins by default.** A source only replaces a pre-existing value when its own `override_existing: true` is set (Bitwarden defaults to true so central rotation works).
|
||||
2. **Mapped sources beat bulk sources.** A source where you explicitly bind env vars to references (an `env:` map) outranks a source that injects a whole project of secrets implicitly, regardless of ordering.
|
||||
3. **First source wins.** Within the same shape, the order of the optional `secrets.sources` list (or registration order) decides. Later claims on an already-claimed var are skipped — with a startup warning, never silently.
|
||||
|
||||
`override_existing` never lets one source overwrite a var another source already claimed, and no source can ever overwrite another source's bootstrap token (e.g. `BWS_ACCESS_TOKEN`).
|
||||
|
||||
```yaml
|
||||
secrets:
|
||||
sources: [bitwarden] # optional explicit ordering
|
||||
bitwarden:
|
||||
enabled: true
|
||||
project_id: "..."
|
||||
```
|
||||
|
||||
Every credential injected by a source is labelled with its origin — setup flows and `hermes model` show `(from Bitwarden)` next to detected keys so you always know where a value came from.
|
||||
|
||||
## Profiles and shared vaults
|
||||
|
||||
Two orchestrator-level knobs make one shared vault safe across [profiles](../profiles):
|
||||
|
||||
- **`secrets.preserve_existing`** — a list of env var names whose existing `.env` / shell value always wins, even against a source with `override_existing: true`. Use it for per-profile platform secrets (e.g. `FEISHU_APP_SECRET`) that intentionally differ across profiles while everything else rotates centrally:
|
||||
|
||||
```yaml
|
||||
secrets:
|
||||
preserve_existing: [FEISHU_APP_SECRET, TELEGRAM_BOT_TOKEN]
|
||||
```
|
||||
|
||||
- **Profile aliasing** (on by default, `secrets.profile_alias: false` to disable) — when Hermes runs under a named profile, a vault secret named `FOO_<PROFILE>` (credential-shaped suffixes only: `*_API_KEY`, `*_TOKEN`, `*_SECRET`, `*_KEY`, `*_PASSWORD`) also hydrates the canonical `FOO`. Store `TELEGRAM_BOT_TOKEN_MILLA` in the shared project and the `milla` profile's adapters — which read the fixed name `TELEGRAM_BOT_TOKEN` — get the right value automatically. A var the vault supplies directly under its canonical name always beats an alias.
|
||||
|
||||
Both apply to every source — bundled and plugin — because they live in the orchestrator, not the backends.
|
||||
|
||||
## Adding your own backend
|
||||
|
||||
Third-party secret managers ship as standalone plugins, not core PRs. A backend subclasses `agent.secret_sources.base.SecretSource` (one required method: `fetch(cfg, home_path) -> FetchResult`) and registers via `ctx.register_secret_source(MySource())` in the plugin's `register(ctx)`. The orchestrator owns precedence, conflict handling, timeouts, and provenance — your source only fetches. Full guide with the contract rules, subprocess-safety helper, and conformance kit: [Building a Secret Source Plugin](/developer-guide/secret-source-plugin).
|
||||
|
||||
The bundled set is deliberately closed (same policy as memory providers): Bitwarden and 1Password ship in-tree. Everything else — Infisical, Proton Pass, HashiCorp Vault, AWS Secrets Manager, OS keystores — belongs in plugin repos; share them in the Nous Research Discord (`#plugins-skills-and-skins`).
|
||||
@@ -0,0 +1,169 @@
|
||||
# 1Password
|
||||
|
||||
Resolve provider API keys from [1Password](https://1password.com/) at process startup instead of storing them in plaintext inside `~/.hermes/.env`. You keep your keys as 1Password items and reference them by `op://vault/item/field`; rotating a credential becomes a single change in 1Password.
|
||||
|
||||
## How it works
|
||||
|
||||
1. You install the official [1Password CLI](https://developer.1password.com/docs/cli/get-started/) (`op`) and authenticate it — either with a **service-account token** (headless servers) or an **interactive/desktop session** (your laptop).
|
||||
2. You map environment-variable names to `op://` references in `~/.hermes/config.yaml`.
|
||||
3. Every time `hermes` (or the gateway, or a cron job) starts, after `~/.hermes/.env` has loaded, Hermes runs `op read` for each reference and sets the resolved values into `os.environ`.
|
||||
4. By default Hermes **overrides** values already in your environment, so 1Password is the source of truth — rotate a credential once and every Hermes process picks it up on next start. Flip `override_existing: false` if you want `.env` to win instead.
|
||||
|
||||
Hermes never authenticates on your behalf and never downloads `op`: it shells out to your already-installed, already-trusted CLI. If `op` is missing, your session is locked, or a reference is wrong, Hermes prints a one-line warning and continues with whatever credentials `.env` already had — it never blocks startup.
|
||||
|
||||
## Authentication
|
||||
|
||||
`op` supports two non-interactive-friendly modes; Hermes works with either:
|
||||
|
||||
- **Service accounts** (recommended for servers/CI): create a service account in 1Password, grant it read access to the relevant vault, and export its token as `OP_SERVICE_ACCOUNT_TOKEN` in `~/.hermes/.env`. The token is the credential — treat it like any other bearer token.
|
||||
- **Desktop / interactive sessions** (laptops): run `op signin` (or enable CLI integration in the 1Password app). Hermes passes your `OP_SESSION_*` variables through to the `op` child process. The 1Password cache key includes those session variables, so signing into a different account never serves a value cached under the previous identity.
|
||||
|
||||
## Bootstrap token
|
||||
|
||||
When you authenticate with a **service-account token**, that token is itself the bootstrap credential Hermes needs *before* it can resolve any `op://` reference. It must be present in `os.environ` of every process that resolves secrets — including cron jobs (`kanban.dispatch_in_gateway: false`), subprocess invocations, CLI runs, macOS launchd agents, and Docker containers — not just the interactive gateway. There are three ways to make it available, in order of precedence:
|
||||
|
||||
1. **In `~/.hermes/.env` (recommended).** `hermes secrets onepassword setup --token <token>` writes the token to `~/.hermes/.env`, exactly like Bitwarden's `BWS_ACCESS_TOKEN`. Because `load_hermes_dotenv()` always loads `.env`, the token is available everywhere with zero extra setup. This is the simplest reliable option.
|
||||
|
||||
2. **In `~/.hermes/.op.env` (gitignored).** If you'd rather keep the service-account token out of `.env` — for example so `.env` can be checked into a private dotfiles repo while the token stays out of version control — place it in `~/.hermes/.op.env`:
|
||||
|
||||
```bash
|
||||
echo 'OP_SERVICE_ACCOUNT_TOKEN=ops_...' > ~/.hermes/.op.env
|
||||
chmod 600 ~/.hermes/.op.env
|
||||
```
|
||||
|
||||
Hermes auto-loads `.op.env` at startup, **after** `.env`, and **never** overrides a token already present in the environment. `.op.env` is gitignored so the token never enters a committed file.
|
||||
|
||||
3. **Via systemd `EnvironmentFile` (Linux gateway).** If you run the gateway under systemd, you can inject the token directly into the service environment:
|
||||
|
||||
```ini
|
||||
[Service]
|
||||
EnvironmentFile=-/home/youruser/.hermes/.op.env
|
||||
```
|
||||
|
||||
A token injected this way takes precedence — Hermes detects that `OP_SERVICE_ACCOUNT_TOKEN` is already set and skips loading `.op.env` entirely.
|
||||
|
||||
If the token is reachable only through an interactive shell (`op signin`, `OP_SESSION_*` exports in `.bashrc`, etc.), it will **not** be inherited by cron jobs or freshly spawned subprocesses, and those contexts will log a warning and fall back to whatever credentials `.env` already held. Use one of the three options above for any non-interactive workload.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install and sign in to `op`
|
||||
|
||||
Follow the [1Password CLI getting-started guide](https://developer.1password.com/docs/cli/get-started/). Verify it works:
|
||||
|
||||
```bash
|
||||
op whoami
|
||||
```
|
||||
|
||||
### 2. Enable the integration
|
||||
|
||||
```bash
|
||||
hermes secrets onepassword setup
|
||||
```
|
||||
|
||||
This verifies `op` is on `PATH` (or use `--binary-path`), records your account/token settings, checks for an active session, and flips `secrets.onepassword.enabled: true`. Non-interactive flags:
|
||||
|
||||
```bash
|
||||
hermes secrets onepassword setup \
|
||||
--account my.1password.com \
|
||||
--token-env OP_SERVICE_ACCOUNT_TOKEN \
|
||||
--token "$OP_SERVICE_ACCOUNT_TOKEN"
|
||||
```
|
||||
|
||||
### 3. Map your credentials
|
||||
|
||||
The reference format is `op://<vault>/<item>/<field>`:
|
||||
|
||||
```bash
|
||||
hermes secrets onepassword set OPENAI_API_KEY "op://Private/OpenAI/api key"
|
||||
hermes secrets onepassword set ANTHROPIC_API_KEY "op://Private/Anthropic/credential"
|
||||
```
|
||||
|
||||
### 4. Preview and confirm
|
||||
|
||||
```bash
|
||||
hermes secrets onepassword sync # dry-run: resolve now, show what would apply
|
||||
hermes secrets onepassword status # config + binary + references + auth
|
||||
```
|
||||
|
||||
From now on, every `hermes` invocation resolves the references at startup. You'll see a one-line summary in stderr the first time secrets are applied in a process.
|
||||
|
||||
## CLI
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `hermes secrets onepassword setup` | Verify `op`, set account / token env var, enable |
|
||||
| `hermes secrets onepassword status` | Show config, binary, auth, and configured references |
|
||||
| `hermes secrets onepassword token` | Rotate the service-account token: validate with `op whoami`, then store it in `.env` |
|
||||
| `hermes secrets onepassword set ENV_VAR "op://…"` | Map an env var to a reference (stored stripped + validated) |
|
||||
| `hermes secrets onepassword remove ENV_VAR` | Drop a mapping |
|
||||
| `hermes secrets onepassword sync` | Dry-run: resolve references now and show what would apply |
|
||||
| `hermes secrets onepassword sync --apply` | Resolve and export into the current shell's environment |
|
||||
| `hermes secrets onepassword disable` | Flip `enabled: false`; leaves mappings in place |
|
||||
|
||||
`op` and `1password` are accepted as aliases for `onepassword`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Defaults in `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
secrets:
|
||||
onepassword:
|
||||
enabled: false
|
||||
env:
|
||||
OPENAI_API_KEY: "op://Private/OpenAI/api key"
|
||||
ANTHROPIC_API_KEY: "op://Private/Anthropic/credential"
|
||||
account: ""
|
||||
service_account_token_env: OP_SERVICE_ACCOUNT_TOKEN
|
||||
binary_path: ""
|
||||
cache_ttl_seconds: 300
|
||||
override_existing: true
|
||||
```
|
||||
|
||||
| Key | Default | What it does |
|
||||
|---|---|---|
|
||||
| `enabled` | `false` | Master switch. When false, `op` is never invoked. |
|
||||
| `env` | `{}` | Mapping of env-var name → `op://vault/item/field` reference. Entries whose name isn't a valid env-var name, or whose value isn't an `op://` reference, are skipped with a warning. |
|
||||
| `account` | `""` | Account shorthand / sign-in address passed as `op read --account`. Empty uses `op`'s default account. |
|
||||
| `service_account_token_env` | `OP_SERVICE_ACCOUNT_TOKEN` | Env var Hermes reads the service-account token from. Its value is exported to the `op` child as `OP_SERVICE_ACCOUNT_TOKEN` (the name `op` expects). Leave the var unset to use a desktop/interactive session. |
|
||||
| `binary_path` | `""` | Absolute path to `op`. When set, it is used verbatim and `PATH` is **not** consulted — pin this to avoid trusting whatever `op` appears first on `PATH`. |
|
||||
| `cache_ttl_seconds` | `300` | How long resolved values are reused (in-process and on disk). Set to `0` to disable **both** cache layers — no values are written to disk at all. |
|
||||
| `override_existing` | `true` | When true, resolved values overwrite anything already in env (so rotation takes effect). Flip to `false` to let `.env` / shell exports win; those references are then skipped *before* `op` is invoked. |
|
||||
|
||||
## Failure modes
|
||||
|
||||
1Password never blocks Hermes startup. If anything goes wrong you'll see a one-line warning in stderr and Hermes continues:
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---|---|---|
|
||||
| `the op CLI was not found on PATH` | `op` not installed / not on PATH | Install the CLI, or set `secrets.onepassword.binary_path` |
|
||||
| `op read failed for 'op://…': …` | Locked session, expired token, or no vault access | `op signin`, run `hermes secrets onepassword token` to rotate the service-account token, or grant the service account access |
|
||||
| `op read returned an empty value for 'op://…'` | The referenced field exists but is empty | Fix the item/field in 1Password (an empty value is never applied — your existing env var is left intact) |
|
||||
| `… is not an op:// secret reference` | A mapping value isn't an `op://` reference | Re-set it with the correct `op://vault/item/field` form |
|
||||
| `op read timed out` | Network blocked or 1Password slow | Check connectivity / the desktop app integration |
|
||||
|
||||
Startup warnings now include a `→` remediation line telling you exactly which command fixes the failure.
|
||||
|
||||
## Caching
|
||||
|
||||
Successful, complete pulls are cached in-process and on disk under `<hermes_home>/cache/op_cache.json` (written atomically, mode `0600`), so back-to-back short-lived `hermes` invocations don't re-shell `op` for every reference. The cache:
|
||||
|
||||
- stores only resolved secret **values** — never the service-account token or any raw auth material (auth is fingerprinted into the cache key);
|
||||
- is invalidated when the token, account, `OP_SESSION_*` variables, or the set of references change;
|
||||
- is **not** written when a pull had any per-reference error, so a transient auth failure isn't frozen in for the TTL;
|
||||
- is fully disabled — reads *and* writes — when `cache_ttl_seconds: 0`.
|
||||
|
||||
## Security notes
|
||||
|
||||
- A 1Password service-account token can read every secret the account has access to. Store it in `~/.hermes/.env` (not `config.yaml`), and revoke + regenerate from 1Password if it leaks.
|
||||
- Hermes refuses to let a resolved value overwrite the token env var itself, even with `override_existing: true`.
|
||||
- The `op` child process gets a minimal allowlisted environment (auth/session vars + `PATH`/`HOME`), not a copy of the full `os.environ`, so post-dotenv provider credentials aren't all inherited by the child.
|
||||
- References are validated to start with `op://`, and the reference is passed after a `--` option terminator so a crafted value can't be parsed as an `op` flag.
|
||||
|
||||
## When NOT to use this
|
||||
|
||||
- **Single-machine personal setups** where `~/.hermes/.env` is fine.
|
||||
- **Air-gapped environments** that can't reach 1Password.
|
||||
- **CI/CD** where an existing secrets-injection mechanism is already wired up — pick one path, not two.
|
||||
|
||||
The good case for this is multi-machine fleets, shared dev boxes, gateway VPSes, or anywhere you want centralized rotation and revocation across multiple Hermes installations.
|
||||
Reference in New Issue
Block a user