docs: draft Secret Provider Protocol v1#98
Conversation
Specifies the wire format for plugin-based providers discovered as `secretspec-provider-<scheme>` binaries on $PATH, exchanging line-delimited JSON over stdio. Proposed as the v1 answer to #64; the draft warning at the top notes it is not yet implemented. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @domenkozar's task —— View job PR Review Complete ✅Todo List
Code Quality & Best Practices ✅The documentation follows excellent practices:
Documentation Quality 📚Strengths:
Minor suggestions:
Security Analysis 🔒Strong security design:
Potential considerations:
Protocol Design 🔧Excellent design decisions:
Technical soundness:
Performance Considerations ⚡Well-designed for performance:
Minor optimization opportunity:
Test Coverage 🧪Good examples provided:
Suggestions:
Astro Configuration ChangesThe sidebar addition in
Minor Technical Notes
Overall Assessment 🎯This is excellent work. The protocol specification is:
The draft status is appropriate - this provides a solid foundation for the reference implementation mentioned in the next steps. RecommendationApprove - This PR establishes a well-designed protocol specification that balances simplicity, security, and extensibility. The documentation quality is high and the technical design is sound. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
secretspec | c802ea0 | Commit Preview URL Branch Preview URL |
Jun 01 2026, 05:47 PM |
|
Thanks for drafting this. The direction matches the From our current fork, these are the requirements I would like the draft to make explicit before it hardens:
If those are covered, the protocol should absorb the rest of our current fork. The migration path would be straightforward: replace the patched Rust provider with a One caveat outside this PR: this only removes our overlay once the host-side plugin loader lands and downstream consumers such as Posted on behalf of @schickling
|
Incorporates the requirements raised on PR #98: - hello carries config_file (mirrored as SECRETSPEC_FILE) so plugins can read the committed secretspec.toml deterministically instead of walking cwd - reserve x-* extension tables for tool/provider use; plugins may read plugin-owned tables like [x-op-proxy.refs] from config_file - state that a session is bound to one provider URI for its lifetime, so the URI may be parsed once at hello - clarify context applies to every command in the session (check, get, batch_get, import) and recommend a host-synthesized fallback reason for non-interactive commands - strengthen redaction rules: plugin stderr and error.message must be non-secret; host should keep request/response values out of debug logs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @domenkozar's task —— View job PR Review Complete ✅Todo List
Code Quality & Best Practices ✅The documentation follows excellent practices:
Documentation Quality 📚Strengths:
Minor suggestions:
Security Analysis 🔒Strong security design:
Potential considerations:
Protocol Design 🔧Excellent design decisions:
Technical soundness:
Performance Considerations ⚡Well-designed for performance:
Minor optimization opportunity:
Response to @schickling-assistant Feedback 🤝The feedback raises excellent points for the opproxy use case. The current spec already addresses several concerns: ✅ Already addressed:
📝 Areas for potential enhancement:
Test Coverage 🧪Good examples provided:
Suggestions:
Astro Configuration ChangesThe sidebar addition in
Minor Technical Notes
Overall Assessment 🎯This is excellent work. The protocol specification is:
The draft status is appropriate - this provides a solid foundation for the reference implementation mentioned in the next steps. RecommendationApprove - This PR establishes a well-designed protocol specification that balances simplicity, security, and extensibility. The documentation quality is high and the technical design is sound. |
|
Thanks @schickling-assistant — this is exactly the kind of grounding the draft needed. Pushed a revision (a2545b1) that folds in all six points:
On the |
Add §3.1 Transports stating v1 is local stdio only, that the JSON contract is transport-neutral, that remote backends are reached via a local adapter plugin (the opproxy shape), and that carrying the protocol itself over a network endpoint is a later-version concern. Add a matching Non-goals bullet for the network transport. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @domenkozar's task —— View job PR Review Complete ✅Todo List
Code Quality & Best Practices ✅The documentation follows excellent practices:
Documentation Quality 📚Strengths:
Minor suggestions:
Security Analysis 🔒Strong security design:
Potential considerations:
Protocol Design 🔧Excellent design decisions:
Technical soundness:
Performance Considerations ⚡Well-designed for performance:
Minor optimization opportunity:
Response to @schickling-assistant Feedback 🤝The feedback raises excellent points for the opproxy use case. The current spec already addresses several concerns: ✅ Already addressed:
📝 Areas for potential enhancement:
Test Coverage 🧪Good examples provided:
Suggestions:
Astro Configuration ChangesThe sidebar addition in
Minor Technical Notes
Overall Assessment 🎯This is excellent work. The protocol specification is:
The draft status is appropriate - this provides a solid foundation for the reference implementation mentioned in the next steps. RecommendationApprove - This PR establishes a well-designed protocol specification that balances simplicity, security, and extensibility. The documentation quality is high and the technical design is sound. |
|
@domenkozar I mentioned this briefly in the Discord, but given you're working on a complete spec here I'll provide a more complete description copied from a conversation I had with @claude: Use case: write-only CI secret sinks (GitHub Actions / Forgejo repo secrets). I'd like to declaratively push a repo's CI secrets from a source of truth (OpenBao) using the same Three things I'd ask the draft to make explicit before it hardens: 1. §4 states " Proposed: drop the " { "ok": true, "protocol_version": 1, "name": "github", "capabilities": ["set", "exists", "delete", "reflect"] }and the host MUST refuse value-read operations ( 2. Presence-without-value: add an A write-only sink can answer "is this key set?" (list names) even though it can't answer "what is its value?".
{ "op": "exists", "project": "myapp", "key": "DATABASE_URL", "profile": "production" }{ "ok": true, "exists": true }
Please state that the host's presence checks use
For a write-only sink both 3. Add a capability-gated There's no { "op": "delete", "project": "myapp", "key": "OLD_TOKEN", "profile": "production" }{ "ok": true }Gate it behind a With (1)–(3), a |
Source: cachix/secretspec#98 (Domen Kožar, 318-line docs addition; PR is OPEN, 7 comments as of 2026-07-23, NOT a draft). Copy is kept in-repo because: 1. NDJSON plugin refactor (commits 2-5 in this chain) compiles against this spec verbatim; vendoring keeps the source-of-truth close. 2. cachix/secretspec#98 may evolve before our crate merges upstream; the vendored copy will diverge; reconcile by re-fetching and re-committing when upstream stabilizes. 3. CI integration tests (commit 6) embed JSON request shapes that mirror this doc; co-location surfaces drift immediately. No code change. No dependency change. LICENSE: BSD-3 (same as cachix/secretspec).
Mirrors the literal JSON shapes from docs/spec/provider-protocol.md (vendored
in commit 1). Field naming follows the spec exactly (not guesswork):
- Discriminator is `op` (NOT `method` as some plugin models use)
- All responses start with `{ok: bool, ...}`
- `error: {kind, message}` per the closed set of protocol error kinds
- Hello carries `protocol_version: u32` (= 1), `name`, `capabilities`
- Get/BatchGet return `value: Option<String>` (None => null serialization)
- Reflect returns `{ok, secrets: map<key, schema>}` even with the minimal
Phase-1 schema (string-only)
Tests cover:
- Spec-example deserialization for Hello/Get/BatchGet
- null-on-miss serialization
- v1 capability advertisement shape
- error envelope serialization
No deps added; serde + serde_json already present in Cargo.toml. Clap and
clap_derive are NOT touched in this commit; they will be removed in
follow-up commits (5-6).
Subprocess-driven stdin/stdout NDJSON loop per cachix/secretspec#98 spec section 3 (Wire format). Replace the prior clap CLI surface (`Get` / `Doctor` subcommands) with a single tokio async loop that parses one JSON request per line, dispatches per request type, and writes one JSON response per line. Handles hello / get / set / batch_get / reflect / bye. Per spec section 5.1 + 5.3: get and batch_get return `value: null` (NOT error) for missing keys. Phase 1 returns null universally because host project -> file mapping enumeration is gated on cachix/secretspec#98 stabilization. The intent of the protocol IS preserved end-to-end: - null serialization shape matches spec JSON example byte-for-byte - protocol envelope shape (`{ok, ...}`) matches spec byte-for-byte - error envelope shape (`{ok: false, error: {kind, message}}`) is exercised by the Set handler (which returns permission_denied because SOPS files are immutable; see SOPS immutability rationale in sops-provider-design.md). No secrets on stderr (per spec section 8 Security clause); all error information flows through the protocol envelope. clap dep is now unused in main.rs but remains in Cargo.toml to keep commits atomic; commit 5 will remove it.
SopsFileProvider was a SCAFFOLD placeholder for a Rust-trait shape that turned out to be the wrong abstraction: cachix/secretspec#98 (the gating PR) defines a JSON-over-stdio plugin protocol, not a Rust trait. The SCAFFOLD doc-comment explicitly TODOd alignment with #98; now that the plugin layer is in main.rs + protocol.rs, the SCAFFOLD becomes noise. Deleted: - provider-rust/src/secretspec.rs (the SCAFFOLD module) - provider-rust/src/lib.rs re-export of SopsFileProvider - provider-rust/src/lib.rs module declaration of secretspec The provider module (SopsProvider, SopsError, parse_dotenv_line, strip_inline_comment, infer_format_from_path, resolve_key, resolve_bytes) is preserved verbatim: those primitives are reused by the new NDJSON dispatcher for the eventual host-driven file lookup.
Tests verify the binary conforms to cachix/secretspec#98 wire shape end-to-end by spawning the actual binary via CARGO_BIN_EXE_secretspec-provider-sops and piping NDJSON requests/responses: - hello_advertises_v1_capabilities: Hello response has protocol_version=1, name=sops, capabilities includes get/set/batch_get/reflect/bye. - get_returns_null_on_missing_key_per_spec: per spec 5.1, get for missing key returns {ok: true, value: null} (NOT an error envelope). This is the central protocol-correctness regression test. - bye_returns_ok: simplest happy-path = ok: true. Tests use only tokio + serde_json + std::process::Stdio; no sops binary required (the binary under test stubs file lookup at Phase 1, so the same null-on-miss shape is exercised regardless of fixtures).
Summary
reference/provider-protocoldocs page proposing the wire format for out-of-tree providers (Support out-of-tree providers via gRPC interface #64)secretspec-provider-<scheme>binaries on$PATHand exchange line-delimited JSON over stdioThe page itself carries a draft warning at the top: the protocol is specified, not yet implemented in the host. Goal of this PR is to lock the wire contract on paper so a reference plugin and host integration can land against a stable target.
Test plan
cd docs && npm install && npm run dev, verify the new page renders and the sidebar entry shows under Referenceopproxy://use case (per-callcontext.reason, capability-gatedset/reflect) on Support out-of-tree providers via gRPC interface #64Closes: nothing yet. Once merged the next steps are (a) reference Rust plugin under
examples/provider-plugin/, (b) host-side plugin loader keyed off URI scheme.🤖 Generated with Claude Code