Skip to content

feat(secrets): add 1Password secret source - #32254

Closed
tsaiDavid wants to merge 4 commits into
NousResearch:mainfrom
tsaiDavid:feat/secrets-onepassword
Closed

feat(secrets): add 1Password secret source#32254
tsaiDavid wants to merge 4 commits into
NousResearch:mainfrom
tsaiDavid:feat/secrets-onepassword

Conversation

@tsaiDavid

@tsaiDavid tsaiDavid commented May 25, 2026

Copy link
Copy Markdown

Add generic 1Password op:// secret-reference resolution behind the existing secret-source interface. Wire startup loading, source labels, hermes secrets onepassword CLI commands, docs, and tests.

What does this PR do?

This adds first-class 1Password support for resolving environment credentials at Hermes startup without storing resolved secret values in config.yaml.

Users configure secrets.onepassword.env as a mapping from environment variable names to official 1Password op://vault/item/field references. After .env loading, Hermes resolves those references through the official op CLI and injects them into os.environ, matching the existing external-secret-source pattern used by Bitwarden Secrets Manager.

The implementation is intentionally defensive and startup-safe: missing op, expired auth, locked desktop app sessions, bad references, or permission failures report warnings and fall back to whatever credentials were already present from .env or the shell.

This also adds short-lived-process performance support. Successful reads are cached in-process and on disk under <hermes_home>/cache/op_cache.json, with 0600 permissions and a TTL controlled by secrets.onepassword.cache_ttl_seconds. The cache key uses auth/session fingerprints, account, and configured references; it does not store the service-account token or raw auth material.

Related Issue

Fixes #36949.

Related competing PRs: #36896, #45439.

#45439 was called out as a duplicate of this PR and includes unrelated branch contamination; this PR is the narrower, reviewed, and currently mergeable implementation.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • agent/secret_sources/onepassword.py
    • Adds 1Password op://... reference validation and op read integration.
    • Supports optional --account selection.
    • Supports configurable service-account-token env var via service_account_token_env.
    • Adds non-fatal fetch/apply behavior for startup loading.
    • Adds in-process and disk cache layers controlled by cache_ttl_seconds.
    • Skips fetching values that would be ignored when override_existing: false.
  • agent/secret_sources/__init__.py
    • Registers/describes the shipped 1Password secret source.
  • hermes_cli/env_loader.py
    • Applies 1Password secrets after .env loading.
    • Records onepassword as the credential source for display labels.
    • Passes hermes_home through so disk cache paths are profile-aware.
  • hermes_cli/config.py
    • Adds default secrets.onepassword config keys.
  • cli-config.yaml.example
    • Adds a commented 1Password external-secret-source example.
  • hermes_cli/onepassword_secrets_cli.py
    • Adds hermes secrets onepassword commands: setup, status, sync, set, remove, and disable.
  • hermes_cli/main.py
    • Wires the 1Password CLI aliases into the existing hermes secrets command tree.
  • tests/test_onepassword_secrets.py
    • Adds hermetic tests for fetch/apply behavior, validation, failures, account/token handling, caching, CLI commands, and skip behavior.
  • tests/test_env_loader_secret_sources.py
    • Adds source-label/env-loader coverage for 1Password.
  • website/docs/user-guide/secrets/onepassword.md
    • Adds setup, CLI, config, failure-mode, caching, and security documentation.
  • website/docs/user-guide/secrets/index.md
    • Links the new 1Password docs page.

How to Test

  1. Run the targeted lint checks:

    uv run --extra dev ruff check agent/secret_sources/onepassword.py hermes_cli/env_loader.py hermes_cli/config.py hermes_cli/onepassword_secrets_cli.py tests/test_onepassword_secrets.py tests/test_env_loader_secret_sources.py
  2. Run the targeted tests:

    uv run --extra dev pytest tests/test_onepassword_secrets.py tests/test_env_loader_secret_sources.py -q
  3. Optionally smoke-test with a temporary Hermes home and a fake op binary to verify startup loading and disk-cache reuse across separate processes.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu/Linux 6.8.0-111-generic, Python 3.11 via uv

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
    • N/A; this follows the existing secret-source architecture and does not change contributor workflows.
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A
    • N/A; no model tool schema changes.

@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have labels May 25, 2026

@flamerged flamerged left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a thorough pass on this — a clean, well-scoped, well-tested PR. Detailed findings are left as inline comments on the relevant lines; overview below.

What's done well

  • No shell=True anywhere — op is always invoked with an argv list, closing OS-level command injection.
  • Secret values never hit logs, argv, or stdout (only env-var names/actions are printed).
  • The service-account token is handled with care: never written to the disk cache, fingerprinted (truncated SHA-256) rather than stored raw in the cache key, with a regression test; the token env var is never overwritten even under override_existing=true.
  • Fail-open discipline + at-rest hygiene are solid: never raises out of apply, atomic mkstemp -> chmod 0600 -> os.replace disk writes, 30s subprocess timeout, thorough tests.

Findings (details inline)

