Skip to content

Commit 1bbca6a

Browse files
committed
Merge branch 'main' into registry-import-bug-fix
2 parents 3fb3961 + 16aca06 commit 1bbca6a

17 files changed

Lines changed: 1332 additions & 41 deletions

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,87 @@ Hermes files are managed in:
259259
- `~/.hermes/state.db` — session history database
260260
- `~/.hermes/cron/jobs.json` — scheduled tasks
261261
262+
## Secrets provider
263+
264+
By default, API keys live in `~/.hermes/.env` (the **env** provider). No
265+
configuration is needed — this is byte-for-byte the historical behavior, and
266+
nothing changes for you.
267+
268+
If you'd rather not keep keys in a plaintext `.env`, the opt-in **command**
269+
provider resolves them by running a helper command you configure. Resolution
270+
order everywhere is: `process.env` → `.env` → provider → unset.
271+
272+
Per-key helper (the requested key name arrives as `$HERMES_SECRET_KEY`):
273+
274+
```yaml
275+
# ~/.hermes/config.yaml
276+
secrets:
277+
provider: command
278+
command: secret-tool lookup hermes "$HERMES_SECRET_KEY"
279+
```
280+
281+
Or a helper that dumps a dotenv blob (e.g. a vault that unseals into tmpfs):
282+
283+
```yaml
284+
secrets:
285+
provider: command
286+
command: "cat /run/user/1000/hermes-secrets.env"
287+
```
288+
289+
The helper's stdout may be either a single bare value (per-key helpers) or
290+
`KEY=VALUE` lines (dotenv dumps); both shapes are auto-detected.
291+
292+
### Vault / secret manager integration (no TPM required)
293+
294+
The `command` provider is **vault-agnostic** — it runs whatever helper you
295+
configure and reads its stdout. The helper is the only thing that needs to
296+
talk to your secret store. If you don't have a TPM-sealed keyfile, any of
297+
these work without code changes to Hermes:
298+
299+
- **KeePassXC (password-only DB, no keyfile):** point `secrets.command` at a
300+
small `kpxc-export.sh` script that does
301+
`keepassxc-cli ls ~/secrets/hermes.kdbx <<<"$KPXC_PASSWORD"` and dumps
302+
the relevant group as dotenv. Prompt the user for the master password
303+
once per session.
304+
- **GnuPG with a passphrase-only key:** `gpg --batch --passphrase-fd 0
305+
--decrypt ~/.keys/api-keys.gpg` works directly as the `command` value.
306+
Pass the passphrase via a file descriptor or env var, never argv.
307+
- **`pass` (the standard unix password manager):**
308+
`command: "pass show hermes/$HERMES_SECRET_KEY"` for a per-key helper,
309+
or a small wrapper script for a dotenv dump.
310+
- **`secret-tool` (libsecret/Gnome Keyring):**
311+
`command: "secret-tool lookup hermes $HERMES_SECRET_KEY"` (already
312+
shown above as the canonical per-key example).
313+
- **Bitwarden CLI:** `bw get item "$HERMES_SECRET_KEY" | jq -r .notes`
314+
(after `bw unlock` in the session).
315+
- **1Password CLI:** `op read "op://vault/$HERMES_SECRET_KEY/credential"`.
316+
- **Plain env file with user-managed permissions:**
317+
`command: "cat ~/.config/hermes/secrets.env"` with `chmod 600` and
318+
the file owned by your user. Not as secure as a vault, but better than
319+
a world-readable `.env`.
320+
321+
The point: **any helper that prints a value (per-key) or a dotenv blob
322+
(list-mode) on stdout will work**, and Hermes imposes a 3-second timeout
323+
and 1 MiB output cap on the helper so a misbehaving one can't wedge the
324+
app. The provider makes no assumptions about TPM, FIDO2, smart cards,
325+
or platform keychains.
326+
327+
Security model:
328+
329+
- The command string is your own configuration — same trust level as `.env`.
330+
It runs via `/bin/sh -c`, so the command provider is POSIX-only
331+
(Linux/macOS); Windows stays on the env provider.
332+
- The helper inherits the process environment plus `HERMES_SECRET_KEY`; the
333+
key name is passed as data, never interpolated into the shell string.
334+
- Hard 3-second timeout (resolution is synchronous on the main process — keep
335+
helpers fast and non-interactive), 1 MiB output cap, and stderr is discarded.
336+
- Resolved values are never logged or written to disk; failures degrade to
337+
"key unset", logging only exit code/signal.
338+
- The gateway-spawn broadcast uses a single `list()` call, never a per-key
339+
helper loop.
340+
341+
Source of truth: [`src/main/secrets/`](src/main/secrets/).
342+
262343
## Tech Stack
263344
264345
- **Electron** 39 — cross-platform desktop shell

0 commit comments

Comments
 (0)