diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ec9cba45a..9f9a09ff23d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Docs: https://docs.openclaw.ai - Approvals/runtime: move native approval lifecycle assembly into shared core bootstrap/runtime seams driven by channel capabilities and runtime contexts, and remove the legacy bundled approval fallback wiring. (#62135) Thanks @gumadeiras. - Security/fetch-guard: stop rejecting operator-configured proxy hostnames against the target-scoped hostname allowlist in SSRF-guarded fetches, restoring proxy-based media downloads for Telegram and other channels. (#62312) Thanks @ademczuk. - iOS/gateway: replace string-matched connection error UI with structured gateway connection problems, preserve actionable pairing/auth failures over later generic disconnect noise, and surface reusable problem banners and details across onboarding, settings, and root status surfaces. (#62650) Thanks @ngutman. +- Git/env sanitization: block additional Git repository-plumbing env variables such as `GIT_DIR`, `GIT_WORK_TREE`, `GIT_COMMON_DIR`, `GIT_INDEX_FILE`, `GIT_OBJECT_DIRECTORY`, `GIT_ALTERNATE_OBJECT_DIRECTORIES`, and `GIT_NAMESPACE` so host-run Git commands cannot be redirected to attacker-chosen repository state through inherited or request-scoped env. (#62002) Thanks @eleqtrizit. ## 2026.4.5 diff --git a/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift b/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift index c423a62d255..cddda7bce40 100644 --- a/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift +++ b/apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift @@ -19,7 +19,14 @@ enum HostEnvSecurityPolicy { "BROWSER", "GIT_EDITOR", "GIT_EXTERNAL_DIFF", + "GIT_DIR", + "GIT_WORK_TREE", + "GIT_COMMON_DIR", "GIT_EXEC_PATH", + "GIT_INDEX_FILE", + "GIT_OBJECT_DIRECTORY", + "GIT_ALTERNATE_OBJECT_DIRECTORIES", + "GIT_NAMESPACE", "GIT_SEQUENCE_EDITOR", "GIT_TEMPLATE_DIR", "GIT_SSL_NO_VERIFY", @@ -59,6 +66,13 @@ enum HostEnvSecurityPolicy { "HOME", "GRADLE_USER_HOME", "ZDOTDIR", + "GIT_DIR", + "GIT_WORK_TREE", + "GIT_COMMON_DIR", + "GIT_INDEX_FILE", + "GIT_OBJECT_DIRECTORY", + "GIT_ALTERNATE_OBJECT_DIRECTORIES", + "GIT_NAMESPACE", "GIT_SSH_COMMAND", "GIT_SSH", "GIT_PROXY_COMMAND", diff --git a/src/infra/host-env-security-policy.json b/src/infra/host-env-security-policy.json index 949a9faef9a..acb654e8b51 100644 --- a/src/infra/host-env-security-policy.json +++ b/src/infra/host-env-security-policy.json @@ -13,7 +13,14 @@ "BROWSER", "GIT_EDITOR", "GIT_EXTERNAL_DIFF", + "GIT_DIR", + "GIT_WORK_TREE", + "GIT_COMMON_DIR", "GIT_EXEC_PATH", + "GIT_INDEX_FILE", + "GIT_OBJECT_DIRECTORY", + "GIT_ALTERNATE_OBJECT_DIRECTORIES", + "GIT_NAMESPACE", "GIT_SEQUENCE_EDITOR", "GIT_TEMPLATE_DIR", "GIT_SSL_NO_VERIFY", @@ -52,6 +59,13 @@ "HOME", "GRADLE_USER_HOME", "ZDOTDIR", + "GIT_DIR", + "GIT_WORK_TREE", + "GIT_COMMON_DIR", + "GIT_INDEX_FILE", + "GIT_OBJECT_DIRECTORY", + "GIT_ALTERNATE_OBJECT_DIRECTORIES", + "GIT_NAMESPACE", "GIT_SSH_COMMAND", "GIT_SSH", "GIT_PROXY_COMMAND", diff --git a/src/infra/host-env-security.test.ts b/src/infra/host-env-security.test.ts index e1e7c2f13ec..f235eb36f4b 100644 --- a/src/infra/host-env-security.test.ts +++ b/src/infra/host-env-security.test.ts @@ -134,7 +134,14 @@ describe("isDangerousHostEnvVarName", () => { expect(isDangerousHostEnvVarName("GIT_EDITOR")).toBe(true); expect(isDangerousHostEnvVarName("git_editor")).toBe(true); expect(isDangerousHostEnvVarName("GIT_EXTERNAL_DIFF")).toBe(true); + expect(isDangerousHostEnvVarName("GIT_DIR")).toBe(true); + expect(isDangerousHostEnvVarName("git_work_tree")).toBe(true); + expect(isDangerousHostEnvVarName("GIT_COMMON_DIR")).toBe(true); expect(isDangerousHostEnvVarName("git_exec_path")).toBe(true); + expect(isDangerousHostEnvVarName("GIT_INDEX_FILE")).toBe(true); + expect(isDangerousHostEnvVarName("git_object_directory")).toBe(true); + expect(isDangerousHostEnvVarName("git_alternate_object_directories")).toBe(true); + expect(isDangerousHostEnvVarName("GIT_NAMESPACE")).toBe(true); expect(isDangerousHostEnvVarName("GIT_SEQUENCE_EDITOR")).toBe(true); expect(isDangerousHostEnvVarName("git_sequence_editor")).toBe(true); expect(isDangerousHostEnvVarName("GIT_TEMPLATE_DIR")).toBe(true); @@ -220,7 +227,14 @@ describe("sanitizeHostExecEnv", () => { BROWSER: "/tmp/pwn-browser", GIT_EDITOR: "/tmp/pwn-editor", GIT_EXTERNAL_DIFF: "/tmp/pwn.sh", + GIT_DIR: "/tmp/evil-git-dir", + GIT_WORK_TREE: "/tmp/evil-work-tree", + GIT_COMMON_DIR: "/tmp/evil-common-dir", GIT_TEMPLATE_DIR: "/tmp/git-template", + GIT_INDEX_FILE: "/tmp/evil-git-index", + GIT_OBJECT_DIRECTORY: "/tmp/evil-git-objects", + GIT_ALTERNATE_OBJECT_DIRECTORIES: "/tmp/evil-git-alt-objects", + GIT_NAMESPACE: "evil-namespace", GIT_SEQUENCE_EDITOR: "/tmp/pwn-sequence-editor", HGRCPATH: "/tmp/evil-hgrc", CARGO_BUILD_RUSTC_WRAPPER: "/tmp/evil-rustc-wrapper", @@ -267,7 +281,14 @@ describe("sanitizeHostExecEnv", () => { HGRCPATH: "/tmp/evil-hgrc", GIT_SSH_COMMAND: "touch /tmp/pwned", GIT_EDITOR: "/tmp/git-editor", + GIT_DIR: "/tmp/evil-git-dir", + GIT_WORK_TREE: "/tmp/evil-work-tree", + GIT_COMMON_DIR: "/tmp/evil-common-dir", GIT_EXEC_PATH: "/tmp/git-exec-path", + GIT_INDEX_FILE: "/tmp/evil-git-index", + GIT_OBJECT_DIRECTORY: "/tmp/evil-git-objects", + GIT_ALTERNATE_OBJECT_DIRECTORIES: "/tmp/evil-git-alt-objects", + GIT_NAMESPACE: "evil-namespace", GIT_SEQUENCE_EDITOR: "/tmp/git-sequence-editor", EDITOR: "/tmp/editor", NPM_CONFIG_USERCONFIG: "/tmp/npmrc", @@ -332,6 +353,9 @@ describe("sanitizeHostExecEnv", () => { expect(env.BASH_ENV).toBeUndefined(); expect(env.BROWSER).toBeUndefined(); expect(env.GIT_EDITOR).toBeUndefined(); + expect(env.GIT_DIR).toBeUndefined(); + expect(env.GIT_WORK_TREE).toBeUndefined(); + expect(env.GIT_COMMON_DIR).toBeUndefined(); expect(env.CC).toBeUndefined(); expect(env.CXX).toBeUndefined(); expect(env.CARGO_BUILD_RUSTC).toBeUndefined(); @@ -341,6 +365,10 @@ describe("sanitizeHostExecEnv", () => { expect(env.RUSTC_WRAPPER).toBeUndefined(); expect(env.HGRCPATH).toBeUndefined(); expect(env.GIT_TEMPLATE_DIR).toBeUndefined(); + expect(env.GIT_INDEX_FILE).toBeUndefined(); + expect(env.GIT_OBJECT_DIRECTORY).toBeUndefined(); + expect(env.GIT_ALTERNATE_OBJECT_DIRECTORIES).toBeUndefined(); + expect(env.GIT_NAMESPACE).toBeUndefined(); expect(env.GIT_SEQUENCE_EDITOR).toBeUndefined(); expect(env.AWS_CONFIG_FILE).toBeUndefined(); expect(env.GIT_SSH_COMMAND).toBeUndefined(); @@ -523,6 +551,13 @@ describe("isDangerousHostEnvOverrideVarName", () => { it("matches override-only blocked keys case-insensitively", () => { expect(isDangerousHostEnvOverrideVarName("HOME")).toBe(true); expect(isDangerousHostEnvOverrideVarName("zdotdir")).toBe(true); + expect(isDangerousHostEnvOverrideVarName("GIT_DIR")).toBe(true); + expect(isDangerousHostEnvOverrideVarName("git_work_tree")).toBe(true); + expect(isDangerousHostEnvOverrideVarName("GIT_COMMON_DIR")).toBe(true); + expect(isDangerousHostEnvOverrideVarName("git_index_file")).toBe(true); + expect(isDangerousHostEnvOverrideVarName("GIT_OBJECT_DIRECTORY")).toBe(true); + expect(isDangerousHostEnvOverrideVarName("git_alternate_object_directories")).toBe(true); + expect(isDangerousHostEnvOverrideVarName("git_namespace")).toBe(true); expect(isDangerousHostEnvOverrideVarName("GIT_SSH_COMMAND")).toBe(true); expect(isDangerousHostEnvOverrideVarName("editor")).toBe(true); expect(isDangerousHostEnvOverrideVarName("NPM_CONFIG_USERCONFIG")).toBe(true); @@ -624,6 +659,13 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => { SSL_CERT_DIR: "/tmp/evil-cert-dir", REQUESTS_CA_BUNDLE: "/tmp/evil-requests-ca.pem", CURL_CA_BUNDLE: "/tmp/evil-curl-ca.pem", + GIT_DIR: "/tmp/evil-git-dir", + GIT_WORK_TREE: "/tmp/evil-work-tree", + GIT_COMMON_DIR: "/tmp/evil-common-dir", + GIT_INDEX_FILE: "/tmp/evil-git-index", + GIT_OBJECT_DIRECTORY: "/tmp/evil-git-objects", + GIT_ALTERNATE_OBJECT_DIRECTORIES: "/tmp/evil-git-alt-objects", + GIT_NAMESPACE: "evil-namespace", GOPROXY: "https://example.invalid/proxy", GONOSUMCHECK: "example.invalid/*", GONOSUMDB: "example.invalid/*", @@ -666,9 +708,16 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => { "DOCKER_CONTEXT", "DOCKER_HOST", "DOCKER_TLS_VERIFY", + "GIT_ALTERNATE_OBJECT_DIRECTORIES", + "GIT_COMMON_DIR", + "GIT_DIR", + "GIT_INDEX_FILE", + "GIT_NAMESPACE", + "GIT_OBJECT_DIRECTORY", "GIT_SSL_CAINFO", "GIT_SSL_CAPATH", "GIT_SSL_NO_VERIFY", + "GIT_WORK_TREE", "GOENV", "GONOPROXY", "GONOSUMCHECK", @@ -742,6 +791,13 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => { expect(result.env.SSL_CERT_DIR).toBeUndefined(); expect(result.env.REQUESTS_CA_BUNDLE).toBeUndefined(); expect(result.env.CURL_CA_BUNDLE).toBeUndefined(); + expect(result.env.GIT_DIR).toBeUndefined(); + expect(result.env.GIT_WORK_TREE).toBeUndefined(); + expect(result.env.GIT_COMMON_DIR).toBeUndefined(); + expect(result.env.GIT_INDEX_FILE).toBeUndefined(); + expect(result.env.GIT_ALTERNATE_OBJECT_DIRECTORIES).toBeUndefined(); + expect(result.env.GIT_OBJECT_DIRECTORY).toBeUndefined(); + expect(result.env.GIT_NAMESPACE).toBeUndefined(); expect(result.env.GOPROXY).toBeUndefined(); expect(result.env.GONOSUMCHECK).toBeUndefined(); expect(result.env.GONOSUMDB).toBeUndefined();