mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-17 04:11:23 +00:00
fix(security): validate script templates before base64 encoding (#3132)
Add pre-encoding validation to reject ${} interpolation patterns in
script template strings before they are base64-encoded and injected
into systemd services running with root privileges on remote VMs.
Defense-in-depth against future regressions where template variable
interpolation before encoding could allow command injection.
Fixes #3130
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:
parent
9895d6e8cc
commit
3b61c22f25
3 changed files with 32 additions and 2 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.30.1",
|
||||
"version": "0.30.2",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"spawn": "cli.js"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,27 @@ export interface CloudRunner {
|
|||
downloadFile(remotePath: string, localPath: string): Promise<void>;
|
||||
}
|
||||
|
||||
// ─── Script template validation ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Validate that a script template string does not contain JS template
|
||||
* interpolation patterns (`${...}`) before it is base64-encoded for shell
|
||||
* injection into systemd units or remote commands.
|
||||
*
|
||||
* Defense-in-depth: the scripts are currently static string arrays joined
|
||||
* with `\n`, so they should never contain interpolation markers. This guard
|
||||
* catches future regressions where a developer might accidentally introduce
|
||||
* template literal interpolation before encoding.
|
||||
*
|
||||
* Note: backticks alone are allowed (used in markdown content for skill
|
||||
* files), but `${` is always rejected as it indicates JS interpolation.
|
||||
*/
|
||||
export function validateScriptTemplate(script: string, label: string): void {
|
||||
if (/\$\{/.test(script)) {
|
||||
throw new Error(`Script template "${label}" contains \${} interpolation — refusing to encode`);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Install helpers ────────────────────────────────────────────────────────
|
||||
|
||||
async function installAgent(
|
||||
|
|
@ -550,6 +571,9 @@ export async function startGateway(runner: CloudRunner): Promise<void> {
|
|||
"WantedBy=multi-user.target",
|
||||
].join("\n");
|
||||
|
||||
validateScriptTemplate(wrapperScript, "gateway-wrapper");
|
||||
validateScriptTemplate(unitFile, "gateway-unit");
|
||||
|
||||
const wrapperB64 = Buffer.from(wrapperScript).toString("base64");
|
||||
const unitB64 = Buffer.from(unitFile).toString("base64");
|
||||
if (!/^[A-Za-z0-9+/=]+$/.test(wrapperB64)) {
|
||||
|
|
@ -811,6 +835,10 @@ export async function setupAutoUpdate(runner: CloudRunner, agentName: string, up
|
|||
"WantedBy=timers.target",
|
||||
].join("\n");
|
||||
|
||||
validateScriptTemplate(wrapperScript, "auto-update-wrapper");
|
||||
validateScriptTemplate(unitFile, "auto-update-unit");
|
||||
validateScriptTemplate(timerFile, "auto-update-timer");
|
||||
|
||||
const wrapperB64 = Buffer.from(wrapperScript).toString("base64");
|
||||
const unitB64 = Buffer.from(unitFile).toString("base64");
|
||||
const timerB64 = Buffer.from(timerFile).toString("base64");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import type { CloudRunner } from "./agent-setup.js";
|
||||
|
||||
import { wrapSshCall } from "./agent-setup.js";
|
||||
import { validateScriptTemplate, wrapSshCall } from "./agent-setup.js";
|
||||
import { asyncTryCatchIf, isOperationalError } from "./result.js";
|
||||
import { logInfo, logWarn } from "./ui.js";
|
||||
|
||||
|
|
@ -158,6 +158,8 @@ export async function injectSpawnSkill(runner: CloudRunner, agentName: string):
|
|||
return;
|
||||
}
|
||||
|
||||
validateScriptTemplate(config.content, `spawn-skill-${agentName}`);
|
||||
|
||||
const b64 = Buffer.from(config.content).toString("base64");
|
||||
if (!/^[A-Za-z0-9+/=]+$/.test(b64)) {
|
||||
throw new Error("Unexpected characters in base64 output");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue