Skip to content

Commit 24fb806

Browse files
committed
fix: reject shell expansion in safe-bin tokens
1 parent efacb91 commit 24fb806

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

src/infra/exec-approvals-safe-bins.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,30 @@ describe("exec approvals safe bins", () => {
223223
expected: false,
224224
setup: (cwd) => fs.writeFileSync(path.join(cwd, "secret.json"), "{}"),
225225
},
226+
{
227+
name: "blocks POSIX parameter expansion in safe-bin value tokens",
228+
argv: ["head", "-c${IFS}16${IFS}${OPENCLAW_CONFIG_PATH}"],
229+
resolvedPath: "/usr/bin/head",
230+
expected: false,
231+
safeBins: ["head"],
232+
executableName: "head",
233+
},
234+
{
235+
name: "blocks POSIX parameter expansion in safe-bin long option values",
236+
argv: ["head", "--bytes=${IFS}16"],
237+
resolvedPath: "/usr/bin/head",
238+
expected: false,
239+
safeBins: ["head"],
240+
executableName: "head",
241+
},
242+
{
243+
name: "blocks POSIX parameter expansion in safe-bin positional tokens",
244+
argv: ["tr", "${IFS}", "_"],
245+
resolvedPath: "/usr/bin/tr",
246+
expected: false,
247+
safeBins: ["tr"],
248+
executableName: "tr",
249+
},
226250
{
227251
name: "blocks safe bins resolved from untrusted directories",
228252
argv: ["jq", ".foo"],

src/infra/exec-safe-bin-policy-validator.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@ function hasGlobToken(value: string): boolean {
2929
return /[*?[\]]/.test(value);
3030
}
3131

32+
function hasShellExpansionToken(value: string): boolean {
33+
return /\$(?:[A-Za-z0-9_@*?!$#-]|\{|\(|\[)/.test(value);
34+
}
35+
3236
const NO_FLAGS: ReadonlySet<string> = new Set();
3337

3438
function isSafeLiteralToken(value: string): boolean {
3539
if (!value || value === "-") {
3640
return true;
3741
}
38-
return !hasGlobToken(value) && !isPathLikeToken(value);
42+
return !hasGlobToken(value) && !hasShellExpansionToken(value) && !isPathLikeToken(value);
3943
}
4044

4145
function isInvalidValueToken(value: string | undefined): boolean {

src/node-host/invoke-system-run.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,21 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
750750
}
751751
});
752752

753+
it.runIf(process.platform !== "win32")(
754+
"denies safe-bin shell expansion carriers in allowlist mode",
755+
async () => {
756+
const { runCommand, sendInvokeResult } = await runSystemInvoke({
757+
preferMacAppExecHost: false,
758+
security: "allowlist",
759+
ask: "off",
760+
command: ["/bin/sh", "-lc", "head -c${IFS}16${IFS}${OPENCLAW_CONFIG_PATH}"],
761+
});
762+
763+
expect(runCommand).not.toHaveBeenCalled();
764+
expectInvokeErrorMessage(sendInvokeResult, { message: "allowlist miss" });
765+
},
766+
);
767+
753768
it("denies abbreviated PowerShell encoded payloads even when the wrapper is allowlisted", async () => {
754769
const binDir = createFixtureDir("openclaw-pwsh-allowlist-");
755770
const executablePath = createTempExecutable({ dir: binDir, name: "pwsh" });

0 commit comments

Comments
 (0)