-
Notifications
You must be signed in to change notification settings - Fork 4
fix(sandboxes): prevent shell injection in setupOpenClaw #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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<void> { | ||||||||||||||||||||||||
| 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", | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
Comment on lines
51
to
55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stop logging secret fragments and raw account IDs. This security fix still emits a stable prefix of Suggested fix logger.log("Injecting env vars into openclaw.json", {
- RECOUP_API_KEY: `${recoupApiKey.slice(0, 4)}...`,
- RECOUP_ACCOUNT_ID: accountId,
+ RECOUP_API_KEY: "present",
+ RECOUP_ACCOUNT_ID: "present",
GITHUB_TOKEN: githubToken ? "present" : "missing",
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| 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<string, unknown> = { | ||||||||||||||||||||||||
| 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"); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear
GITHUB_TOKENwhen it is absent.openclaw.jsonis persisted, so skipping this branch leaves any previousc.env.GITHUB_TOKENin place. A later run withoutGITHUB_TOKENwill keep using the old credential.Suggested fix
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; + } else { + delete c.env.GITHUB_TOKEN; }📝 Committable suggestion
🤖 Prompt for AI Agents