Severity Location Issue
🔴 Blocking onepassword.py:110 disk-cache substrate copy-pasted from bitwarden.py — centralize
🟠 Major onepassword.py:389 empty op read (rc 0) silently clobbers a good env var
🟠 Major onepassword.py:398 _auth_fingerprint reads literal OP_SESSION (should be OP_SESSION_*)
🟠 Major env_loader.py:309 unguarded float(cache_ttl_seconds) can crash startup
🟠 Minor onepassword.py:362 op child inherits the full os.environ (blast radius)
🟠 Minor onepassword.py:415 _reset_cache_for_tests doesn't clear the disk cache
🧹 Nit onepassword.py:373 subprocess lacks encoding="utf-8" (locale-codepage decode)
🧹 Nit onepassword.py:384 failure message can echo op stdout (200 chars)
🧹 Nit onepassword.py:119 cache dir created without explicit 0700 mode
🧹 Nit onepassword.py:296 empty refs_to_fetch can fall through
🧹 Nit onepassword.py:173 op resolved via PATH only, no pinning (informational)
🧹 Nit …cli.py:178 override_existing default differs (status/sync False vs startup True)
🧹 Nit …cli.py:192 sync --apply reimplements the apply policy
🧹 Nit …cli.py:261 cmd_set persists the unstripped reference

These are peer notes — we recently built the Proton Pass secret source and hit several of the same questions (env-inheritance scope, fingerprint correctness, the non-blocking guarantee, shared-substrate-vs-copy). Take or leave any of them; you know this codebase and its trust boundaries best. Thanks for a thoughtful PR.

@flamerged flamerged left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-filed my earlier review as inline, line-anchored comments below — each tagged by severity (🔴 Blocking / 🟠 Major / 🟠 Minor / 🧹 Nitpick) with a suggested fix. High-level overview is in the review above. Thanks again for a clean, well-tested PR.

Comment thread agent/secret_sources/onepassword.py
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread hermes_cli/env_loader.py Outdated
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread agent/secret_sources/onepassword.py
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread agent/secret_sources/onepassword.py
Comment thread agent/secret_sources/onepassword.py

@flamerged flamerged left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more, surfaced by diffing this against an independent Proton Pass secret source I built in parallel off the same bitwarden template — spots where the two implementations diverged. All low-severity except the first, which is a fail-open break (and the same pattern is in the bitwarden branch). Peer notes — take or leave.

Comment thread hermes_cli/env_loader.py Outdated
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread agent/secret_sources/onepassword.py Outdated
Comment thread agent/secret_sources/onepassword.py Outdated
tsaiDavid and others added 2 commits June 2, 2026 10:15
Add generic 1Password op:// secret-reference resolution behind the existing secret-source interface. Wire startup loading, source labels, hermes secrets onepassword CLI commands, docs, and tests.
@tsaiDavid
tsaiDavid force-pushed the feat/secrets-onepassword branch from 5967cf6 to ec5c352 Compare June 2, 2026 17:15
@tsaiDavid

tsaiDavid commented Jun 2, 2026

Copy link
Copy Markdown
Author

Follow-up pushed in commit cecf6b9 to close the remaining review notes:

  • added an op read option terminator and preserved account flag ordering
  • stripped full ANSI CSI sequences from surfaced op errors and kept stdout out of failure messages
  • made cache_ttl_seconds <= 0 disable both in-memory and disk cache writes
  • partitioned the in-process 1Password cache by HERMES_HOME / profile home
  • made non-dict Bitwarden/1Password config sections fail open instead of aborting startup
  • added regression coverage for those paths

Local verification on rebased main: 77 passed for tests/test_bitwarden_secrets.py, tests/test_onepassword_secrets.py, and tests/test_env_loader_secret_sources.py, plus compileall and git diff --check.

@tsaiDavid

Copy link
Copy Markdown
Author

Follow-up pushed in commit 923d950 to tighten the shared cache behavior touched by the 1Password review work:

  • made Bitwarden respect cache_ttl_seconds <= 0 as a full cache opt-out, matching the 1Password path from the previous hardening commit
  • added regression coverage proving TTL 0 skips both in-process cache reuse and disk cache writes

Local verification on the PR branch:

  • scripts/run_tests.sh tests/test_bitwarden_secrets.py tests/test_onepassword_secrets.py tests/test_env_loader_secret_sources.py -- -q: 78 passed
  • uv run --extra dev ruff check agent/secret_sources/bitwarden.py agent/secret_sources/onepassword.py agent/secret_sources/disk_cache.py hermes_cli/env_loader.py hermes_cli/onepassword_secrets_cli.py tests/test_bitwarden_secrets.py tests/test_onepassword_secrets.py tests/test_env_loader_secret_sources.py: all checks passed
  • python3 -m compileall agent/secret_sources/bitwarden.py agent/secret_sources/onepassword.py agent/secret_sources/disk_cache.py hermes_cli/env_loader.py hermes_cli/onepassword_secrets_cli.py: succeeded
  • git diff --check: clean

@tsaiDavid

Copy link
Copy Markdown
Author

@kshitijk4poor could you help route/decide the canonical 1Password implementation here?

There are now multiple competing 1Password PRs:

I’ve updated this branch through the review feedback, and it is currently mergeable. Local verification is posted above: targeted scripts/run_tests.sh passed, ruff passed, compileall passed, and git diff --check is clean.

Happy to adjust this PR if maintainers prefer a detail from #36896, but I think the next step is a maintainer decision on which implementation should be canonical for #36949.

@ericelizes

Copy link
Copy Markdown

Been looking for a feature like this, really like the approach

@teknium1

teknium1 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

1Password support landed on main via PR #59498. Your PR was the earliest 1Password submission in the cluster (May 25) — thank you for kicking this off. The merged implementation is a salvage of #36896 adapted onto a new pluggable SecretSource interface (multiple vaults can now be enabled at once with deterministic precedence), so this PR is superseded. Both you and @hwrdprkns are credited in the merge PR body.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: 1Password (op://) secret source backend

5 participants