fix(security): use uploadConfigFile for config deployment, chmod 600 openclaw config (#3038)

Replace base64-into-shell interpolation with SCP-based uploadConfigFile()
for Claude Code settings.json and Cursor CLI config files. This eliminates
the attack surface of injecting encoded payloads into shell command strings.

Add chmod 600 on ~/.openclaw/openclaw.json after writing the Telegram bot
token to prevent other users on the VM from reading the token in plaintext.

Fixes #3033
Fixes #3034

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-03-26 16:15:03 -07:00 committed by GitHub
parent a7b1596b98
commit f685374567
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 14 additions and 29 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.27.0",
"version": "0.27.1",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -146,20 +146,13 @@ async function setupClaudeCodeConfig(runner: CloudRunner, apiKey: string): Promi
}
}`;
// Safety: base64 output only contains [A-Za-z0-9+/=] — never single quotes —
// so interpolating into a single-quoted shell string is safe.
const settingsB64 = Buffer.from(settingsJson).toString("base64");
if (!/^[A-Za-z0-9+/=]+$/.test(settingsB64)) {
throw new Error("Unexpected characters in base64 output");
}
// Upload settings via SCP — avoids base64 interpolation into shell commands.
await uploadConfigFile(runner, settingsJson, "$HOME/.claude/settings.json");
// Build ~/.claude.json on the remote using $HOME so the workspace trust
// entry uses the actual home directory path (e.g. /root, /home/user).
// This pre-accepts the "Quick safety check" trust dialog for the home dir.
const stateScript = [
"mkdir -p ~/.claude",
`printf '%s' '${settingsB64}' | base64 -d > ~/.claude/settings.json`,
"chmod 600 ~/.claude/settings.json",
'printf \'{"hasCompletedOnboarding":true,"bypassPermissionsModeAccepted":true,"projects":{"%s":{"hasTrustDialogAccepted":true}}}\\n\' "$HOME" > ~/.claude.json',
"chmod 600 ~/.claude.json",
"touch ~/.claude/CLAUDE.md",
@ -208,29 +201,19 @@ async function setupCursorConfig(runner: CloudRunner, _apiKey: string): Promise<
"",
].join("\n");
const configB64 = Buffer.from(configJson).toString("base64");
if (!/^[A-Za-z0-9+/=]+$/.test(configB64)) {
throw new Error("Unexpected characters in base64 output");
}
// Upload config files via SCP — avoids base64 interpolation into shell commands.
await uploadConfigFile(runner, configJson, "$HOME/.cursor/cli-config.json");
await uploadConfigFile(runner, spawnRule, "$HOME/.cursor/rules/spawn.mdc");
// Spawn rule should be world-readable (not sensitive)
await runner.runServer("chmod 644 ~/.cursor/rules/spawn.mdc");
const ruleB64 = Buffer.from(spawnRule).toString("base64");
if (!/^[A-Za-z0-9+/=]+$/.test(ruleB64)) {
throw new Error("Unexpected characters in base64 output");
}
const script = [
"mkdir -p ~/.cursor ~/.cursor/rules",
`printf '%s' '${configB64}' | base64 -d > ~/.cursor/cli-config.json`,
"chmod 600 ~/.cursor/cli-config.json",
// Inject spawn skill as a Cursor rule
`printf '%s' '${ruleB64}' | base64 -d > ~/.cursor/rules/spawn.mdc`,
"chmod 644 ~/.cursor/rules/spawn.mdc",
// Persist PATH so agent binary is available
// Persist PATH so agent binary is available
const pathScript = [
'grep -q ".cursor/bin" ~/.bashrc 2>/dev/null || printf \'\\nexport PATH="$HOME/.cursor/bin:$PATH"\\n\' >> ~/.bashrc',
'grep -q ".cursor/bin" ~/.zshrc 2>/dev/null || printf \'\\nexport PATH="$HOME/.cursor/bin:$PATH"\\n\' >> ~/.zshrc',
].join(" && ");
await runner.runServer(script);
await runner.runServer(pathScript);
logInfo("Cursor CLI configured");
}
@ -532,7 +515,9 @@ async function setupOpenclawConfig(
"openclaw config set channels.telegram.enabled true >/dev/null; " +
`openclaw config set channels.telegram.botToken ${shellQuote(telegramBotToken)} >/dev/null; ` +
"openclaw config set channels.telegram.dmPolicy pairing >/dev/null; " +
"openclaw config set channels.telegram.groupPolicy open >/dev/null",
"openclaw config set channels.telegram.groupPolicy open >/dev/null; " +
// Restrict config file permissions — it now contains the Telegram bot token
"chmod 600 ~/.openclaw/openclaw.json 2>/dev/null || true",
),
);
if (telegramResult.ok) {