diff --git a/docs/gateway/secrets.md b/docs/gateway/secrets.md index 987fc801252..3f6be621c01 100644 --- a/docs/gateway/secrets.md +++ b/docs/gateway/secrets.md @@ -335,6 +335,8 @@ the config fields that accept SecretRefs. - `BWS_ACCESS_TOKEN` available to the Gateway service. - `PATH` passed to the resolver, or `BWS_BIN` set to the absolute `bws` binary path. + - `BWS_SERVER_URL` must be set in the environment when using a self-hosted + Bitwarden instance. ```json5 { @@ -343,7 +345,7 @@ the config fields that accept SecretRefs. bws: { source: "exec", command: "/usr/local/bin/openclaw-bws-resolver.mjs", - passEnv: ["BWS_ACCESS_TOKEN", "PATH", "BWS_BIN"], + passEnv: ["BWS_ACCESS_TOKEN", "BWS_SERVER_URL", "PATH", "BWS_BIN"], jsonOnly: true, }, }, diff --git a/scripts/secrets/openclaw-bws-resolver.mjs b/scripts/secrets/openclaw-bws-resolver.mjs index 79b5571d18b..8cb62fbd4e4 100755 --- a/scripts/secrets/openclaw-bws-resolver.mjs +++ b/scripts/secrets/openclaw-bws-resolver.mjs @@ -51,6 +51,7 @@ const main = async () => { encoding: "utf8", env: { BWS_ACCESS_TOKEN: process.env.BWS_ACCESS_TOKEN, + BWS_SERVER_URL: process.env.BWS_SERVER_URL, PATH: process.env.PATH || "", }, maxBuffer: 1024 * 1024, diff --git a/test/scripts/openclaw-bws-resolver.test.ts b/test/scripts/openclaw-bws-resolver.test.ts new file mode 100644 index 00000000000..a27a4cc25ce --- /dev/null +++ b/test/scripts/openclaw-bws-resolver.test.ts @@ -0,0 +1,59 @@ +import { spawnSync } from "node:child_process"; +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +const tempDirs: string[] = []; +const resolverPath = path.resolve("scripts/secrets/openclaw-bws-resolver.mjs"); + +function makeTempDir(): string { + const dir = mkdtempSync(path.join(tmpdir(), "openclaw-bws-resolver-")); + tempDirs.push(dir); + return dir; +} + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { force: true, recursive: true }); + } +}); + +describe("openclaw-bws-resolver", () => { + it("forwards the self-hosted server URL without inheriting unrelated variables", () => { + const dir = makeTempDir(); + const fakeBwsPath = path.join(dir, "bws"); + writeFileSync( + fakeBwsPath, + [ + "#!/usr/bin/env node", + 'if (process.env.BWS_ACCESS_TOKEN !== "test-token") process.exit(10);', + 'if (process.env.BWS_SERVER_URL !== "https://bws.example.test") process.exit(11);', + "if (process.env.UNRELATED_PARENT_VALUE !== undefined) process.exit(12);", + 'process.stdout.write(JSON.stringify([{ key: "example", value: "resolved" }]));', + ].join("\n"), + { mode: 0o755 }, + ); + chmodSync(fakeBwsPath, 0o755); + + const result = spawnSync(process.execPath, [resolverPath], { + encoding: "utf8", + env: { + BWS_ACCESS_TOKEN: "test-token", + BWS_BIN: fakeBwsPath, + BWS_SERVER_URL: "https://bws.example.test", + PATH: process.env.PATH ?? "", + UNRELATED_PARENT_VALUE: "do-not-forward", + }, + input: JSON.stringify({ protocolVersion: 1, ids: ["example"] }), + }); + + expect(result.status).toBe(0); + expect(result.stderr).toBe(""); + expect(JSON.parse(result.stdout)).toEqual({ + protocolVersion: 1, + values: { example: "resolved" }, + errors: {}, + }); + }); +});