feat: add 1Password vault provider integration#113
Conversation
|
Hey @yaniv-golan, thanks for putting this together, appreciate the contribution. this is a pretty substantial PR so we'll need a bit of time to go through it properly. we'll review it soon and follow up with any feedback. thanks again 🙏 @guyb1 🔌 |
|
Great work on this @yaniv-golan, the SSRF protection, encrypted token storage, credential caching, and the concurrent provider lookup with grace period are all really solid. The test coverage is thorough too. Before we build the UI for this, I want to share some thoughts on simplifying the setup experience. I looked at how Anchor Browser handles their 1Password integration, and I think there's a simpler model we should consider. Their approach: one field, paste a Service Account Token, done. Then use What I'm thinking for us:
The Bitwarden setup is one step (paste a code). I'd like 1Password to feel equally simple: paste a token, then add hostname→ What do you think? Happy to hop on a call if it's easier to discuss live. |
|
Thanks Jonathan, really appreciate the thoughtful feedback - and the Anchor Browser reference, you're right, that's a clean model. Fully agree on simplifying to SA-token-only for v1. The op:// reference approach is better UX - users already know these from their CI/CD pipelines and .env files, so there's zero learning curve. And op read is simpler than op item get + field extraction on onecli side too. A few notes on the implementation: What stays as-is: The encrypted token storage, credential caching, concurrent provider lookup with grace period, CORS allowlist, and the provider trait extension — none of that changes. What simplifies:
One question on credential shape: Currently the proxy injects username + password pairs. With op://, each reference resolves to a single value. I'm thinking each mapping points to the specific field the proxy needs (e.g., op://API Keys/Anthropic/credential for an API key). The proxy injects that as the password/token. Does that match what you had in mind? |
|
Yes, single One thing I noticed while testing with the Really appreciate how quickly you aligned on the simplification, and the implementation breakdown you laid out is exactly right. Excited to get this shipped. For next steps:
Sound good? |
macOS TCC impact on CLI modeI just spent a few hours debugging this exact issue on a project that spawns The problemOn macOS Sequoia+, Apple extended TCC ( This means any process that spawns
The "responsible process" is the parent (OneCLI in this case), not What doesn't work
Impact on this PR
SuggestionConsider adding SDK-based auth as an alternative to CLI mode. The 1Password SDKs (available in Rust, JS, Python, Go) authenticate via WASM+HTTPS directly - they never touch the filesystem or Group Containers, so zero TCC prompts. For a Rust project, the References |
|
Good catch on the TCC issue. I did some research and verified the core problem: on macOS Sequoia+, That said, I don't think this changes our v1 plan:
On the SDK suggestion: the Go, JS, and Python SDKs exist, but there's no official Rust crate. The internal Rust core isn't published as a standalone package, so "the For v2, the cleaner path is probably calling the 1Password Connect REST API directly for service account resolution (HTTPS only, zero filesystem access, zero TCC). The Connect infrastructure is already partially built in your PR. But that's a separate discussion. For now, let's ship v1 with @guyb1 pls also take a look 🙏 |
|
@johnnyfish I stand corrected on the Rust SDK - there's no official one. On shipping v1 with Worth documenting that macOS service users need FDA on the binary and that it resets on upgrades. One thing worth tracking: there's an unofficial Rust SDK wrapping the same |
|
One more thing on the FDA-resets-on-upgrade problem. I initially thought we could create a stable wrapper binary at a fixed path (e.g., That doesn't work. TCC checks the actual binary performing the file access, not its parent. A non-bundled parent's FDA does not propagate to child processes. But - an app bundle wrapper does work. macOS has a "responsible process" concept — when an app bundle spawns child processes, its TCC grants cover them. This is the same mechanism that makes Terminal.app's FDA cover all commands run within it. The key difference: app bundles are identified by So if OneCLI shipped as a minimal Users grant FDA once to Not suggesting this for v1 - just documenting the pattern in case the upgrade friction becomes a real issue. |
a346411 to
2647b92
Compare
|
Updated the PR with the simplified implementation:
Tested end-to-end with a live SA token - pair, mapping, credential injection via CONNECT proxy all working. |
2647b92 to
7cb3918
Compare
…/ mappings Add OnePasswordVaultProvider implementing the VaultProvider trait: - Service Account Token authentication (paste token, done) - op:// secret reference mappings (hostname -> op://vault/item/field) - Credential resolution via `op read` at proxy time - Encrypted token storage via CryptoService (AES-256-GCM) - Concurrent provider lookup via JoinSet with 500ms grace period - Credential caching (60s positive, 30s negative TTL) - Error cooldown (60s) with stale mapping tracking - CORS origin allowlist (replaces permissive mirror) - VaultError enum with typed HTTP status responses - Opt-in op CLI installation in Docker (multi-arch) - macOS TCC/FDA setup documentation
7cb3918 to
6f7077f
Compare
|
Reviewed the updated PR, the implementation looks great. SA-token-only We ran a local e2e test against a live SA token: pair, add mappings, status check, invalid reference rejection, hostname validation, delete mapping, all working. Also confirmed with @guyb1 and I are going to run it through more testing this week (credential injection via the CONNECT proxy, cache behavior, error/cooldown paths) and I want him to review the code as well. We'll follow up with our thoughts once we're done, then merge and start on the dashboard UI. Really clean work on the simplification, the diff went from ~2400 lines to ~1400 and the API surface is much simpler to build a UI on top of. |
|
super excited about this one, will test ASAP when merged :D |
|
Hey @yaniv-golan , thanks for the patience on this. The implementation is solid and we've been testing it. One thing I'm still working through is that I have some concerns about bundling the op CLI binary inside our Docker image. It means we're shipping and maintaining a third-party binary inside our image (also for users without 1Password), and spawning a process every time we need to resolve a secret. I'm exploring whether there's a cleaner path (Connect Server sidecar, the unofficial Rust FFI crate, etc.) that avoids embedding the CLI. Still thinking through this, wanted to let you know why it's taking a bit longer on our end! |
|
Hey @guyb1, that's a helpful pushback actually- you're right to question the op CLI dependency. Let me walk through the constraints so you have the full picture, and where I landed: The current state: INSTALL_OP_CLI defaults to false in the Dockerfile, but publish.yml passes true, so the published image ships op to all users regardless of whether they use 1Password. That's not great. Quick fix - separate image tags (onecli:latest without op, onecli:1password with it) would address the "shipping to non-1P users" concern, but doesn't solve the deeper issue of shelling out to a third-party binary per secret resolution. Why alternatives are constrained:
What does exist: an official JS/TS SDK (@1password/sdk) that resolves op:// references natively via client.secrets.resolve("op://vault/item/field"). No CLI, no filesystem access, no TCC issues. Proposed approach: Move secret resolution from the Rust gateway to the Node.js web app layer, following the same pattern as Bitwarden - where the gateway just decrypts and injects stored credentials, and doesn't talk to the vault backend directly. The flow would be:
This removes the op binary entirely, uses an officially supported SDK, and keeps the gateway lean. The tradeoff is a freshness lag on rotation (minutes instead of seconds), but the current implementation already caches for 60s, so comparable in practice. This is a bigger change than what's in the PR today - wanted to check if this aligns with what you had in mind before going down that path. |
|
Hey @yaniv-golan, thanks again for the detailed breakdown and for thinking through the alternatives! One thing we want to avoid is storing resolved secrets from vaults in our DB. We treat the vault as the single source of truth ,and keeping those values in our storage adds a breach surface we'd rather not have. The op:// references are safe to store tho. What about this flow:
This removes the op CLI entirely, uses the official JS SDK, and keeps secrets out of our storage. The tradeoff is the gateway now depends on the web app for vault resolution, which is not great. I think this is still far from optimal and we should probably try to get in touch with someone from the 1Password team to understand what they'd recommend for this kind of integration. But curious what you think of this direction before we commit to it. Again, thank you for your time! |
|
I want to say how excited I am for a 1Password integration! If there is someway I can join in and help move this forward I will. |
|
it's been a hot minute since this was last discussed, any chance it is still in the pipeline? |
Summary
OnePasswordVaultProviderimplementing the existingVaultProvidertrait with three auth modes: Service Account, CLI (desktop app), and Connect Servertokio::JoinSetwith 500ms grace period (Bitwarden preferred)no_proxy(), no redirects, configurable allowlistCryptoService(AES-256-GCM), decrypted in memory per sessionmirror_request()ONEPASSWORD_CLI_MODE=trueenv varopCLI in Docker (multi-arch: amd64/arm64)New endpoints
GET /api/vault/onepassword/info— capabilities and available modes (unauthenticated)POST /api/vault/onepassword/discover/accounts— list signed-in 1Password accounts (CLI mode)POST /api/vault/onepassword/discover/vaults— list vaults with temporary credentialsGET /api/vault/{provider}/vaults— list vaults for paired accountGET /api/vault/{provider}/vaults/{vault_id}/items— list items in a vaultGET /api/vault/{provider}/mappings— list explicit mappingsPUT /api/vault/{provider}/mappings— create/update mapping (validates item exists)DELETE /api/vault/{provider}/mappings/{hostname}— delete mappingTest plan
cargo clippy -- -D warningscleancargo fmt --checkcleanopCLI (v2.32.1) against real 1Password vaults