From 2e410053e3c12af79c58609e31ac9f366408e21d Mon Sep 17 00:00:00 2001 From: Enzo Ricciulli Date: Wed, 8 Apr 2026 19:10:19 +0200 Subject: [PATCH] fix: resolve gateway secret from OpenClaw credentials store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If no secret is found in plugin config or env vars, fall back to reading /credentials/gateway_token via api.runtime.state.resolveStateDir(). This covers local installs where the HMAC secret lives in the credentials store rather than the process environment (e.g. launchd agents without shell profile sourcing). Existing priority order is preserved — env vars still take precedence over the credential file. No config schema changes. --- src/gateway-secret.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/gateway-secret.ts b/src/gateway-secret.ts index e90f5a6..6a30d3b 100644 --- a/src/gateway-secret.ts +++ b/src/gateway-secret.ts @@ -1,8 +1,17 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; /** * Resolve the gateway HMAC secret from config or environment variables. * + * Priority: + * 1. Plugin config (gateway.auth.token) + * 2. OPENCLAW_GATEWAY_TOKEN env var + * 3. CLAWDBOT_GATEWAY_TOKEN env var + * 4. OpenClaw credentials store (/credentials/gateway_token) + * 5. null → caller returns 500 "Gateway not configured" + * * This lives in its own module so that the HTTP handler file contains zero * `process.env` references — plugin security scanners flag "env access + * network send" when both appear in the same source file. @@ -16,5 +25,20 @@ export function resolveGatewaySecret(api: OpenClawPluginApi): string | null { if (typeof secret === "string" && secret) { return secret; } + + // Fallback: read from OpenClaw credentials store. + // Avoids requiring users to duplicate the gateway token in launchd env or + // shell profile — the credentials store is the canonical location on local installs. + try { + const stateDir = api.runtime.state.resolveStateDir(); + const credPath = join(stateDir, "credentials", "gateway_token"); + const token = readFileSync(credPath, "utf-8").trim(); + if (token) { + return token; + } + } catch { + // File not found or unreadable — fall through + } + return null; }