mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-29 05:25:43 +02:00
Infra: block additional host exec env keys (#55977)
This commit is contained in:
@@ -18,6 +18,7 @@ enum HostEnvSecurityPolicy {
|
||||
"ENV",
|
||||
"GIT_EXTERNAL_DIFF",
|
||||
"GIT_EXEC_PATH",
|
||||
"GIT_TEMPLATE_DIR",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
"PS4",
|
||||
@@ -34,7 +35,8 @@ enum HostEnvSecurityPolicy {
|
||||
"MAVEN_OPTS",
|
||||
"SBT_OPTS",
|
||||
"GRADLE_OPTS",
|
||||
"ANT_OPTS"
|
||||
"ANT_OPTS",
|
||||
"AWS_CONFIG_FILE"
|
||||
]
|
||||
|
||||
static let blockedOverrideKeys: Set<String> = [
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"ENV",
|
||||
"GIT_EXTERNAL_DIFF",
|
||||
"GIT_EXEC_PATH",
|
||||
"GIT_TEMPLATE_DIR",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
"PS4",
|
||||
@@ -28,7 +29,8 @@
|
||||
"MAVEN_OPTS",
|
||||
"SBT_OPTS",
|
||||
"GRADLE_OPTS",
|
||||
"ANT_OPTS"
|
||||
"ANT_OPTS",
|
||||
"AWS_CONFIG_FILE"
|
||||
],
|
||||
"blockedOverrideKeys": [
|
||||
"HOME",
|
||||
|
||||
@@ -37,6 +37,34 @@ async function runGitLsRemote(gitPath: string, target: string, env: NodeJS.Proce
|
||||
});
|
||||
}
|
||||
|
||||
async function runGitCommand(
|
||||
gitPath: string,
|
||||
args: string[],
|
||||
options?: {
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
},
|
||||
) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const child = spawn(gitPath, args, {
|
||||
cwd: options?.cwd,
|
||||
env: options?.env,
|
||||
stdio: "ignore",
|
||||
});
|
||||
child.once("error", () => resolve());
|
||||
child.once("close", () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
async function runGitClone(
|
||||
gitPath: string,
|
||||
source: string,
|
||||
destination: string,
|
||||
env: NodeJS.ProcessEnv,
|
||||
) {
|
||||
await runGitCommand(gitPath, ["clone", source, destination], { env });
|
||||
}
|
||||
|
||||
describe("isDangerousHostEnvVarName", () => {
|
||||
it("matches dangerous keys and prefixes case-insensitively", () => {
|
||||
expect(isDangerousHostEnvVarName("BASH_ENV")).toBe(true);
|
||||
@@ -44,6 +72,8 @@ describe("isDangerousHostEnvVarName", () => {
|
||||
expect(isDangerousHostEnvVarName("SHELL")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("GIT_EXTERNAL_DIFF")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("git_exec_path")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("GIT_TEMPLATE_DIR")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("git_template_dir")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("SHELLOPTS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("ps4")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("DYLD_INSERT_LIBRARIES")).toBe(true);
|
||||
@@ -71,6 +101,8 @@ describe("isDangerousHostEnvVarName", () => {
|
||||
expect(isDangerousHostEnvVarName("gradle_opts")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("ANT_OPTS")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("ant_opts")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("AWS_CONFIG_FILE")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("aws_config_file")).toBe(true);
|
||||
expect(isDangerousHostEnvVarName("PATH")).toBe(false);
|
||||
expect(isDangerousHostEnvVarName("FOO")).toBe(false);
|
||||
expect(isDangerousHostEnvVarName("GRADLE_USER_HOME")).toBe(false);
|
||||
@@ -84,6 +116,8 @@ describe("sanitizeHostExecEnv", () => {
|
||||
PATH: "/usr/bin:/bin",
|
||||
BASH_ENV: "/tmp/pwn.sh",
|
||||
GIT_EXTERNAL_DIFF: "/tmp/pwn.sh",
|
||||
GIT_TEMPLATE_DIR: "/tmp/git-template",
|
||||
AWS_CONFIG_FILE: "/tmp/aws-config",
|
||||
LD_PRELOAD: "/tmp/pwn.so",
|
||||
OK: "1",
|
||||
},
|
||||
@@ -126,6 +160,8 @@ describe("sanitizeHostExecEnv", () => {
|
||||
expect(env.PATH).toBe("/usr/bin:/bin");
|
||||
expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
|
||||
expect(env.BASH_ENV).toBeUndefined();
|
||||
expect(env.GIT_TEMPLATE_DIR).toBeUndefined();
|
||||
expect(env.AWS_CONFIG_FILE).toBeUndefined();
|
||||
expect(env.GIT_SSH_COMMAND).toBeUndefined();
|
||||
expect(env.GIT_EXEC_PATH).toBeUndefined();
|
||||
expect(env.EDITOR).toBeUndefined();
|
||||
@@ -426,6 +462,91 @@ describe("git env exploit regression", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("blocks inherited GIT_TEMPLATE_DIR so git clone cannot install hook payloads", async () => {
|
||||
const gitPath = getSystemGitPath();
|
||||
if (!gitPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const repoDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), `openclaw-git-template-source-${process.pid}-${Date.now()}-`),
|
||||
);
|
||||
const cloneDir = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-git-template-clone-${process.pid}-${Date.now()}`,
|
||||
);
|
||||
const safeCloneDir = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-git-template-safe-clone-${process.pid}-${Date.now()}`,
|
||||
);
|
||||
const templateDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), `openclaw-git-template-dir-${process.pid}-${Date.now()}-`),
|
||||
);
|
||||
const hooksDir = path.join(templateDir, "hooks");
|
||||
const marker = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-git-template-marker-${process.pid}-${Date.now()}`,
|
||||
);
|
||||
|
||||
try {
|
||||
fs.mkdirSync(hooksDir, { recursive: true });
|
||||
clearMarker(marker);
|
||||
fs.writeFileSync(
|
||||
path.join(hooksDir, "post-checkout"),
|
||||
`#!/bin/sh\ntouch ${JSON.stringify(marker)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
fs.chmodSync(path.join(hooksDir, "post-checkout"), 0o755);
|
||||
|
||||
await runGitCommand(gitPath, ["init", repoDir]);
|
||||
await runGitCommand(
|
||||
gitPath,
|
||||
[
|
||||
"-C",
|
||||
repoDir,
|
||||
"-c",
|
||||
"user.name=OpenClaw Test",
|
||||
"-c",
|
||||
"user.email=test@example.com",
|
||||
"commit",
|
||||
"--allow-empty",
|
||||
"-m",
|
||||
"init",
|
||||
],
|
||||
{
|
||||
env: {
|
||||
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const unsafeEnv = {
|
||||
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
||||
GIT_TEMPLATE_DIR: templateDir,
|
||||
GIT_TERMINAL_PROMPT: "0",
|
||||
};
|
||||
|
||||
await runGitClone(gitPath, repoDir, cloneDir, unsafeEnv);
|
||||
|
||||
expect(fs.existsSync(marker)).toBe(true);
|
||||
clearMarker(marker);
|
||||
|
||||
const safeEnv = sanitizeHostExecEnv({
|
||||
baseEnv: unsafeEnv,
|
||||
});
|
||||
|
||||
await runGitClone(gitPath, repoDir, safeCloneDir, safeEnv);
|
||||
|
||||
expect(fs.existsSync(marker)).toBe(false);
|
||||
} finally {
|
||||
fs.rmSync(repoDir, { recursive: true, force: true });
|
||||
fs.rmSync(cloneDir, { recursive: true, force: true });
|
||||
fs.rmSync(safeCloneDir, { recursive: true, force: true });
|
||||
fs.rmSync(templateDir, { recursive: true, force: true });
|
||||
fs.rmSync(marker, { force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("blocks GIT_SSH_COMMAND override so git cannot execute helper payloads", async () => {
|
||||
const gitPath = getSystemGitPath();
|
||||
if (!gitPath) {
|
||||
|
||||
Reference in New Issue
Block a user