fix: multiple dangerous build tool environment variab (#317) (#62079)

This commit is contained in:
Devin Robison
2026-04-06 13:10:38 -06:00
committed by GitHub
parent 43f84890ce
commit 7306cf3707
4 changed files with 122 additions and 2 deletions

View File

@@ -954,6 +954,7 @@ Docs: https://docs.openclaw.ai
- Security/path resolution: prefer non-user-writable absolute helper binaries for OpenClaw CLI, ffmpeg, and OpenSSL resolution so PATH hijacks cannot replace trusted helpers with attacker-controlled executables.
- Security/gateway command scopes: require `operator.admin` before Telegram target writeback and Talk Voice `/voice set` config writes persist through gateway message flows.
- Security/OpenShell mirror: exclude workspace `hooks/` from mirror sync so untrusted sandbox files cannot become trusted host hooks on gateway startup.
- Exec env policy: block Mercurial config redirects, Rust compiler wrappers, and GNU make flag env vars in host exec sanitization so inherited env and request-scoped overrides cannot redirect build-tool execution.
## 2026.3.24-beta.2

View File

@@ -28,6 +28,8 @@ enum HostEnvSecurityPolicy {
"CC",
"CXX",
"CARGO_BUILD_RUSTC",
"CARGO_BUILD_RUSTC_WRAPPER",
"RUSTC_WRAPPER",
"CMAKE_C_COMPILER",
"CMAKE_CXX_COMPILER",
"SHELL",
@@ -44,9 +46,12 @@ enum HostEnvSecurityPolicy {
"DOTNET_ADDITIONAL_DEPS",
"GLIBC_TUNABLES",
"MAVEN_OPTS",
"MAKEFLAGS",
"MFLAGS",
"SBT_OPTS",
"GRADLE_OPTS",
"ANT_OPTS"
"ANT_OPTS",
"HGRCPATH"
]
static let blockedOverrideKeys: Set<String> = [
@@ -83,6 +88,8 @@ enum HostEnvSecurityPolicy {
"CGO_CFLAGS",
"CGO_LDFLAGS",
"GOFLAGS",
"MAKEFLAGS",
"MFLAGS",
"CORECLR_PROFILER_PATH",
"PHPRC",
"PHP_INI_SCAN_DIR",
@@ -134,7 +141,9 @@ enum HostEnvSecurityPolicy {
"GOPRIVATE",
"GOENV",
"GOPATH",
"HGRCPATH",
"PYTHONUSERBASE",
"RUSTC_WRAPPER",
"VIRTUAL_ENV",
"LUA_PATH",
"LUA_CPATH",
@@ -142,6 +151,7 @@ enum HostEnvSecurityPolicy {
"GEM_PATH",
"BUNDLE_GEMFILE",
"COMPOSER_HOME",
"CARGO_BUILD_RUSTC_WRAPPER",
"XDG_CONFIG_HOME",
"AWS_CONFIG_FILE"
]

View File

@@ -22,6 +22,8 @@
"CC",
"CXX",
"CARGO_BUILD_RUSTC",
"CARGO_BUILD_RUSTC_WRAPPER",
"RUSTC_WRAPPER",
"CMAKE_C_COMPILER",
"CMAKE_CXX_COMPILER",
"SHELL",
@@ -38,9 +40,12 @@
"DOTNET_ADDITIONAL_DEPS",
"GLIBC_TUNABLES",
"MAVEN_OPTS",
"MAKEFLAGS",
"MFLAGS",
"SBT_OPTS",
"GRADLE_OPTS",
"ANT_OPTS"
"ANT_OPTS",
"HGRCPATH"
],
"blockedOverrideKeys": [
"HOME",
@@ -76,6 +81,8 @@
"CGO_CFLAGS",
"CGO_LDFLAGS",
"GOFLAGS",
"MAKEFLAGS",
"MFLAGS",
"CORECLR_PROFILER_PATH",
"PHPRC",
"PHP_INI_SCAN_DIR",
@@ -127,7 +134,9 @@
"GOPRIVATE",
"GOENV",
"GOPATH",
"HGRCPATH",
"PYTHONUSERBASE",
"RUSTC_WRAPPER",
"VIRTUAL_ENV",
"LUA_PATH",
"LUA_CPATH",
@@ -135,6 +144,7 @@
"GEM_PATH",
"BUNDLE_GEMFILE",
"COMPOSER_HOME",
"CARGO_BUILD_RUSTC_WRAPPER",
"XDG_CONFIG_HOME",
"AWS_CONFIG_FILE"
],

View File

@@ -143,10 +143,14 @@ describe("isDangerousHostEnvVarName", () => {
expect(isDangerousHostEnvVarName("cxx")).toBe(true);
expect(isDangerousHostEnvVarName("CARGO_BUILD_RUSTC")).toBe(true);
expect(isDangerousHostEnvVarName("cargo_build_rustc")).toBe(true);
expect(isDangerousHostEnvVarName("CARGO_BUILD_RUSTC_WRAPPER")).toBe(true);
expect(isDangerousHostEnvVarName("cargo_build_rustc_wrapper")).toBe(true);
expect(isDangerousHostEnvVarName("CMAKE_C_COMPILER")).toBe(true);
expect(isDangerousHostEnvVarName("cmake_c_compiler")).toBe(true);
expect(isDangerousHostEnvVarName("CMAKE_CXX_COMPILER")).toBe(true);
expect(isDangerousHostEnvVarName("cmake_cxx_compiler")).toBe(true);
expect(isDangerousHostEnvVarName("RUSTC_WRAPPER")).toBe(true);
expect(isDangerousHostEnvVarName("rustc_wrapper")).toBe(true);
expect(isDangerousHostEnvVarName("SHELLOPTS")).toBe(true);
expect(isDangerousHostEnvVarName("ps4")).toBe(true);
expect(isDangerousHostEnvVarName("DYLD_INSERT_LIBRARIES")).toBe(true);
@@ -168,12 +172,18 @@ describe("isDangerousHostEnvVarName", () => {
expect(isDangerousHostEnvVarName("glibc_tunables")).toBe(true);
expect(isDangerousHostEnvVarName("MAVEN_OPTS")).toBe(true);
expect(isDangerousHostEnvVarName("maven_opts")).toBe(true);
expect(isDangerousHostEnvVarName("MAKEFLAGS")).toBe(true);
expect(isDangerousHostEnvVarName("makeflags")).toBe(true);
expect(isDangerousHostEnvVarName("MFLAGS")).toBe(true);
expect(isDangerousHostEnvVarName("mflags")).toBe(true);
expect(isDangerousHostEnvVarName("SBT_OPTS")).toBe(true);
expect(isDangerousHostEnvVarName("sbt_opts")).toBe(true);
expect(isDangerousHostEnvVarName("GRADLE_OPTS")).toBe(true);
expect(isDangerousHostEnvVarName("gradle_opts")).toBe(true);
expect(isDangerousHostEnvVarName("ANT_OPTS")).toBe(true);
expect(isDangerousHostEnvVarName("ant_opts")).toBe(true);
expect(isDangerousHostEnvVarName("HGRCPATH")).toBe(true);
expect(isDangerousHostEnvVarName("hgrcpath")).toBe(true);
expect(isDangerousHostEnvVarName("HTTPS_PROXY")).toBe(false);
expect(isDangerousHostEnvVarName("https_proxy")).toBe(false);
expect(isDangerousHostEnvVarName("HTTP_PROXY")).toBe(false);
@@ -210,6 +220,11 @@ describe("sanitizeHostExecEnv", () => {
GIT_EXTERNAL_DIFF: "/tmp/pwn.sh",
GIT_TEMPLATE_DIR: "/tmp/git-template",
GIT_SEQUENCE_EDITOR: "/tmp/pwn-sequence-editor",
HGRCPATH: "/tmp/evil-hgrc",
CARGO_BUILD_RUSTC_WRAPPER: "/tmp/evil-rustc-wrapper",
RUSTC_WRAPPER: "/tmp/evil-rustc-wrapper",
MAKEFLAGS: "--eval=$(shell touch /tmp/pwned)",
MFLAGS: "--eval=$(shell touch /tmp/pwned-too)",
AWS_CONFIG_FILE: "/tmp/aws-config",
LD_PRELOAD: "/tmp/pwn.so",
OK: "1",
@@ -242,8 +257,11 @@ describe("sanitizeHostExecEnv", () => {
CC: "/tmp/evil-cc",
CXX: "/tmp/evil-cxx",
CARGO_BUILD_RUSTC: "/tmp/evil-rustc",
CARGO_BUILD_RUSTC_WRAPPER: "/tmp/evil-rustc-wrapper",
CMAKE_C_COMPILER: "/tmp/evil-c-compiler",
CMAKE_CXX_COMPILER: "/tmp/evil-cxx-compiler",
RUSTC_WRAPPER: "/tmp/evil-rustc-wrapper",
HGRCPATH: "/tmp/evil-hgrc",
GIT_SSH_COMMAND: "touch /tmp/pwned",
GIT_EDITOR: "/tmp/git-editor",
GIT_EXEC_PATH: "/tmp/git-exec-path",
@@ -295,6 +313,8 @@ describe("sanitizeHostExecEnv", () => {
PS4: "$(touch /tmp/pwned)",
CLASSPATH: "/tmp/evil-classpath",
GOFLAGS: "-mod=mod",
MAKEFLAGS: "--eval=$(shell touch /tmp/pwned)",
MFLAGS: "--eval=$(shell touch /tmp/pwned-too)",
PHPRC: "/tmp/evil-php.ini",
XDG_CONFIG_HOME: "/tmp/evil-config",
SAFE: "ok",
@@ -309,8 +329,11 @@ describe("sanitizeHostExecEnv", () => {
expect(env.CC).toBeUndefined();
expect(env.CXX).toBeUndefined();
expect(env.CARGO_BUILD_RUSTC).toBeUndefined();
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBeUndefined();
expect(env.CMAKE_C_COMPILER).toBeUndefined();
expect(env.CMAKE_CXX_COMPILER).toBeUndefined();
expect(env.RUSTC_WRAPPER).toBeUndefined();
expect(env.HGRCPATH).toBeUndefined();
expect(env.GIT_TEMPLATE_DIR).toBeUndefined();
expect(env.GIT_SEQUENCE_EDITOR).toBeUndefined();
expect(env.AWS_CONFIG_FILE).toBeUndefined();
@@ -324,6 +347,8 @@ describe("sanitizeHostExecEnv", () => {
expect(env.PS4).toBeUndefined();
expect(env.CLASSPATH).toBeUndefined();
expect(env.GOFLAGS).toBeUndefined();
expect(env.MAKEFLAGS).toBeUndefined();
expect(env.MFLAGS).toBeUndefined();
expect(env.PHPRC).toBeUndefined();
expect(env.XDG_CONFIG_HOME).toBeUndefined();
expect(env.YARN_RC_FILENAME).toBe(".trusted-yarnrc.yml");
@@ -524,8 +549,18 @@ describe("isDangerousHostEnvOverrideVarName", () => {
expect(isDangerousHostEnvOverrideVarName("virtual_env")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("CLASSPATH")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("classpath")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("MAKEFLAGS")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("makeflags")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("MFLAGS")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("mflags")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("GOFLAGS")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("goflags")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("HGRCPATH")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("hgrcpath")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("RUSTC_WRAPPER")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("rustc_wrapper")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("CARGO_BUILD_RUSTC_WRAPPER")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("cargo_build_rustc_wrapper")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("CORECLR_PROFILER_PATH")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("coreclr_profiler_path")).toBe(true);
expect(isDangerousHostEnvOverrideVarName("XDG_CONFIG_HOME")).toBe(true);
@@ -547,6 +582,7 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
overrides: {
PATH: "/tmp/evil",
CXX: "/tmp/evil-cxx",
CARGO_BUILD_RUSTC_WRAPPER: "/tmp/evil-rustc-wrapper",
CARGO_REGISTRIES_CRATES_IO_INDEX: "https://example.invalid/crates.io-index",
CMAKE_C_COMPILER: "/tmp/evil-c-compiler",
CLASSPATH: "/tmp/evil-classpath",
@@ -582,7 +618,11 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
GOPRIVATE: "example.invalid/*",
GOENV: "/tmp/evil-goenv",
GOPATH: "/tmp/evil-go",
HGRCPATH: "/tmp/evil-hgrc",
MAKEFLAGS: "--eval=$(shell touch /tmp/pwned)",
MFLAGS: "--eval=$(shell touch /tmp/pwned-too)",
PYTHONUSERBASE: "/tmp/evil-python-userbase",
RUSTC_WRAPPER: "/tmp/evil-rustc-wrapper",
VIRTUAL_ENV: "/tmp/evil-venv",
YARN_RC_FILENAME: ".evil-yarnrc.yml",
HTTPS_PROXY: "http://proxy.example.test:8080",
@@ -597,6 +637,7 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
expect(result.rejectedOverrideBlockedKeys).toEqual([
"C_INCLUDE_PATH",
"CARGO_BUILD_RUSTC_WRAPPER",
"CARGO_REGISTRIES_CRATES_IO_INDEX",
"CLASSPATH",
"CMAKE_C_COMPILER",
@@ -618,8 +659,11 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
"GOPATH",
"GOPRIVATE",
"GOPROXY",
"HGRCPATH",
"HTTPS_PROXY",
"LIBRARY_PATH",
"MAKEFLAGS",
"MFLAGS",
"NODE_EXTRA_CA_CERTS",
"NODE_TLS_REJECT_UNAUTHORIZED",
"OBJC_INCLUDE_PATH",
@@ -632,6 +676,7 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
"PIP_TRUSTED_HOST",
"PYTHONUSERBASE",
"REQUESTS_CA_BUNDLE",
"RUSTC_WRAPPER",
"SSL_CERT_DIR",
"SSL_CERT_FILE",
"UV_DEFAULT_INDEX",
@@ -648,6 +693,7 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
expect(result.env.CLASSPATH).toBeUndefined();
expect(result.env.CXX).toBeUndefined();
expect(result.env.CMAKE_C_COMPILER).toBeUndefined();
expect(result.env.CARGO_BUILD_RUSTC_WRAPPER).toBeUndefined();
expect(result.env.CARGO_REGISTRIES_CRATES_IO_INDEX).toBeUndefined();
expect(result.env.PIP_INDEX_URL).toBeUndefined();
expect(result.env.PIP_PYPI_URL).toBeUndefined();
@@ -684,9 +730,13 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
expect(result.env.GOPRIVATE).toBeUndefined();
expect(result.env.GOENV).toBeUndefined();
expect(result.env.GOPATH).toBeUndefined();
expect(result.env.HGRCPATH).toBeUndefined();
expect(result.env.HTTPS_PROXY).toBeUndefined();
expect(result.env.MAKEFLAGS).toBeUndefined();
expect(result.env.MFLAGS).toBeUndefined();
expect(result.env.NODE_TLS_REJECT_UNAUTHORIZED).toBeUndefined();
expect(result.env.PYTHONUSERBASE).toBeUndefined();
expect(result.env.RUSTC_WRAPPER).toBeUndefined();
expect(result.env.VIRTUAL_ENV).toBeUndefined();
expect(result.env.YARN_RC_FILENAME).toBeUndefined();
});
@@ -1100,3 +1150,52 @@ describe("compiler override exploit regression", () => {
}
});
});
describe("make env exploit regression", () => {
it("blocks MAKEFLAGS overrides so make cannot evaluate shell payloads from env", async () => {
const makePath = getSystemMakePath();
if (!makePath) {
return;
}
const tempDir = fs.mkdtempSync(
path.join(os.tmpdir(), `openclaw-makeflags-override-${process.pid}-${Date.now()}-`),
);
const exploitPath = path.join(tempDir, "evil-makeflags.sh");
const marker = path.join(os.tmpdir(), `openclaw-makeflags-marker-${process.pid}-${Date.now()}`);
try {
clearMarker(marker);
fs.writeFileSync(path.join(tempDir, "Makefile"), "all:\n\t@:\n", "utf8");
fs.writeFileSync(exploitPath, `#!/bin/sh\ntouch ${JSON.stringify(marker)}\n`, "utf8");
fs.chmodSync(exploitPath, 0o755);
const exploitValue = `--eval=$(shell ${exploitPath})`;
const baseEnv = {
PATH: process.env.PATH ?? "/usr/bin:/bin",
};
await runMakeCommand(makePath, tempDir, {
...baseEnv,
MAKEFLAGS: exploitValue,
});
expect(fs.existsSync(marker)).toBe(true);
clearMarker(marker);
const safeEnv = sanitizeHostExecEnv({
baseEnv,
overrides: {
MAKEFLAGS: exploitValue,
},
});
await runMakeCommand(makePath, tempDir, safeEnv);
expect(fs.existsSync(marker)).toBe(false);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
fs.rmSync(marker, { force: true });
}
});
});