diff --git a/src/sandboxes/setupOpenClaw.ts b/src/sandboxes/setupOpenClaw.ts index 7f7c480..2fbfb27 100644 --- a/src/sandboxes/setupOpenClaw.ts +++ b/src/sandboxes/setupOpenClaw.ts @@ -3,6 +3,30 @@ import { logger } from "@trigger.dev/sdk/v3"; import { onboardOpenClaw } from "./onboardOpenClaw"; import { OPENCLAW_DEFAULT_MODEL } from "../consts"; +// Constant script body. No interpolation of caller-supplied values — all +// secrets are read at runtime from process.env inside the sandbox, so a +// value containing quotes, backslashes, or newlines cannot break out of +// the shell or the JS string. See: shell-injection fix for accountId. +const INJECT_ENV_SCRIPT = ` + const fs = require('fs'); + const os = require('os'); + const path = os.homedir() + '/.openclaw/openclaw.json'; + const c = JSON.parse(fs.readFileSync(path, 'utf8')); + c.env = c.env || {}; + c.env.RECOUP_API_KEY = process.env.RECOUP_API_KEY; + c.env.RECOUP_ACCOUNT_ID = process.env.RECOUP_ACCOUNT_ID; + if (process.env.GITHUB_TOKEN) { + c.env.GITHUB_TOKEN = process.env.GITHUB_TOKEN; + } + c.tools = c.tools || {}; + c.tools.profile = 'coding'; + c.agents = c.agents || {}; + c.agents.defaults = c.agents.defaults || {}; + c.agents.defaults.model = c.agents.defaults.model || {}; + c.agents.defaults.model.primary = process.env.OPENCLAW_DEFAULT_MODEL; + fs.writeFileSync(path, JSON.stringify(c, null, 2)); +`; + /** * Ensures OpenClaw is onboarded, seeds env vars into the config, * and starts the gateway process in the background. @@ -17,42 +41,31 @@ export async function setupOpenClaw( ): Promise { await onboardOpenClaw(sandbox); - // Inject RECOUP env vars into openclaw.json's env block so they're - // available to the agent and all tools/subprocesses it spawns. - if (!process.env.RECOUP_API_KEY) { + const recoupApiKey = process.env.RECOUP_API_KEY; + if (!recoupApiKey) { throw new Error("Missing RECOUP_API_KEY environment variable"); } const githubToken = process.env.GITHUB_TOKEN; logger.log("Injecting env vars into openclaw.json", { - RECOUP_API_KEY: `${process.env.RECOUP_API_KEY.slice(0, 4)}...`, + RECOUP_API_KEY: `${recoupApiKey.slice(0, 4)}...`, RECOUP_ACCOUNT_ID: accountId, GITHUB_TOKEN: githubToken ? "present" : "missing", }); - const injectEnv = await sandbox.runCommand({ - cmd: "sh", - args: [ - "-c", - `node -e " - const fs = require('fs'); - const p = require('os').homedir() + '/.openclaw/openclaw.json'; - const c = JSON.parse(fs.readFileSync(p, 'utf8')); - c.env = c.env || {}; - c.env.RECOUP_API_KEY = '${process.env.RECOUP_API_KEY}'; - c.env.RECOUP_ACCOUNT_ID = '${accountId}'; - ${githubToken ? `c.env.GITHUB_TOKEN = '${githubToken}';` : ""} - c.tools = c.tools || {}; - c.tools.profile = 'coding'; - c.agents = c.agents || {}; - c.agents.defaults = c.agents.defaults || {}; - c.agents.defaults.model = c.agents.defaults.model || {}; - c.agents.defaults.model.primary = '${OPENCLAW_DEFAULT_MODEL}'; - fs.writeFileSync(p, JSON.stringify(c, null, 2)); - "`, - ], - }); + const injectEnvOpts: Record = { + cmd: "node", + args: ["-e", INJECT_ENV_SCRIPT], + env: { + RECOUP_API_KEY: recoupApiKey, + RECOUP_ACCOUNT_ID: accountId, + OPENCLAW_DEFAULT_MODEL, + ...(githubToken ? { GITHUB_TOKEN: githubToken } : {}), + }, + }; + + const injectEnv = await sandbox.runCommand(injectEnvOpts as any); if (injectEnv.exitCode !== 0) { const stderr = (await injectEnv.stderr()) || ""; @@ -82,4 +95,4 @@ export async function setupOpenClaw( } logger.log("OpenClaw gateway started"); -} +} \ No newline at end of file