From ec0f9e15427b31b7018bc4c0fefedd6624e88923 Mon Sep 17 00:00:00 2001 From: Mariano Belinky Date: Fri, 27 Feb 2026 18:44:14 +0000 Subject: [PATCH] docs(secrets): add Bitwarden Secrets exec resolver example --- docs/gateway/secrets.md | 33 ++++++++++++++++ scripts/secrets/openclaw-bws-resolver | 56 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100755 scripts/secrets/openclaw-bws-resolver diff --git a/docs/gateway/secrets.md b/docs/gateway/secrets.md index 9fdec280d611..f727e4d07261 100644 --- a/docs/gateway/secrets.md +++ b/docs/gateway/secrets.md @@ -213,6 +213,39 @@ Optional per-id errors: } ``` +### Bitwarden Secrets (BWS) + +This example uses a small wrapper script that implements the exec provider protocol and calls the Bitwarden Secrets CLI (`bws`) once per batch. + +- Script: `scripts/secrets/openclaw-bws-resolver` +- `bws` must be installed and authenticated via `BWS_ACCESS_TOKEN` + +```json5 +{ + secrets: { + providers: { + bws: { + source: "exec", + // Point this at wherever you install the resolver. + command: "/usr/local/bin/openclaw-bws-resolver", + args: [], + passEnv: ["BWS_ACCESS_TOKEN", "PATH"], + jsonOnly: true, + }, + }, + }, + models: { + providers: { + openai: { + baseUrl: "https://api.openai.com/v1", + models: [{ id: "gpt-5", name: "gpt-5" }], + apiKey: { source: "exec", provider: "bws", id: "OPENAI_API_KEY" }, + }, + }, + }, +} +``` + ### `sops` ```json5 diff --git a/scripts/secrets/openclaw-bws-resolver b/scripts/secrets/openclaw-bws-resolver new file mode 100755 index 000000000000..35dce64f5f65 --- /dev/null +++ b/scripts/secrets/openclaw-bws-resolver @@ -0,0 +1,56 @@ +#!/usr/bin/env node +// +// OpenClaw SecretRef exec resolver for Bitwarden Secrets (bws). +// Protocol v1: reads JSON from stdin, returns JSON on stdout. +// + +const { execFileSync } = require("child_process"); + +const BWS = "/usr/local/bin/bws"; + +async function main() { + let input = ""; + for await (const chunk of process.stdin) input += chunk; + + const req = JSON.parse(input); + if (req.protocolVersion !== 1) { + process.stderr.write(`unsupported protocol version: ${req.protocolVersion}\n`); + process.exit(1); + } + + const ids = req.ids || []; + if (ids.length === 0) { + process.stdout.write(JSON.stringify({ protocolVersion: 1, values: {} })); + return; + } + + let secrets; + try { + const raw = execFileSync(BWS, ["secret", "list"], { + env: { BWS_ACCESS_TOKEN: process.env.BWS_ACCESS_TOKEN, PATH: process.env.PATH || "" }, + timeout: 15000, + maxBuffer: 1024 * 1024, + }); + secrets = JSON.parse(raw.toString()); + } catch (err) { + process.stderr.write(`bws secret list failed: ${err.message}\n`); + process.exit(1); + } + + const keyMap = {}; + for (const s of secrets) keyMap[s.key] = s.value; + + const values = {}; + const errors = {}; + for (const id of ids) { + if (id in keyMap) values[id] = keyMap[id]; + else errors[id] = { message: `secret key "${id}" not found in BWS` }; + } + + process.stdout.write(JSON.stringify({ protocolVersion: 1, values, errors })); +} + +main().catch((err) => { + process.stderr.write(`resolver error: ${err.message}\n`); + process.exit(1); +});