From 0d4936b98f6ad1c1dbb3d213723e11ea8ea21f61 Mon Sep 17 00:00:00 2001 From: fengjun Date: Fri, 24 Apr 2026 18:44:17 +0800 Subject: [PATCH] fix: avoid base64 eval wrappers in bash snapshots --- .../coding-agent/src/utils/shell-snapshot.ts | 5 +-- .../coding-agent/test/bash-executor.test.ts | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/src/utils/shell-snapshot.ts b/packages/coding-agent/src/utils/shell-snapshot.ts index 0e4071bf5..851f67bbb 100644 --- a/packages/coding-agent/src/utils/shell-snapshot.ts +++ b/packages/coding-agent/src/utils/shell-snapshot.ts @@ -59,10 +59,9 @@ done echo "# Functions" >> "$SNAPSHOT_FILE" # Force autoload all functions first declare -f > /dev/null 2>&1 -# Get user function names - filter system/private ones, use base64 for special chars +# Get user function names - filter system/private ones declare -F 2>/dev/null | cut -d' ' -f3 | grep -vE '^(_|__)' | grep -vE '${commonToolsRegex}' | while read func; do - encoded_func=$(declare -f "$func" | base64) - echo "eval \\"\\$(echo '$encoded_func' | base64 -d)\\" > /dev/null 2>&1" >> "$SNAPSHOT_FILE" + declare -f "$func" >> "$SNAPSHOT_FILE" 2>/dev/null done `; diff --git a/packages/coding-agent/test/bash-executor.test.ts b/packages/coding-agent/test/bash-executor.test.ts index 892a0ee43..8d5b91800 100644 --- a/packages/coding-agent/test/bash-executor.test.ts +++ b/packages/coding-agent/test/bash-executor.test.ts @@ -309,6 +309,49 @@ describe("executeBash", () => { expect(result.output.trim()).toBe("from_snapshot"); }); + it("sources large bash functions without base64 eval wrappers", async () => { + if (process.platform === "win32") { + return; + } + const realBashPath = Bun.env.SHELL?.includes("bash") ? Bun.env.SHELL : "/bin/bash"; + if (!fs.existsSync(realBashPath)) { + return; + } + + const bashPath = path.join(tempDir, "test-bash"); + fs.symlinkSync(realBashPath, bashPath); + const largeBody = Array.from({ length: 200 }, (_, index) => ` echo "snapshot ${index}"`).join("\n"); + fs.writeFileSync(path.join(tempDir, ".bashrc"), `pi_snapshot_large_function ()\n{\n${largeBody}\n}\n`); + + vi.spyOn(os, "homedir").mockReturnValue(tempDir); + vi.spyOn(Settings.prototype, "getShellConfig").mockReturnValue({ + shell: bashPath, + args: ["-l", "-c"], + env: { + PATH: Bun.env.PATH ?? "", + HOME: tempDir, + }, + prefix: undefined, + }); + + const snapshotPath = await shellSnapshot.getOrCreateSnapshot(bashPath, { + PATH: Bun.env.PATH ?? "", + HOME: tempDir, + }); + expect(snapshotPath).not.toBeNull(); + const snapshot = fs.readFileSync(snapshotPath!, "utf8"); + expect(snapshot).toContain("pi_snapshot_large_function"); + expect(snapshot).not.toContain("base64 -d"); + + const result = await executeBash("printf 'snapshot_ok\\n'", { + cwd: tempDir, + timeout: 5000, + sessionKey: "large-function-snapshot", + }); + expect(result.cancelled).toBe(false); + expect(result.output.trim()).toBe("snapshot_ok"); + }); + it("does not allow exec to replace the host", async () => { const result = await executeBash("exec echo hi", { cwd: tempDir, timeout: 5000 }); expect(result.cancelled).toBe(false);