feat(secrets): add Proton Pass (pass://) secret source - #52621
Closed
jhbarnett wants to merge 1 commit into
Closed
Conversation
Resolve provider credentials at startup from Proton Pass `pass://vault/item/field` references via the official `pass-cli`, alongside the existing Bitwarden Secrets Manager source. Users map env-var names to references under `secrets.protonpass.env`. After `.env` loads, each reference is resolved with `pass-cli run --no-masking -- <python> -c <echo>` (documented substitution semantics; the wrapped interpreter keeps resolution cross-platform) and injected into `os.environ`. Auth uses Proton Pass's persistent session: an existing session is reused, and `pass-cli login` (consuming `PROTON_PASS_PERSONAL_ACCESS_TOKEN` via the child env, never argv) is only attempted on an auth-shaped failure, then retried once. Fail-open and startup-safe: a missing binary, login failure, bad reference, or empty value each surface a one-line warning and Hermes continues with whatever `.env` already had. Successful complete pulls are cached in-process and on disk (`<hermes_home>/cache/protonpass_cache.json`, atomic 0600, TTL); only resolved values are stored, the token is fingerprinted into the cache key. The `env_loader` hook is refactored into per-backend handlers so a disabled Bitwarden section no longer short-circuits other sources. Adds the backend module, config schema + migration (v31), docs, and hermetic tests (subprocess mocked). Self-contained against main; can be rebased onto the shared agent/secret_sources/_cache.py from NousResearch#36896 once it lands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds a Proton Pass secret source so provider credentials can be resolved at startup from
pass://vault/item/fieldreferences via the officialpass-cli— alongside the existing Bitwarden Secrets Manager source, and mirroring the 1Password source proposed in #36949 / #36896.Users map environment-variable names to references under
secrets.protonpass.env:After
~/.hermes/.envloads, each reference is resolved and injected intoos.environ— the same startup hook the Bitwarden source uses. It is startup-safe and fail-open: a missing binary, a login failure, a bad reference, or an empty value each surface a one-line warning and Hermes continues with whatever.envalready had.Why this approach
Two things make Proton Pass differ from the
bws/opintegrations, and drove the design:pass-clilogs in once (pass-cli login, which readsPROTON_PASS_PERSONAL_ACCESS_TOKEN) and stores a session in a platform keyring. So Hermes reuses an existing session and only attempts apass-cli login— token passed via the child env, never argv — when a resolve fails for an auth-shaped reason, then retries once. You can also establish the session out of band (e.g. in a container entrypoint) and leave the token unset.readcommand. Resolution goes through the documentedrunsubstitution:pass-cli run --no-masking -- <python> -c "<echo the env var>", with the reference placed in that env var.--no-maskingis required (otherwiserunreplaces the value with<concealed by Proton Pass>on stdout). Hermes wraps its own Python interpreter rather than a POSIX builtin likeprintenv, so resolution is identical on Linux, macOS, and Windows.Hermes never downloads
pass-cli; it shells out to your already-installed, already-trusted CLI.Related Issue
Relates to #36949 (1Password request, same pluggable interface). No dedicated Proton Pass issue yet.
Type of Change
env_loadersplit into per-backend handlersChanges Made
agent/secret_sources/protonpass.py(new) — reference validation,pass-cli runresolution, lazypass-cli login+ retry, allowlisted child env, in-process + disk cache, fail-openapply_protonpass_secrets().hermes_cli/env_loader.py— refactor_apply_external_secret_sourcesinto per-backend handlers (_apply_bitwarden,_apply_protonpass) each in its own guard, so a disabled/broken section can't abort startup or short-circuit the other sources; add a(from Proton Pass)origin label.hermes_cli/config.py—secrets.protonpassdefaults + a v30→31 seeding migration.agent/secret_sources/__init__.py— document the new backend.website/docs/user-guide/secrets/{index,protonpass}.md— docs.tests/test_protonpass_secrets.py(new) + additions totests/test_env_loader_secret_sources.py— hermetic,subprocessmocked.Note on the cache substrate
This backend keeps its
FetchResult/ disk-cache plumbing self-contained (mirroringbitwarden.pyonmain) so it merges without depending on the unmerged sharedagent/secret_sources/_cache.pyfrom #36896. Happy to rebase onto that substrate once it lands.How to Test
Live (optional):
pass-cli loginwith a personal access token, add asecrets.protonpassblock mapping an env var to apass://reference, then start anyhermescommand and confirm the one-line "Proton Pass: applied N secret(s)" status and that the value is present in the environment.Checklist
Code
feat(secrets): …)Documentation & Housekeeping
website/docs/user-guide/secrets/)cli-config.yaml.example— N/A (it does not document thesecrets:section; defaults live inconfig.pyDEFAULT_CONFIG, consistent with the Bitwarden source)