You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support out-of-tree providers via gRPC interface #64
+1 — landed here while working around exactly this gap. Concrete use case + a sketch of an alternative transport that might be lower-friction than gRPC for the most common case.
Our setup
We have an internal tool op-proxy that wraps the 1Password CLI with approval gates, audit logging, and a local cache. It's intentionally machine-local — every read is op-proxy read --reason "<text>" --cache <ttl> op://Vault/.../field, and the reason gets surfaced to a desktop approval prompt or recorded in an audit log.
SECRETSPEC_OPCLI_PATH doesn't fit:
op-proxy's CLI shape differs from op (op-proxy read -r <reason> <op://...> vs op item get --format=json <name>).
The built-in onepassword:// provider owns its own naming (secretspec/{project}/{profile}/{key}) — we read arbitrary op://Vault/... refs.
--reason is required on every call; secretspec can't supply one through the current trait.
So we patched a real opproxy:// provider into a fork (~250 lines of Rust modeled on env.rs/onepassword.rs, registered through the existing linkme::distributed_slice). Works great as a fork, but we don't want to maintain a Nix overlay rebase against every upstream release, and the provider has no place in upstream because op-proxy is intentionally machine-local.
On the transport
gRPC works but introduces a real dependency footprint (tonic, prost, schema versioning, codegen) and a long-lived subprocess lifecycle problem (when does the plugin server start/stop? how does it authenticate to the host? how does it pass --reason-style per-call context cleanly?).
Two cheaper alternatives worth considering:
WASM component-model plugins (wasmtime + WIT). The host loads .wasm plugins from a config-declared path; each plugin exports a small WIT interface (get(project, profile, key) -> result<option<string>, error>, optionally set, reflect). No transport, no codegen for the plugin author beyond wit-bindgen, no process lifecycle, sandboxed by default. Closest production references: Zellij plugins, Spin, OPA's Rego.
Stdin/stdout subprocess plugins (like git credential helpers / docker credential helpers). Host invokes secretspec-provider-<scheme> with a small JSON request on stdin, reads JSON response. Trivial for plugin authors in any language; no codegen. Closer to the pattern most tools that already do "external provider" use. Less sandboxed than WASM but very simple.
For our case (single-binary op-proxy), the stdin/stdout pattern is the lightest possible escape hatch and would let us delete the Nix overlay. WASM is the more principled long-term answer if you want plugin distribution as .wasm artifacts.
Happy to prototype either against provider/mod.rs if there's interest in a direction.
Per-session context map carries metadata like --reason, populated from a secretspec run --context key=value flag, SDK builder, or SECRETSPEC_CONTEXT_<KEY> env vars
Streaming / lease-refresh deferred to v2 (Dynamic secrets #11), WASM and gRPC explicitly non-goals for v1
The page itself carries a draft warning, so merging the PR doesn't commit us to anything beyond having something to react to.
@schickling-assistant the opproxy:// case is the load-bearing example I designed around: per-call --reason lands as context.reason in hello, missing required context fails the handshake with an invalid_request error so the user sees a clear message. Does the spec as drafted absorb the rest of your fork, or are there shapes (set semantics, multi-vault, anything else) that still need a slot?
Hi @domenkozar our team is currently evaluating devenv and cachix and one of the roadblocks we are running into is exactly what @schickling-assistant ran into.
All of our infra references 1Password secrets via granular references op://Vault/.../field and it's not something we would be able to change. If the secretspec 1pw provider supported secret references, we wouldn't even have a desire to extend secretspec in the first place.
If we go with secretspec's current approach with op now, it would fragment how we manage secrets.
Hi @domenkozar the preference would be a direct reference otherwise we'd have a lot of indirection on how we define our secrets. We'd need to declare one provider per secret if I understand right? And would we lose batch loading?
Our current workaround is to use op run instead of the secretspec commands. We introduced our own ref keyword that we parse out:
@schickling-assistant I've added #122 for approval gates and #121 for arbitrary refs. Would that remove the need for your wrapper (we should still ship it, but these things should be part of secretspec anyway).
Closing #345 in favor of this tracking issue, as this perfectly captures the plugin architecture we need!
We recently built sudo-secretspec (#345) to solve the security pain point of AI agents running locally as the user and having unfettered read-access to secrets. Our solution uses an isolated service-user vault and a sudo companion binary to enforce a physical boundary. We were about to propose a generic exec:// provider to allow upstream to integrate with us cleanly, but it looks like you are already designing the ideal solution here.
Regarding the discussion and the proposed spec in #98, here is how it relates to our use case:
Transport (Stdin/stdout subprocess vs WASM vs gRPC): The stdio subprocess model is by far the best option for us. WASM's strict sandboxing would make it extremely difficult (or impossible) for us to invoke sudo to transition to our service-user and read the isolated filesystem vault. The Git Credential Helper model is the perfect amount of flexibility for OS-level privilege boundaries.
Context Map (--reason): Your inclusion of the context.reason in the hello handshake is fantastic. Our entire service-user boundary relies on producing an immutable SQLite ledger of why the AI requested the secret. Having secretspec natively forward the --reason flag via the IPC context map makes our integration seamless.
Distribution (secretspec-provider-foo): The implicit $PATH resolution fits perfectly into our Homebrew packaging. We can simply ship our binary as secretspec-provider-sudo and users can use sudo:// in their manifests.
This direction fully supports our architecture without requiring any upstream macOS-specific maintenance. We are very excited for this spec to land!
Activity
domenkozar commentedon Mar 11, 2026
Could be a provider called grpc that has a spec written how it works. Happy to merge that :)
domenkozar commentedon Mar 19, 2026
Could also be something more lightweight like Secretspec Server Protocol using JSONRPC
schickling-assistant commentedon May 23, 2026
+1 — landed here while working around exactly this gap. Concrete use case + a sketch of an alternative transport that might be lower-friction than gRPC for the most common case.
Our setup
We have an internal tool
op-proxythat wraps the 1Password CLI with approval gates, audit logging, and a local cache. It's intentionally machine-local — every read isop-proxy read --reason "<text>" --cache <ttl> op://Vault/.../field, and the reason gets surfaced to a desktop approval prompt or recorded in an audit log.SECRETSPEC_OPCLI_PATHdoesn't fit:op-proxy's CLI shape differs fromop(op-proxy read -r <reason> <op://...>vsop item get --format=json <name>).onepassword://provider owns its own naming (secretspec/{project}/{profile}/{key}) — we read arbitraryop://Vault/...refs.--reasonis required on every call; secretspec can't supply one through the current trait.So we patched a real
opproxy://provider into a fork (~250 lines of Rust modeled onenv.rs/onepassword.rs, registered through the existinglinkme::distributed_slice). Works great as a fork, but we don't want to maintain a Nix overlay rebase against every upstream release, and the provider has no place in upstream because op-proxy is intentionally machine-local.On the transport
gRPC works but introduces a real dependency footprint (tonic, prost, schema versioning, codegen) and a long-lived subprocess lifecycle problem (when does the plugin server start/stop? how does it authenticate to the host? how does it pass
--reason-style per-call context cleanly?).Two cheaper alternatives worth considering:
WASM component-model plugins (wasmtime + WIT). The host loads
.wasmplugins from a config-declared path; each plugin exports a small WIT interface (get(project, profile, key) -> result<option<string>, error>, optionallyset,reflect). No transport, no codegen for the plugin author beyondwit-bindgen, no process lifecycle, sandboxed by default. Closest production references: Zellij plugins, Spin, OPA's Rego.Stdin/stdout subprocess plugins (like git credential helpers / docker credential helpers). Host invokes
secretspec-provider-<scheme>with a small JSON request on stdin, reads JSON response. Trivial for plugin authors in any language; no codegen. Closer to the pattern most tools that already do "external provider" use. Less sandboxed than WASM but very simple.For our case (single-binary
op-proxy), the stdin/stdout pattern is the lightest possible escape hatch and would let us delete the Nix overlay. WASM is the more principled long-term answer if you want plugin distribution as.wasmartifacts.Happy to prototype either against
provider/mod.rsif there's interest in a direction.domenkozar commentedon May 28, 2026
Drafted a full v1 spec as a docs page so the wire contract is concrete enough to react to: #98.
TL;DR of the direction:
foo://...resolves tosecretspec-provider-fooon$PATHhellohandshake with capability advertisement, thenget/set/batch_get/reflect/byenot_found,auth_failed,permission_denied,rate_limited,unsupported,unsupported_version,invalid_request,internal)contextmap carries metadata like--reason, populated from asecretspec run --context key=valueflag, SDK builder, orSECRETSPEC_CONTEXT_<KEY>env varsThe page itself carries a draft warning, so merging the PR doesn't commit us to anything beyond having something to react to.
@schickling-assistant the
opproxy://case is the load-bearing example I designed around: per-call--reasonlands ascontext.reasoninhello, missing required context fails the handshake with aninvalid_requesterror so the user sees a clear message. Does the spec as drafted absorb the rest of your fork, or are there shapes (set semantics, multi-vault, anything else) that still need a slot?venkatd commentedon Jul 5, 2026
Hi @domenkozar our team is currently evaluating devenv and cachix and one of the roadblocks we are running into is exactly what @schickling-assistant ran into.
All of our infra references 1Password secrets via granular references
op://Vault/.../fieldand it's not something we would be able to change. If the secretspec 1pw provider supported secret references, we wouldn't even have a desire to extend secretspec in the first place.If we go with secretspec's current approach with op now, it would fragment how we manage secrets.
domenkozar commentedon Jul 6, 2026
@venkatd would #121 work?
venkatd commentedon Jul 6, 2026
Hi @domenkozar the preference would be a direct reference otherwise we'd have a lot of indirection on how we define our secrets. We'd need to declare one provider per secret if I understand right? And would we lose batch loading?
Our current workaround is to use
op runinstead of the secretspec commands. We introduced our ownrefkeyword that we parse out:Example in our
secretspec.tomlI think fnox has a good reference on the ideal too: https://fnox.jdx.dev/providers/1password.html#referencing-secrets
domenkozar commentedon Jul 6, 2026
@schickling-assistant I've added #122 for approval gates and #121 for arbitrary refs. Would that remove the need for your wrapper (we should still ship it, but these things should be part of secretspec anyway).
feat!: native secret references (`ref`) as provider independent coord…
feat!: native secret references (`ref`) as provider independent coord…
feat!: native secret references (`ref`) as provider independent coord…
feat!: native secret references (`ref`) as provider independent coord…
feat!: native secret references (`ref`) as provider independent coord…
feat!: native secret references (`ref`) as provider independent coord…
domenkozar commentedon Jul 9, 2026
https://secretspec.dev/blog/secretspec-0-14-secret-references/
feat!: sync upstream SecretSpec through v0.14.0 (#24)
venkatd commentedon Jul 14, 2026
@domenkozar this is great! We're about to sign up for a cachix plan :)
djbclark commentedon Aug 14, 2026
Closing #345 in favor of this tracking issue, as this perfectly captures the plugin architecture we need!
We recently built
sudo-secretspec(#345) to solve the security pain point of AI agents running locally as the user and having unfettered read-access to secrets. Our solution uses an isolated service-user vault and asudocompanion binary to enforce a physical boundary. We were about to propose a genericexec://provider to allow upstream to integrate with us cleanly, but it looks like you are already designing the ideal solution here.Regarding the discussion and the proposed spec in #98, here is how it relates to our use case:
stdiosubprocess model is by far the best option for us. WASM's strict sandboxing would make it extremely difficult (or impossible) for us to invokesudoto transition to our service-user and read the isolated filesystem vault. The Git Credential Helper model is the perfect amount of flexibility for OS-level privilege boundaries.--reason): Your inclusion of thecontext.reasonin thehellohandshake is fantastic. Our entire service-user boundary relies on producing an immutable SQLite ledger of why the AI requested the secret. Havingsecretspecnatively forward the--reasonflag via the IPC context map makes our integration seamless.secretspec-provider-foo): The implicit$PATHresolution fits perfectly into our Homebrew packaging. We can simply ship our binary assecretspec-provider-sudoand users can usesudo://in their manifests.This direction fully supports our architecture without requiring any upstream macOS-specific maintenance. We are very excited for this spec to land!
Track cachix/secretspec#64
domenkozar commentedon Aug 16, 2026
See #362
docs: add upstream contact ledger and two owed upstream drafts
docs: post the check-stdout issue and PR, track upstream's IPC v1 PR