VYPR
High severity8.8NVD Advisory· Published May 6, 2026· Updated May 7, 2026

CVE-2026-43584

CVE-2026-43584

Description

OpenClaw before 2026.4.10 contains an insufficient environment variable denylist vulnerability in its exec environment policy that allows operator-supplied overrides of high-risk interpreter startup variables including VIMINIT, EXINIT, LUA_INIT, and HOSTALIASES. Attackers can exploit this by manipulating these environment variables to influence downstream execution behavior or network connectivity.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.4.102026.4.10

Affected products

2
  • OpenClaw/Openclawreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*range: <2026.4.10

Patches

1
2d126fc62343

fix(infra): expand host env security policy denylist [AI] (#63277)

https://github.com/openclaw/openclawPavan Kumar GondhiApr 10, 2026via ghsa
13 files changed · +1165 21
  • apps/macos/Sources/OpenClaw/HostEnvSanitizer.swift+8 1 modified
    @@ -8,6 +8,8 @@ struct HostEnvOverrideDiagnostics: Equatable {
     enum HostEnvSanitizer {
         /// Generated from src/infra/host-env-security-policy.json via scripts/generate-host-env-security-policy-swift.mjs.
         /// Parity is validated by src/infra/host-env-security.policy-parity.test.ts.
    +    private static let blockedInheritedKeys = HostEnvSecurityPolicy.blockedInheritedKeys
    +    private static let blockedInheritedPrefixes = HostEnvSecurityPolicy.blockedInheritedPrefixes
         private static let blockedKeys = HostEnvSecurityPolicy.blockedKeys
         private static let blockedPrefixes = HostEnvSecurityPolicy.blockedPrefixes
         private static let blockedOverrideKeys = HostEnvSecurityPolicy.blockedOverrideKeys
    @@ -28,6 +30,11 @@ enum HostEnvSanitizer {
             return self.blockedPrefixes.contains(where: { upperKey.hasPrefix($0) })
         }
     
    +    private static func isBlockedInherited(_ upperKey: String) -> Bool {
    +        if self.blockedInheritedKeys.contains(upperKey) { return true }
    +        return self.blockedInheritedPrefixes.contains(where: { upperKey.hasPrefix($0) })
    +    }
    +
         private static func isBlockedOverride(_ upperKey: String) -> Bool {
             if self.blockedOverrideKeys.contains(upperKey) { return true }
             return self.blockedOverridePrefixes.contains(where: { upperKey.hasPrefix($0) })
    @@ -113,7 +120,7 @@ enum HostEnvSanitizer {
                 let key = rawKey.trimmingCharacters(in: .whitespacesAndNewlines)
                 guard !key.isEmpty else { continue }
                 let upper = key.uppercased()
    -            if self.isBlocked(upper) { continue }
    +            if self.isBlockedInherited(upper) { continue }
                 merged[key] = value
             }
     
    
  • apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift+273 2 modified
    @@ -5,27 +5,240 @@
     import Foundation
     
     enum HostEnvSecurityPolicy {
    +    static let blockedInheritedKeys: Set<String> = [
    +        "_JAVA_OPTIONS",
    +        "AMQP_URL",
    +        "ANSIBLE_CALLBACK_PLUGINS",
    +        "ANSIBLE_COLLECTIONS_PATH",
    +        "ANSIBLE_CONFIG",
    +        "ANSIBLE_CONNECTION_PLUGINS",
    +        "ANSIBLE_FILTER_PLUGINS",
    +        "ANSIBLE_INVENTORY_PLUGINS",
    +        "ANSIBLE_LIBRARY",
    +        "ANSIBLE_LOOKUP_PLUGINS",
    +        "ANSIBLE_MODULE_UTILS",
    +        "ANSIBLE_REMOTE_TEMP",
    +        "ANSIBLE_ROLES_PATH",
    +        "ANSIBLE_STRATEGY_PLUGINS",
    +        "ANT_OPTS",
    +        "AWS_ACCESS_KEY_ID",
    +        "AWS_CONTAINER_CREDENTIALS_FULL_URI",
    +        "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
    +        "AWS_SECRET_ACCESS_KEY",
    +        "AWS_SECURITY_TOKEN",
    +        "AWS_SESSION_TOKEN",
    +        "AZURE_CLIENT_ID",
    +        "AZURE_CLIENT_SECRET",
    +        "BASH_ENV",
    +        "BROWSER",
    +        "BUN_CONFIG_REGISTRY",
    +        "BUNDLE_GEMFILE",
    +        "BZR_EDITOR",
    +        "BZR_PLUGIN_PATH",
    +        "BZR_SSH",
    +        "C_INCLUDE_PATH",
    +        "CARGO_BUILD_RUSTC",
    +        "CARGO_BUILD_RUSTC_WRAPPER",
    +        "CARGO_HOME",
    +        "CATALINA_OPTS",
    +        "CC",
    +        "CFLAGS",
    +        "CGO_CFLAGS",
    +        "CGO_LDFLAGS",
    +        "CLASSPATH",
    +        "CMAKE_C_COMPILER",
    +        "CMAKE_CXX_COMPILER",
    +        "CMAKE_TOOLCHAIN_FILE",
    +        "COMPOSER_HOME",
    +        "CONFIG_SHELL",
    +        "CONFIG_SITE",
    +        "CORECLR_PROFILER",
    +        "CORECLR_PROFILER_PATH",
    +        "CPATH",
    +        "CPLUS_INCLUDE_PATH",
    +        "CURL_HOME",
    +        "CXX",
    +        "DATABASE_URL",
    +        "DENO_DIR",
    +        "DOTNET_ADDITIONAL_DEPS",
    +        "DOTNET_STARTUP_HOOKS",
    +        "EDITOR",
    +        "ELIXIR_ERL_OPTIONS",
    +        "EMACSLOADPATH",
    +        "ENV",
    +        "ERL_AFLAGS",
    +        "ERL_FLAGS",
    +        "ERL_ZFLAGS",
    +        "EXINIT",
    +        "FCEDIT",
    +        "GCONV_PATH",
    +        "GEM_HOME",
    +        "GEM_PATH",
    +        "GH_TOKEN",
    +        "GIT_ALTERNATE_OBJECT_DIRECTORIES",
    +        "GIT_ASKPASS",
    +        "GIT_COMMON_DIR",
    +        "GIT_DIR",
    +        "GIT_EDITOR",
    +        "GIT_EXEC_PATH",
    +        "GIT_EXTERNAL_DIFF",
    +        "GIT_HOOK_PATH",
    +        "GIT_INDEX_FILE",
    +        "GIT_NAMESPACE",
    +        "GIT_OBJECT_DIRECTORY",
    +        "GIT_PROXY_COMMAND",
    +        "GIT_SEQUENCE_EDITOR",
    +        "GIT_SSH",
    +        "GIT_SSH_COMMAND",
    +        "GIT_SSL_CAINFO",
    +        "GIT_SSL_CAPATH",
    +        "GIT_SSL_NO_VERIFY",
    +        "GIT_TEMPLATE_DIR",
    +        "GIT_WORK_TREE",
    +        "GITHUB_TOKEN",
    +        "GITLAB_TOKEN",
    +        "GLIBC_TUNABLES",
    +        "GOENV",
    +        "GOFLAGS",
    +        "GONOPROXY",
    +        "GONOSUMCHECK",
    +        "GONOSUMDB",
    +        "GOPATH",
    +        "GOPRIVATE",
    +        "GOPROXY",
    +        "GRADLE_OPTS",
    +        "GVIMINIT",
    +        "HELM_HOME",
    +        "HELM_PLUGINS",
    +        "HGRCPATH",
    +        "HOSTALIASES",
    +        "IFS",
    +        "JAVA_OPTS",
    +        "JAVA_TOOL_OPTIONS",
    +        "JDK_JAVA_OPTIONS",
    +        "JULIA_EDITOR",
    +        "LDFLAGS",
    +        "LESSCLOSE",
    +        "LESSOPEN",
    +        "LIBRARY_PATH",
    +        "LUA_CPATH",
    +        "LUA_INIT",
    +        "LUA_INIT_5_1",
    +        "LUA_INIT_5_2",
    +        "LUA_INIT_5_3",
    +        "LUA_INIT_5_4",
    +        "LUA_PATH",
    +        "MAKEFLAGS",
    +        "MAVEN_OPTS",
    +        "MFLAGS",
    +        "MONGODB_URI",
    +        "MYVIMRC",
    +        "NODE_AUTH_TOKEN",
    +        "NODE_OPTIONS",
    +        "NODE_PATH",
    +        "NPM_TOKEN",
    +        "OBJC_INCLUDE_PATH",
    +        "OPENSSL_CONF",
    +        "OPENSSL_ENGINES",
    +        "PACKER_PLUGIN_PATH",
    +        "PERL5DB",
    +        "PERL5DBCMD",
    +        "PERL5LIB",
    +        "PERL5OPT",
    +        "PHP_INI_SCAN_DIR",
    +        "PHPRC",
    +        "PIP_CONFIG_FILE",
    +        "PIP_EXTRA_INDEX_URL",
    +        "PIP_FIND_LINKS",
    +        "PIP_INDEX_URL",
    +        "PIP_PYPI_URL",
    +        "PIP_TRUSTED_HOST",
    +        "PROMPT_COMMAND",
    +        "PS4",
    +        "PYTHONBREAKPOINT",
    +        "PYTHONHOME",
    +        "PYTHONPATH",
    +        "PYTHONSTARTUP",
    +        "PYTHONUSERBASE",
    +        "R_ENVIRON",
    +        "R_ENVIRON_USER",
    +        "R_LIBS_USER",
    +        "R_PROFILE",
    +        "R_PROFILE_USER",
    +        "REDIS_URL",
    +        "RUBYLIB",
    +        "RUBYOPT",
    +        "RUBYSHELL",
    +        "RUSTC_WRAPPER",
    +        "RUSTFLAGS",
    +        "SBT_OPTS",
    +        "SHELL",
    +        "SHELLOPTS",
    +        "SSH_ASKPASS",
    +        "SSLKEYLOGFILE",
    +        "SUDO_ASKPASS",
    +        "SUDO_EDITOR",
    +        "SVN_EDITOR",
    +        "SVN_SSH",
    +        "TF_CLI_CONFIG_FILE",
    +        "TF_PLUGIN_CACHE_DIR",
    +        "UV_DEFAULT_INDEX",
    +        "UV_EXTRA_INDEX_URL",
    +        "UV_INDEX",
    +        "UV_INDEX_URL",
    +        "UV_PYTHON",
    +        "VAGRANT_VAGRANTFILE",
    +        "VIMINIT",
    +        "VIRTUAL_ENV",
    +        "VISUAL",
    +        "WGETRC",
    +        "XDG_CONFIG_DIRS",
    +        "XDG_CONFIG_HOME",
    +        "YARN_RC_FILENAME"
    +    ]
    +
    +    static let blockedInheritedPrefixes: [String] = [
    +        "BASH_FUNC_",
    +        "DYLD_",
    +        "LD_"
    +    ]
    +
         static let blockedKeys: Set<String> = [
             "_JAVA_OPTIONS",
             "ANT_OPTS",
             "BASH_ENV",
             "BROWSER",
    +        "BZR_EDITOR",
    +        "BZR_PLUGIN_PATH",
    +        "BZR_SSH",
             "CARGO_BUILD_RUSTC",
             "CARGO_BUILD_RUSTC_WRAPPER",
    +        "CATALINA_OPTS",
             "CC",
             "CMAKE_C_COMPILER",
             "CMAKE_CXX_COMPILER",
    +        "CMAKE_TOOLCHAIN_FILE",
    +        "CONFIG_SHELL",
    +        "CONFIG_SITE",
    +        "CORECLR_PROFILER",
             "CXX",
             "DOTNET_ADDITIONAL_DEPS",
             "DOTNET_STARTUP_HOOKS",
    +        "ELIXIR_ERL_OPTIONS",
    +        "EMACSLOADPATH",
             "ENV",
    +        "ERL_AFLAGS",
    +        "ERL_FLAGS",
    +        "ERL_ZFLAGS",
    +        "EXINIT",
             "GCONV_PATH",
             "GIT_ALTERNATE_OBJECT_DIRECTORIES",
             "GIT_COMMON_DIR",
             "GIT_DIR",
             "GIT_EDITOR",
             "GIT_EXEC_PATH",
             "GIT_EXTERNAL_DIFF",
    +        "GIT_HOOK_PATH",
             "GIT_INDEX_FILE",
             "GIT_NAMESPACE",
             "GIT_OBJECT_DIRECTORY",
    @@ -37,42 +250,85 @@ enum HostEnvSecurityPolicy {
             "GIT_WORK_TREE",
             "GLIBC_TUNABLES",
             "GRADLE_OPTS",
    +        "GVIMINIT",
    +        "HELM_PLUGINS",
             "HGRCPATH",
    +        "HOSTALIASES",
             "IFS",
             "JAVA_OPTS",
             "JAVA_TOOL_OPTIONS",
             "JDK_JAVA_OPTIONS",
    +        "JULIA_EDITOR",
    +        "LUA_INIT",
    +        "LUA_INIT_5_1",
    +        "LUA_INIT_5_2",
    +        "LUA_INIT_5_3",
    +        "LUA_INIT_5_4",
             "MAKEFLAGS",
             "MAVEN_OPTS",
             "MFLAGS",
    +        "MYVIMRC",
             "NODE_OPTIONS",
             "NODE_PATH",
    +        "PACKER_PLUGIN_PATH",
             "PERL5LIB",
             "PERL5OPT",
             "PS4",
             "PYTHONBREAKPOINT",
             "PYTHONHOME",
             "PYTHONPATH",
    +        "R_ENVIRON",
    +        "R_ENVIRON_USER",
    +        "R_PROFILE",
    +        "R_PROFILE_USER",
             "RUBYLIB",
             "RUBYOPT",
    +        "RUBYSHELL",
             "RUSTC_WRAPPER",
             "SBT_OPTS",
             "SHELL",
             "SHELLOPTS",
    -        "SSLKEYLOGFILE"
    +        "SSLKEYLOGFILE",
    +        "SUDO_ASKPASS",
    +        "SVN_EDITOR",
    +        "SVN_SSH",
    +        "VAGRANT_VAGRANTFILE",
    +        "VIMINIT"
         ]
     
         static let blockedOverrideKeys: Set<String> = [
             "ALL_PROXY",
    +        "AMQP_URL",
    +        "ANSIBLE_CALLBACK_PLUGINS",
    +        "ANSIBLE_COLLECTIONS_PATH",
    +        "ANSIBLE_CONFIG",
    +        "ANSIBLE_CONNECTION_PLUGINS",
    +        "ANSIBLE_FILTER_PLUGINS",
    +        "ANSIBLE_INVENTORY_PLUGINS",
    +        "ANSIBLE_LIBRARY",
    +        "ANSIBLE_LOOKUP_PLUGINS",
    +        "ANSIBLE_MODULE_UTILS",
    +        "ANSIBLE_REMOTE_TEMP",
    +        "ANSIBLE_ROLES_PATH",
    +        "ANSIBLE_STRATEGY_PLUGINS",
    +        "AWS_ACCESS_KEY_ID",
             "AWS_CONFIG_FILE",
    +        "AWS_CONTAINER_CREDENTIALS_FULL_URI",
    +        "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
    +        "AWS_SECRET_ACCESS_KEY",
    +        "AWS_SECURITY_TOKEN",
    +        "AWS_SESSION_TOKEN",
             "AWS_SHARED_CREDENTIALS_FILE",
             "AWS_WEB_IDENTITY_TOKEN_FILE",
             "AZURE_AUTH_LOCATION",
    +        "AZURE_CLIENT_ID",
    +        "AZURE_CLIENT_SECRET",
             "BUN_CONFIG_REGISTRY",
             "BUNDLE_GEMFILE",
             "C_INCLUDE_PATH",
             "CARGO_BUILD_RUSTC_WRAPPER",
             "CARGO_HOME",
    +        "CFLAGS",
             "CGO_CFLAGS",
             "CGO_LDFLAGS",
             "CLASSPATH",
    @@ -82,6 +338,7 @@ enum HostEnvSecurityPolicy {
             "CPLUS_INCLUDE_PATH",
             "CURL_CA_BUNDLE",
             "CURL_HOME",
    +        "DATABASE_URL",
             "DENO_DIR",
             "DOCKER_CERT_PATH",
             "DOCKER_CONTEXT",
    @@ -91,6 +348,7 @@ enum HostEnvSecurityPolicy {
             "FCEDIT",
             "GEM_HOME",
             "GEM_PATH",
    +        "GH_TOKEN",
             "GIT_ALTERNATE_OBJECT_DIRECTORIES",
             "GIT_ASKPASS",
             "GIT_COMMON_DIR",
    @@ -106,6 +364,8 @@ enum HostEnvSecurityPolicy {
             "GIT_SSL_CAPATH",
             "GIT_SSL_NO_VERIFY",
             "GIT_WORK_TREE",
    +        "GITHUB_TOKEN",
    +        "GITLAB_TOKEN",
             "GOENV",
             "GOFLAGS",
             "GONOPROXY",
    @@ -123,6 +383,7 @@ enum HostEnvSecurityPolicy {
             "HTTP_PROXY",
             "HTTPS_PROXY",
             "KUBECONFIG",
    +        "LDFLAGS",
             "LESSCLOSE",
             "LESSOPEN",
             "LIBRARY_PATH",
    @@ -131,9 +392,12 @@ enum HostEnvSecurityPolicy {
             "MAKEFLAGS",
             "MANPAGER",
             "MFLAGS",
    +        "MONGODB_URI",
             "NO_PROXY",
    +        "NODE_AUTH_TOKEN",
             "NODE_EXTRA_CA_CERTS",
             "NODE_TLS_REJECT_UNAUTHORIZED",
    +        "NPM_TOKEN",
             "OBJC_INCLUDE_PATH",
             "OPENSSL_CONF",
             "OPENSSL_ENGINES",
    @@ -151,13 +415,18 @@ enum HostEnvSecurityPolicy {
             "PROMPT_COMMAND",
             "PYTHONSTARTUP",
             "PYTHONUSERBASE",
    +        "R_LIBS_USER",
    +        "REDIS_URL",
             "REQUESTS_CA_BUNDLE",
             "RUSTC_WRAPPER",
             "RUSTFLAGS",
             "SSH_ASKPASS",
    +        "SSH_AUTH_SOCK",
             "SSL_CERT_DIR",
             "SSL_CERT_FILE",
             "SUDO_EDITOR",
    +        "TF_CLI_CONFIG_FILE",
    +        "TF_PLUGIN_CACHE_DIR",
             "UV_DEFAULT_INDEX",
             "UV_EXTRA_INDEX_URL",
             "UV_INDEX",
    @@ -166,6 +435,7 @@ enum HostEnvSecurityPolicy {
             "VIRTUAL_ENV",
             "VISUAL",
             "WGETRC",
    +        "XDG_CONFIG_DIRS",
             "XDG_CONFIG_HOME",
             "YARN_RC_FILENAME",
             "ZDOTDIR"
    @@ -174,7 +444,8 @@ enum HostEnvSecurityPolicy {
         static let blockedOverridePrefixes: [String] = [
             "CARGO_REGISTRIES_",
             "GIT_CONFIG_",
    -        "NPM_CONFIG_"
    +        "NPM_CONFIG_",
    +        "TF_VAR_"
         ]
     
         static let blockedPrefixes: [String] = [
    
  • CHANGELOG.md+1 0 modified
    @@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
     
     ### Fixes
     
    +- fix(infra): expand host env security policy denylist [AI]. (#63277) Thanks @pgondhi987.
     - fix(agents): guard nodes tool outPath against workspace boundary [AI-assisted]. (#63551) Thanks @pgondhi987.
     - fix(qqbot): enforce media storage boundary for all outbound local file paths [AI]. (#63271) Thanks @pgondhi987.
     - iMessage/self-chat: distinguish normal DM outbound rows from true self-chat using `destination_caller_id` plus chat participants, while preserving multi-handle self-chat aliases so outbound DM replies stop looping back as inbound messages. (#61619) Thanks @neeravmakwana.
    
  • scripts/generate-host-env-security-policy-swift.mjs+8 0 modified
    @@ -37,6 +37,14 @@ const generated = `// Generated file. Do not edit directly.
     import Foundation
     
     enum HostEnvSecurityPolicy {
    +    static let blockedInheritedKeys: Set<String> = [
    +${renderSwiftStringArray(policy.blockedInheritedKeys)}
    +    ]
    +
    +    static let blockedInheritedPrefixes: [String] = [
    +${renderSwiftStringArray(policy.blockedInheritedPrefixes)}
    +    ]
    +
         static let blockedKeys: Set<String> = [
     ${renderSwiftStringArray(policy.blockedKeys)}
         ]
    
  • src/agents/bash-tools.exec-runtime.ts+3 3 modified
    @@ -9,7 +9,7 @@ import {
       type ExecTarget,
     } from "../infra/exec-approvals.js";
     import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
    -import { isDangerousHostEnvVarName } from "../infra/host-env-security.js";
    +import { isDangerousHostInheritedEnvVarName } from "../infra/host-env-security.js";
     import { findPathKey, mergePathPrepend } from "../infra/path-prepend.js";
     import { enqueueSystemEvent } from "../infra/system-events.js";
     import { scopedHeartbeatWakeOptions } from "../routing/session-key.js";
    @@ -72,7 +72,7 @@ export function sanitizeHostBaseEnv(env: Record<string, string>): Record<string,
           sanitized[key] = value;
           continue;
         }
    -    if (isDangerousHostEnvVarName(upperKey)) {
    +    if (isDangerousHostInheritedEnvVarName(upperKey)) {
           continue;
         }
         sanitized[key] = value;
    @@ -86,7 +86,7 @@ export function validateHostEnv(env: Record<string, string>): void {
         const upperKey = key.toUpperCase();
     
         // 1. Block known dangerous variables (Fail Closed)
    -    if (isDangerousHostEnvVarName(upperKey)) {
    +    if (isDangerousHostInheritedEnvVarName(upperKey)) {
           throw new Error(
             `Security Violation: Environment variable '${key}' is forbidden during host execution.`,
           );
    
  • src/infra/host-env-security-policy.d.ts+3 0 modified
    @@ -1,6 +1,9 @@
     export type HostEnvSecurityPolicy = Readonly<{
       blockedEverywhereKeys: readonly string[];
       blockedOverrideOnlyKeys: readonly string[];
    +  allowedInheritedOverrideOnlyKeys: readonly string[];
    +  blockedInheritedKeys: readonly string[];
    +  blockedInheritedPrefixes: readonly string[];
       blockedPrefixes: readonly string[];
       blockedOverridePrefixes: readonly string[];
       blockedKeys: readonly string[];
    
  • src/infra/host-env-security-policy.js+21 2 modified
    @@ -11,12 +11,26 @@ function sortUniqueUppercase(values) {
     function derivePolicyArrays(policy) {
       const blockedEverywhereKeys = policy.blockedEverywhereKeys ?? [];
       const blockedOverrideOnlyKeys = policy.blockedOverrideOnlyKeys ?? [];
    +  const allowedInheritedOverrideOnlyKeys = policy.allowedInheritedOverrideOnlyKeys ?? [];
    +  const allowedInheritedOverrideOnlyUpper = new Set(
    +    allowedInheritedOverrideOnlyKeys.map((value) => value.toUpperCase()),
    +  );
    +  const blockedPrefixes = policy.blockedPrefixes ?? [];
    +  const blockedOverridePrefixes = policy.blockedOverridePrefixes ?? [];
    +  const blockedInheritedPrefixes = policy.blockedInheritedPrefixes ?? blockedPrefixes;
     
       return {
    +    blockedInheritedKeys: sortUniqueUppercase([
    +      ...blockedEverywhereKeys,
    +      ...blockedOverrideOnlyKeys.filter(
    +        (value) => !allowedInheritedOverrideOnlyUpper.has(value.toUpperCase()),
    +      ),
    +    ]),
    +    blockedInheritedPrefixes: sortUniqueUppercase(blockedInheritedPrefixes),
         blockedKeys: sortUniqueUppercase(blockedEverywhereKeys),
         blockedOverrideKeys: sortUniqueUppercase(blockedOverrideOnlyKeys),
    -    blockedPrefixes: sortUniqueUppercase(policy.blockedPrefixes ?? []),
    -    blockedOverridePrefixes: sortUniqueUppercase(policy.blockedOverridePrefixes ?? []),
    +    blockedPrefixes: sortUniqueUppercase(blockedPrefixes),
    +    blockedOverridePrefixes: sortUniqueUppercase(blockedOverridePrefixes),
       };
     }
     
    @@ -25,6 +39,11 @@ export function loadHostEnvSecurityPolicy(rawPolicy = HOST_ENV_SECURITY_POLICY_J
       return Object.freeze({
         blockedEverywhereKeys: Object.freeze(rawPolicy.blockedEverywhereKeys ?? []),
         blockedOverrideOnlyKeys: Object.freeze(rawPolicy.blockedOverrideOnlyKeys ?? []),
    +    allowedInheritedOverrideOnlyKeys: Object.freeze(
    +      rawPolicy.allowedInheritedOverrideOnlyKeys ?? [],
    +    ),
    +    blockedInheritedKeys: derived.blockedInheritedKeys,
    +    blockedInheritedPrefixes: derived.blockedInheritedPrefixes,
         blockedPrefixes: derived.blockedPrefixes,
         blockedOverridePrefixes: derived.blockedOverridePrefixes,
         blockedKeys: derived.blockedKeys,
    
  • src/infra/host-env-security-policy.json+105 9 modified
    @@ -53,7 +53,43 @@
         "SBT_OPTS",
         "GRADLE_OPTS",
         "ANT_OPTS",
    -    "HGRCPATH"
    +    "HGRCPATH",
    +    "EXINIT",
    +    "VIMINIT",
    +    "MYVIMRC",
    +    "GVIMINIT",
    +    "LUA_INIT",
    +    "LUA_INIT_5_1",
    +    "LUA_INIT_5_2",
    +    "LUA_INIT_5_3",
    +    "LUA_INIT_5_4",
    +    "EMACSLOADPATH",
    +    "RUBYSHELL",
    +    "GIT_HOOK_PATH",
    +    "SVN_EDITOR",
    +    "SVN_SSH",
    +    "BZR_EDITOR",
    +    "BZR_SSH",
    +    "BZR_PLUGIN_PATH",
    +    "SUDO_ASKPASS",
    +    "JULIA_EDITOR",
    +    "CONFIG_SITE",
    +    "CONFIG_SHELL",
    +    "CMAKE_TOOLCHAIN_FILE",
    +    "CATALINA_OPTS",
    +    "CORECLR_PROFILER",
    +    "HELM_PLUGINS",
    +    "PACKER_PLUGIN_PATH",
    +    "VAGRANT_VAGRANTFILE",
    +    "ERL_AFLAGS",
    +    "ERL_FLAGS",
    +    "ERL_ZFLAGS",
    +    "ELIXIR_ERL_OPTIONS",
    +    "R_ENVIRON",
    +    "R_PROFILE",
    +    "R_ENVIRON_USER",
    +    "R_PROFILE_USER",
    +    "HOSTALIASES"
       ],
       "blockedOverrideOnlyKeys": [
         "HOME",
    @@ -93,6 +129,7 @@
         "WGETRC",
         "CURL_HOME",
         "CLASSPATH",
    +    "CFLAGS",
         "CGO_CFLAGS",
         "CGO_LDFLAGS",
         "GOFLAGS",
    @@ -130,15 +167,11 @@
         "UV_DEFAULT_INDEX",
         "DOCKER_CONTEXT",
         "LIBRARY_PATH",
    +    "LDFLAGS",
         "CPATH",
         "C_INCLUDE_PATH",
         "CPLUS_INCLUDE_PATH",
         "OBJC_INCLUDE_PATH",
    -    "NODE_EXTRA_CA_CERTS",
    -    "SSL_CERT_FILE",
    -    "SSL_CERT_DIR",
    -    "REQUESTS_CA_BUNDLE",
    -    "CURL_CA_BUNDLE",
         "GOPROXY",
         "GONOSUMCHECK",
         "GONOSUMDB",
    @@ -160,15 +193,78 @@
         "COMPOSER_HOME",
         "CARGO_BUILD_RUSTC_WRAPPER",
         "XDG_CONFIG_HOME",
    +    "XDG_CONFIG_DIRS",
         "AWS_CONFIG_FILE",
         "KUBECONFIG",
         "GOOGLE_APPLICATION_CREDENTIALS",
         "AWS_SHARED_CREDENTIALS_FILE",
         "AWS_WEB_IDENTITY_TOKEN_FILE",
         "AZURE_AUTH_LOCATION",
    -    "CARGO_HOME",
    -    "HELM_HOME"
    +    "HELM_HOME",
    +    "ANSIBLE_CONFIG",
    +    "ANSIBLE_LIBRARY",
    +    "ANSIBLE_CALLBACK_PLUGINS",
    +    "ANSIBLE_COLLECTIONS_PATH",
    +    "ANSIBLE_CONNECTION_PLUGINS",
    +    "ANSIBLE_FILTER_PLUGINS",
    +    "ANSIBLE_INVENTORY_PLUGINS",
    +    "ANSIBLE_LOOKUP_PLUGINS",
    +    "ANSIBLE_MODULE_UTILS",
    +    "ANSIBLE_REMOTE_TEMP",
    +    "ANSIBLE_ROLES_PATH",
    +    "ANSIBLE_STRATEGY_PLUGINS",
    +    "R_LIBS_USER",
    +    "TF_CLI_CONFIG_FILE",
    +    "TF_PLUGIN_CACHE_DIR",
    +    "AMQP_URL",
    +    "AWS_ACCESS_KEY_ID",
    +    "AWS_CONTAINER_CREDENTIALS_FULL_URI",
    +    "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
    +    "AWS_SECRET_ACCESS_KEY",
    +    "AWS_SECURITY_TOKEN",
    +    "AWS_SESSION_TOKEN",
    +    "AZURE_CLIENT_ID",
    +    "AZURE_CLIENT_SECRET",
    +    "DATABASE_URL",
    +    "GH_TOKEN",
    +    "GITHUB_TOKEN",
    +    "GITLAB_TOKEN",
    +    "MONGODB_URI",
    +    "NODE_AUTH_TOKEN",
    +    "NPM_TOKEN",
    +    "REDIS_URL",
    +    "SSH_AUTH_SOCK"
    +  ],
    +  "allowedInheritedOverrideOnlyKeys": [
    +    "ALL_PROXY",
    +    "AWS_CONFIG_FILE",
    +    "AWS_SHARED_CREDENTIALS_FILE",
    +    "AWS_WEB_IDENTITY_TOKEN_FILE",
    +    "AZURE_AUTH_LOCATION",
    +    "CURL_CA_BUNDLE",
    +    "DOCKER_CERT_PATH",
    +    "DOCKER_CONTEXT",
    +    "DOCKER_HOST",
    +    "DOCKER_TLS_VERIFY",
    +    "GIT_PAGER",
    +    "GOOGLE_APPLICATION_CREDENTIALS",
    +    "GRADLE_USER_HOME",
    +    "HISTFILE",
    +    "HOME",
    +    "HTTPS_PROXY",
    +    "HTTP_PROXY",
    +    "KUBECONFIG",
    +    "MANPAGER",
    +    "NODE_EXTRA_CA_CERTS",
    +    "NODE_TLS_REJECT_UNAUTHORIZED",
    +    "NO_PROXY",
    +    "PAGER",
    +    "REQUESTS_CA_BUNDLE",
    +    "SSH_AUTH_SOCK",
    +    "SSL_CERT_DIR",
    +    "SSL_CERT_FILE",
    +    "ZDOTDIR"
       ],
    -  "blockedOverridePrefixes": ["GIT_CONFIG_", "NPM_CONFIG_", "CARGO_REGISTRIES_"],
    +  "blockedOverridePrefixes": ["GIT_CONFIG_", "NPM_CONFIG_", "CARGO_REGISTRIES_", "TF_VAR_"],
       "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_"]
     }
    
  • src/infra/host-env-security.policy-parity.test.ts+32 0 modified
    @@ -36,6 +36,14 @@ describe("host env security policy parity", () => {
         const sanitizerSource = fs.readFileSync(sanitizerSwiftPath, "utf8");
     
         const swiftBlockedKeys = parseSwiftStringArray(generatedSource, "static let blockedKeys");
    +    const swiftBlockedInheritedKeys = parseSwiftStringArray(
    +      generatedSource,
    +      "static let blockedInheritedKeys",
    +    );
    +    const swiftBlockedInheritedPrefixes = parseSwiftStringArray(
    +      generatedSource,
    +      "static let blockedInheritedPrefixes",
    +    );
         const swiftBlockedOverrideKeys = parseSwiftStringArray(
           generatedSource,
           "static let blockedOverrideKeys",
    @@ -49,11 +57,19 @@ describe("host env security policy parity", () => {
           "static let blockedPrefixes",
         );
     
    +    expect(swiftBlockedInheritedKeys).toEqual(policy.blockedInheritedKeys);
    +    expect(swiftBlockedInheritedPrefixes).toEqual(policy.blockedInheritedPrefixes ?? []);
         expect(swiftBlockedKeys).toEqual(policy.blockedKeys);
         expect(swiftBlockedOverrideKeys).toEqual(policy.blockedOverrideKeys ?? []);
         expect(swiftBlockedOverridePrefixes).toEqual(policy.blockedOverridePrefixes ?? []);
         expect(swiftBlockedPrefixes).toEqual(policy.blockedPrefixes);
     
    +    expect(sanitizerSource).toContain(
    +      "private static let blockedInheritedKeys = HostEnvSecurityPolicy.blockedInheritedKeys",
    +    );
    +    expect(sanitizerSource).toContain(
    +      "private static let blockedInheritedPrefixes = HostEnvSecurityPolicy.blockedInheritedPrefixes",
    +    );
         expect(sanitizerSource).toContain(
           "private static let blockedKeys = HostEnvSecurityPolicy.blockedKeys",
         );
    @@ -73,8 +89,24 @@ describe("host env security policy parity", () => {
         const policyPath = path.join(repoRoot, "src/infra/host-env-security-policy.json");
         const rawPolicy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
         const policy = loadHostEnvSecurityPolicy(rawPolicy);
    +    const allowedInheritedOverrideOnlyKeys = new Set(
    +      (rawPolicy.allowedInheritedOverrideOnlyKeys ?? []).map((value: string) =>
    +        value.toUpperCase(),
    +      ),
    +    );
     
         expect(policy.blockedKeys).toEqual(sortUnique([...policy.blockedEverywhereKeys]));
         expect(policy.blockedOverrideKeys).toEqual(sortUnique([...policy.blockedOverrideOnlyKeys]));
    +    expect(policy.blockedInheritedKeys).toEqual(
    +      sortUnique([
    +        ...policy.blockedEverywhereKeys,
    +        ...policy.blockedOverrideOnlyKeys.filter(
    +          (value) => !allowedInheritedOverrideOnlyKeys.has(value.toUpperCase()),
    +        ),
    +      ]),
    +    );
    +    expect(policy.blockedInheritedPrefixes).toEqual(
    +      sortUnique(rawPolicy.blockedInheritedPrefixes ?? rawPolicy.blockedPrefixes ?? []),
    +    );
       });
     });
    
  • src/infra/host-env-security.reported-baseline.json+241 0 added
    @@ -0,0 +1,241 @@
    +{
    +  "source": "OpenClaw host env dangerous-variable baseline (reported GHSA class)",
    +  "generatedAt": "2026-04-10",
    +  "reportedDangerousEverywhereKeys": [
    +    "_JAVA_OPTIONS",
    +    "ANT_OPTS",
    +    "BASH_ENV",
    +    "BROWSER",
    +    "BZR_EDITOR",
    +    "BZR_PLUGIN_PATH",
    +    "BZR_SSH",
    +    "CARGO_BUILD_RUSTC",
    +    "CARGO_BUILD_RUSTC_WRAPPER",
    +    "CATALINA_OPTS",
    +    "CC",
    +    "CMAKE_C_COMPILER",
    +    "CMAKE_CXX_COMPILER",
    +    "CMAKE_TOOLCHAIN_FILE",
    +    "CONFIG_SHELL",
    +    "CONFIG_SITE",
    +    "CORECLR_PROFILER",
    +    "CXX",
    +    "DOTNET_ADDITIONAL_DEPS",
    +    "DOTNET_STARTUP_HOOKS",
    +    "ELIXIR_ERL_OPTIONS",
    +    "EMACSLOADPATH",
    +    "ENV",
    +    "ERL_AFLAGS",
    +    "ERL_FLAGS",
    +    "ERL_ZFLAGS",
    +    "EXINIT",
    +    "GCONV_PATH",
    +    "GIT_ALTERNATE_OBJECT_DIRECTORIES",
    +    "GIT_COMMON_DIR",
    +    "GIT_DIR",
    +    "GIT_EDITOR",
    +    "GIT_EXEC_PATH",
    +    "GIT_EXTERNAL_DIFF",
    +    "GIT_HOOK_PATH",
    +    "GIT_INDEX_FILE",
    +    "GIT_NAMESPACE",
    +    "GIT_OBJECT_DIRECTORY",
    +    "GIT_SEQUENCE_EDITOR",
    +    "GIT_SSL_CAINFO",
    +    "GIT_SSL_CAPATH",
    +    "GIT_SSL_NO_VERIFY",
    +    "GIT_TEMPLATE_DIR",
    +    "GIT_WORK_TREE",
    +    "GLIBC_TUNABLES",
    +    "GRADLE_OPTS",
    +    "GVIMINIT",
    +    "HELM_PLUGINS",
    +    "HGRCPATH",
    +    "HOSTALIASES",
    +    "IFS",
    +    "JAVA_OPTS",
    +    "JAVA_TOOL_OPTIONS",
    +    "JDK_JAVA_OPTIONS",
    +    "JULIA_EDITOR",
    +    "LUA_INIT",
    +    "LUA_INIT_5_1",
    +    "LUA_INIT_5_2",
    +    "LUA_INIT_5_3",
    +    "LUA_INIT_5_4",
    +    "MAKEFLAGS",
    +    "MAVEN_OPTS",
    +    "MFLAGS",
    +    "MYVIMRC",
    +    "NODE_OPTIONS",
    +    "NODE_PATH",
    +    "PACKER_PLUGIN_PATH",
    +    "PERL5LIB",
    +    "PERL5OPT",
    +    "PS4",
    +    "PYTHONBREAKPOINT",
    +    "PYTHONHOME",
    +    "PYTHONPATH",
    +    "R_ENVIRON",
    +    "R_ENVIRON_USER",
    +    "R_PROFILE",
    +    "R_PROFILE_USER",
    +    "RUBYLIB",
    +    "RUBYOPT",
    +    "RUBYSHELL",
    +    "RUSTC_WRAPPER",
    +    "SBT_OPTS",
    +    "SHELL",
    +    "SHELLOPTS",
    +    "SSLKEYLOGFILE",
    +    "SUDO_ASKPASS",
    +    "SVN_EDITOR",
    +    "SVN_SSH",
    +    "VAGRANT_VAGRANTFILE",
    +    "VIMINIT"
    +  ],
    +  "reportedDangerousOverrideOnlyKeys": [
    +    "ALL_PROXY",
    +    "AMQP_URL",
    +    "ANSIBLE_CALLBACK_PLUGINS",
    +    "ANSIBLE_COLLECTIONS_PATH",
    +    "ANSIBLE_CONFIG",
    +    "ANSIBLE_CONNECTION_PLUGINS",
    +    "ANSIBLE_FILTER_PLUGINS",
    +    "ANSIBLE_INVENTORY_PLUGINS",
    +    "ANSIBLE_LIBRARY",
    +    "ANSIBLE_LOOKUP_PLUGINS",
    +    "ANSIBLE_MODULE_UTILS",
    +    "ANSIBLE_REMOTE_TEMP",
    +    "ANSIBLE_ROLES_PATH",
    +    "ANSIBLE_STRATEGY_PLUGINS",
    +    "AWS_ACCESS_KEY_ID",
    +    "AWS_CONFIG_FILE",
    +    "AWS_CONTAINER_CREDENTIALS_FULL_URI",
    +    "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
    +    "AWS_SECRET_ACCESS_KEY",
    +    "AWS_SECURITY_TOKEN",
    +    "AWS_SESSION_TOKEN",
    +    "AWS_SHARED_CREDENTIALS_FILE",
    +    "AWS_WEB_IDENTITY_TOKEN_FILE",
    +    "AZURE_AUTH_LOCATION",
    +    "AZURE_CLIENT_ID",
    +    "AZURE_CLIENT_SECRET",
    +    "BUN_CONFIG_REGISTRY",
    +    "BUNDLE_GEMFILE",
    +    "C_INCLUDE_PATH",
    +    "CARGO_BUILD_RUSTC_WRAPPER",
    +    "CARGO_HOME",
    +    "CFLAGS",
    +    "CGO_CFLAGS",
    +    "CGO_LDFLAGS",
    +    "CLASSPATH",
    +    "COMPOSER_HOME",
    +    "CORECLR_PROFILER_PATH",
    +    "CPATH",
    +    "CPLUS_INCLUDE_PATH",
    +    "CURL_CA_BUNDLE",
    +    "CURL_HOME",
    +    "DATABASE_URL",
    +    "DENO_DIR",
    +    "DOCKER_CERT_PATH",
    +    "DOCKER_CONTEXT",
    +    "DOCKER_HOST",
    +    "DOCKER_TLS_VERIFY",
    +    "EDITOR",
    +    "FCEDIT",
    +    "GEM_HOME",
    +    "GEM_PATH",
    +    "GH_TOKEN",
    +    "GIT_ALTERNATE_OBJECT_DIRECTORIES",
    +    "GIT_ASKPASS",
    +    "GIT_COMMON_DIR",
    +    "GIT_DIR",
    +    "GIT_INDEX_FILE",
    +    "GIT_NAMESPACE",
    +    "GIT_OBJECT_DIRECTORY",
    +    "GIT_PAGER",
    +    "GIT_PROXY_COMMAND",
    +    "GIT_SSH",
    +    "GIT_SSH_COMMAND",
    +    "GIT_SSL_CAINFO",
    +    "GIT_SSL_CAPATH",
    +    "GIT_SSL_NO_VERIFY",
    +    "GIT_WORK_TREE",
    +    "GITHUB_TOKEN",
    +    "GITLAB_TOKEN",
    +    "GOENV",
    +    "GOFLAGS",
    +    "GONOPROXY",
    +    "GONOSUMCHECK",
    +    "GONOSUMDB",
    +    "GOOGLE_APPLICATION_CREDENTIALS",
    +    "GOPATH",
    +    "GOPRIVATE",
    +    "GOPROXY",
    +    "GRADLE_USER_HOME",
    +    "HELM_HOME",
    +    "HGRCPATH",
    +    "HISTFILE",
    +    "HOME",
    +    "HTTP_PROXY",
    +    "HTTPS_PROXY",
    +    "KUBECONFIG",
    +    "LDFLAGS",
    +    "LESSCLOSE",
    +    "LESSOPEN",
    +    "LIBRARY_PATH",
    +    "LUA_CPATH",
    +    "LUA_PATH",
    +    "MAKEFLAGS",
    +    "MANPAGER",
    +    "MFLAGS",
    +    "MONGODB_URI",
    +    "NO_PROXY",
    +    "NODE_AUTH_TOKEN",
    +    "NODE_EXTRA_CA_CERTS",
    +    "NODE_TLS_REJECT_UNAUTHORIZED",
    +    "NPM_TOKEN",
    +    "OBJC_INCLUDE_PATH",
    +    "OPENSSL_CONF",
    +    "OPENSSL_ENGINES",
    +    "PAGER",
    +    "PERL5DB",
    +    "PERL5DBCMD",
    +    "PHP_INI_SCAN_DIR",
    +    "PHPRC",
    +    "PIP_CONFIG_FILE",
    +    "PIP_EXTRA_INDEX_URL",
    +    "PIP_FIND_LINKS",
    +    "PIP_INDEX_URL",
    +    "PIP_PYPI_URL",
    +    "PIP_TRUSTED_HOST",
    +    "PROMPT_COMMAND",
    +    "PYTHONSTARTUP",
    +    "PYTHONUSERBASE",
    +    "R_LIBS_USER",
    +    "REDIS_URL",
    +    "REQUESTS_CA_BUNDLE",
    +    "RUSTC_WRAPPER",
    +    "RUSTFLAGS",
    +    "SSH_ASKPASS",
    +    "SSH_AUTH_SOCK",
    +    "SSL_CERT_DIR",
    +    "SSL_CERT_FILE",
    +    "SUDO_EDITOR",
    +    "TF_CLI_CONFIG_FILE",
    +    "TF_PLUGIN_CACHE_DIR",
    +    "UV_DEFAULT_INDEX",
    +    "UV_EXTRA_INDEX_URL",
    +    "UV_INDEX",
    +    "UV_INDEX_URL",
    +    "UV_PYTHON",
    +    "VIRTUAL_ENV",
    +    "VISUAL",
    +    "WGETRC",
    +    "XDG_CONFIG_DIRS",
    +    "XDG_CONFIG_HOME",
    +    "YARN_RC_FILENAME",
    +    "ZDOTDIR"
    +  ],
    +  "expectedTotalReportedEntries": 232
    +}
    
  • src/infra/host-env-security.reported-baseline.test.ts+180 0 added
    @@ -0,0 +1,180 @@
    +import fs from "node:fs";
    +import path from "node:path";
    +import { describe, expect, it } from "vitest";
    +import {
    +  isDangerousHostEnvOverrideVarName,
    +  isDangerousHostEnvVarName,
    +  isDangerousHostInheritedEnvVarName,
    +  sanitizeHostExecEnv,
    +  sanitizeHostExecEnvWithDiagnostics,
    +} from "./host-env-security.js";
    +
    +type HostEnvReportedBaseline = {
    +  source: string;
    +  generatedAt: string;
    +  reportedDangerousEverywhereKeys: string[];
    +  reportedDangerousOverrideOnlyKeys: string[];
    +  expectedTotalReportedEntries: number;
    +};
    +
    +const INHERITED_ALLOWLIST_RATIONALE: Record<string, string> = {
    +  ALL_PROXY: "Trusted inherited global proxy route from operator runtime.",
    +  AWS_CONFIG_FILE: "Trusted inherited AWS CLI/SDK config path selected by operator.",
    +  AWS_SHARED_CREDENTIALS_FILE:
    +    "Trusted inherited AWS shared credentials path selected by operator.",
    +  AWS_WEB_IDENTITY_TOKEN_FILE: "Trusted inherited AWS web identity token path.",
    +  AZURE_AUTH_LOCATION: "Trusted inherited Azure auth location selected by operator.",
    +  CURL_CA_BUNDLE: "Trusted inherited CA bundle path for TLS validation.",
    +  DOCKER_CERT_PATH: "Trusted inherited Docker client certificate location.",
    +  DOCKER_CONTEXT: "Trusted inherited Docker context selector from operator runtime.",
    +  DOCKER_HOST: "Trusted inherited Docker endpoint selected by operator.",
    +  DOCKER_TLS_VERIFY: "Trusted inherited Docker TLS verification mode.",
    +  GIT_PAGER: "Trusted inherited interactive pager preference.",
    +  GOOGLE_APPLICATION_CREDENTIALS:
    +    "Trusted inherited Google application credentials path selected by operator.",
    +  GRADLE_USER_HOME: "Trusted inherited tool cache directory location.",
    +  HISTFILE: "Trusted inherited shell history path.",
    +  HOME: "Trusted inherited process home-directory context.",
    +  HTTPS_PROXY: "Trusted inherited HTTPS proxy route from operator runtime.",
    +  HTTP_PROXY: "Trusted inherited HTTP proxy route from operator runtime.",
    +  KUBECONFIG: "Trusted inherited Kubernetes config path selected by operator.",
    +  MANPAGER: "Trusted inherited manual-page pager preference.",
    +  NODE_EXTRA_CA_CERTS: "Trusted inherited extra Node CA trust roots.",
    +  NODE_TLS_REJECT_UNAUTHORIZED: "Trusted inherited Node TLS mode from runtime policy.",
    +  NO_PROXY: "Trusted inherited proxy bypass list from operator runtime.",
    +  PAGER: "Trusted inherited default pager preference.",
    +  REQUESTS_CA_BUNDLE: "Trusted inherited Python requests CA bundle path.",
    +  SSH_AUTH_SOCK: "Trusted inherited SSH agent socket from operator runtime.",
    +  SSL_CERT_DIR: "Trusted inherited OpenSSL certificate directory path.",
    +  SSL_CERT_FILE: "Trusted inherited OpenSSL certificate file path.",
    +  ZDOTDIR: "Trusted inherited shell startup directory boundary.",
    +};
    +
    +function readBaselineAndPolicy(): {
    +  baseline: HostEnvReportedBaseline;
    +  allowedInheritedOverrideOnlyKeys: string[];
    +} {
    +  const repoRoot = process.cwd();
    +  const baselinePath = path.join(repoRoot, "src/infra/host-env-security.reported-baseline.json");
    +  const policyPath = path.join(repoRoot, "src/infra/host-env-security-policy.json");
    +  const baseline = JSON.parse(fs.readFileSync(baselinePath, "utf8")) as HostEnvReportedBaseline;
    +  const policy = JSON.parse(fs.readFileSync(policyPath, "utf8")) as {
    +    allowedInheritedOverrideOnlyKeys?: string[];
    +  };
    +  return {
    +    baseline,
    +    allowedInheritedOverrideOnlyKeys: (policy.allowedInheritedOverrideOnlyKeys ?? []).map((key) =>
    +      key.toUpperCase(),
    +    ),
    +  };
    +}
    +
    +function sortUniqueUpper(values: string[]): string[] {
    +  return Array.from(new Set(values.map((value) => value.toUpperCase()))).toSorted((a, b) =>
    +    a.localeCompare(b),
    +  );
    +}
    +
    +describe("host env reported baseline coverage", () => {
    +  it("keeps the fixed reported dangerous env baseline fully covered by inherited + override sanitization", () => {
    +    const { baseline, allowedInheritedOverrideOnlyKeys } = readBaselineAndPolicy();
    +
    +    expect(
    +      baseline.reportedDangerousEverywhereKeys.length +
    +        baseline.reportedDangerousOverrideOnlyKeys.length,
    +    ).toBe(baseline.expectedTotalReportedEntries);
    +    expect(baseline.expectedTotalReportedEntries).toBe(232);
    +    expect(sortUniqueUpper(baseline.reportedDangerousEverywhereKeys)).toEqual(
    +      baseline.reportedDangerousEverywhereKeys,
    +    );
    +    expect(sortUniqueUpper(baseline.reportedDangerousOverrideOnlyKeys)).toEqual(
    +      baseline.reportedDangerousOverrideOnlyKeys,
    +    );
    +
    +    const inheritedInput: Record<string, string> = {
    +      PATH: "/usr/bin:/bin",
    +    };
    +    for (const key of baseline.reportedDangerousEverywhereKeys) {
    +      inheritedInput[key] = `${key.toLowerCase()}-from-inherited`;
    +    }
    +    for (const key of baseline.reportedDangerousOverrideOnlyKeys) {
    +      inheritedInput[key] = `${key.toLowerCase()}-from-inherited`;
    +    }
    +    const inheritedSanitized = sanitizeHostExecEnv({ baseEnv: inheritedInput });
    +
    +    for (const key of baseline.reportedDangerousEverywhereKeys) {
    +      expect(isDangerousHostEnvVarName(key)).toBe(true);
    +      expect(isDangerousHostInheritedEnvVarName(key)).toBe(true);
    +      expect(inheritedSanitized[key]).toBeUndefined();
    +    }
    +
    +    const inheritedAllowlist = new Set(allowedInheritedOverrideOnlyKeys);
    +    for (const key of baseline.reportedDangerousOverrideOnlyKeys) {
    +      expect(isDangerousHostEnvOverrideVarName(key)).toBe(true);
    +      if (inheritedAllowlist.has(key)) {
    +        expect(isDangerousHostInheritedEnvVarName(key)).toBe(false);
    +        expect(inheritedSanitized[key]).toBe(`${key.toLowerCase()}-from-inherited`);
    +      } else {
    +        expect(isDangerousHostInheritedEnvVarName(key)).toBe(true);
    +        expect(inheritedSanitized[key]).toBeUndefined();
    +      }
    +    }
    +
    +    const overrideInput: Record<string, string> = {};
    +    for (const key of baseline.reportedDangerousEverywhereKeys) {
    +      overrideInput[key] = `${key.toLowerCase()}-from-override`;
    +    }
    +    for (const key of baseline.reportedDangerousOverrideOnlyKeys) {
    +      overrideInput[key] = `${key.toLowerCase()}-from-override`;
    +    }
    +
    +    const overrideResult = sanitizeHostExecEnvWithDiagnostics({
    +      baseEnv: { PATH: "/usr/bin:/bin" },
    +      overrides: overrideInput,
    +    });
    +    const expectedRejectedOverrideKeys = sortUniqueUpper([
    +      ...baseline.reportedDangerousEverywhereKeys,
    +      ...baseline.reportedDangerousOverrideOnlyKeys,
    +    ]);
    +    expect(overrideResult.rejectedOverrideBlockedKeys).toEqual(expectedRejectedOverrideKeys);
    +    expect(overrideResult.rejectedOverrideInvalidKeys).toEqual([]);
    +
    +    for (const key of expectedRejectedOverrideKeys) {
    +      expect(overrideResult.env[key]).toBeUndefined();
    +    }
    +  });
    +
    +  it("documents and enforces rationale for every inherited allowlist exception", () => {
    +    const { allowedInheritedOverrideOnlyKeys } = readBaselineAndPolicy();
    +    const expectedAllowlistKeys = Object.keys(INHERITED_ALLOWLIST_RATIONALE).toSorted((a, b) =>
    +      a.localeCompare(b),
    +    );
    +    expect(allowedInheritedOverrideOnlyKeys.toSorted((a, b) => a.localeCompare(b))).toEqual(
    +      expectedAllowlistKeys,
    +    );
    +
    +    for (const key of expectedAllowlistKeys) {
    +      expect(INHERITED_ALLOWLIST_RATIONALE[key].trim().length).toBeGreaterThan(0);
    +      expect(isDangerousHostInheritedEnvVarName(key)).toBe(false);
    +      expect(isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key)).toBe(true);
    +
    +      const inheritedSanitized = sanitizeHostExecEnv({
    +        baseEnv: {
    +          PATH: "/usr/bin:/bin",
    +          [key]: `${key.toLowerCase()}-trusted-inherited`,
    +        },
    +      });
    +      expect(inheritedSanitized[key]).toBe(`${key.toLowerCase()}-trusted-inherited`);
    +
    +      const overrideResult = sanitizeHostExecEnvWithDiagnostics({
    +        baseEnv: { PATH: "/usr/bin:/bin" },
    +        overrides: {
    +          [key]: `${key.toLowerCase()}-untrusted-override`,
    +        },
    +      });
    +      expect(overrideResult.rejectedOverrideBlockedKeys).toEqual([key]);
    +      expect(overrideResult.rejectedOverrideInvalidKeys).toEqual([]);
    +      expect(overrideResult.env[key]).toBeUndefined();
    +    }
    +  });
    +});
    
  • src/infra/host-env-security.test.ts+268 3 modified
    @@ -5,6 +5,7 @@ import path from "node:path";
     import { describe, expect, it } from "vitest";
     import {
       isDangerousHostEnvOverrideVarName,
    +  isDangerousHostInheritedEnvVarName,
       isDangerousHostEnvVarName,
       normalizeEnvVarKey,
       sanitizeHostExecEnv,
    @@ -223,6 +224,68 @@ describe("isDangerousHostEnvVarName", () => {
         expect(isDangerousHostEnvVarName("FOO")).toBe(false);
         expect(isDangerousHostEnvVarName("GRADLE_USER_HOME")).toBe(false);
       });
    +
    +  it("blocks newly added startup, orchestration, and resolver env keys", () => {
    +    const keys = [
    +      "VIMINIT",
    +      "EXINIT",
    +      "MYVIMRC",
    +      "GVIMINIT",
    +      "LUA_INIT",
    +      "LUA_INIT_5_4",
    +      "HOSTALIASES",
    +      "CONFIG_SITE",
    +      "CONFIG_SHELL",
    +      "CMAKE_TOOLCHAIN_FILE",
    +      "ERL_AFLAGS",
    +      "ERL_FLAGS",
    +      "ERL_ZFLAGS",
    +      "R_ENVIRON",
    +      "R_PROFILE_USER",
    +    ] as const;
    +
    +    for (const key of keys) {
    +      expect(isDangerousHostEnvVarName(key)).toBe(true);
    +      expect(isDangerousHostEnvVarName(key.toLowerCase())).toBe(true);
    +    }
    +
    +    expect(isDangerousHostEnvVarName("ANSIBLE_CONFIG")).toBe(false);
    +    expect(isDangerousHostEnvVarName("ANSIBLE_LIBRARY")).toBe(false);
    +    expect(isDangerousHostEnvVarName("TF_CLI_CONFIG_FILE")).toBe(false);
    +    expect(isDangerousHostEnvVarName("AWS_CONTAINER_CREDENTIALS_FULL_URI")).toBe(false);
    +    expect(isDangerousHostEnvVarName("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")).toBe(false);
    +  });
    +});
    +
    +describe("isDangerousHostInheritedEnvVarName", () => {
    +  it("blocks inherited keys from both policy buckets while preserving explicit inherited allowlist keys", () => {
    +    expect(isDangerousHostInheritedEnvVarName("BASH_ENV")).toBe(true);
    +    expect(isDangerousHostInheritedEnvVarName("bash_env")).toBe(true);
    +    expect(isDangerousHostInheritedEnvVarName("ANSIBLE_CONFIG")).toBe(true);
    +    expect(isDangerousHostInheritedEnvVarName("ansible_library")).toBe(true);
    +    expect(isDangerousHostInheritedEnvVarName("TF_CLI_CONFIG_FILE")).toBe(true);
    +    expect(isDangerousHostInheritedEnvVarName("TF_VAR_admin_cidr")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("AWS_CONTAINER_CREDENTIALS_FULL_URI")).toBe(true);
    +    expect(isDangerousHostInheritedEnvVarName("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")).toBe(true);
    +    expect(isDangerousHostInheritedEnvVarName("KUBECONFIG")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("GOOGLE_APPLICATION_CREDENTIALS")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("AWS_SHARED_CREDENTIALS_FILE")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("AWS_WEB_IDENTITY_TOKEN_FILE")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("AWS_CONFIG_FILE")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("AZURE_AUTH_LOCATION")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("SSH_AUTH_SOCK")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("DOCKER_CONTEXT")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("GIT_CONFIG_GLOBAL")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("NPM_CONFIG_USERCONFIG")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("CARGO_REGISTRIES_CRATES_IO_INDEX")).toBe(false);
    +
    +    expect(isDangerousHostInheritedEnvVarName("HTTP_PROXY")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("https_proxy")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("SSL_CERT_FILE")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("node_extra_ca_certs")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("HOME")).toBe(false);
    +    expect(isDangerousHostInheritedEnvVarName("FOO")).toBe(false);
    +  });
     });
     
     describe("sanitizeHostExecEnv", () => {
    @@ -255,12 +318,14 @@ describe("sanitizeHostExecEnv", () => {
             AWS_WEB_IDENTITY_TOKEN_FILE: "/tmp/aws-web-token",
             AZURE_AUTH_LOCATION: "/tmp/azure-auth.json",
             AWS_CONFIG_FILE: "/tmp/aws-config",
    +        SSH_AUTH_SOCK: "/tmp/trusted-ssh-agent.sock",
             CARGO_HOME: "/tmp/cargo",
             HELM_HOME: "/tmp/helm",
             HTTP_PROXY: "http://proxy.example.test:8080",
             HTTPS_PROXY: "http://proxy.example.test:8443",
             SSL_CERT_FILE: "/tmp/evil-cert.pem",
             SSL_CERT_DIR: "/tmp/evil-cert-dir",
    +        DOCKER_CONTEXT: "trusted-remote",
             DOCKER_HOST: "tcp://docker.example.test:2376",
             LD_PRELOAD: "/tmp/pwn.so",
             OK: "1",
    @@ -276,12 +341,12 @@ describe("sanitizeHostExecEnv", () => {
           AWS_SHARED_CREDENTIALS_FILE: "/tmp/aws-credentials",
           AWS_WEB_IDENTITY_TOKEN_FILE: "/tmp/aws-web-token",
           AZURE_AUTH_LOCATION: "/tmp/azure-auth.json",
    -      CARGO_HOME: "/tmp/cargo",
    -      HELM_HOME: "/tmp/helm",
    +      SSH_AUTH_SOCK: "/tmp/trusted-ssh-agent.sock",
           HTTP_PROXY: "http://proxy.example.test:8080",
           HTTPS_PROXY: "http://proxy.example.test:8443",
           SSL_CERT_FILE: "/tmp/evil-cert.pem",
           SSL_CERT_DIR: "/tmp/evil-cert-dir",
    +      DOCKER_CONTEXT: "trusted-remote",
           DOCKER_HOST: "tcp://docker.example.test:2376",
           OK: "1",
         });
    @@ -428,7 +493,7 @@ describe("sanitizeHostExecEnv", () => {
         expect(env.MFLAGS).toBeUndefined();
         expect(env.PHPRC).toBeUndefined();
         expect(env.XDG_CONFIG_HOME).toBeUndefined();
    -    expect(env.YARN_RC_FILENAME).toBe(".trusted-yarnrc.yml");
    +    expect(env.YARN_RC_FILENAME).toBeUndefined();
         expect(env.PIP_INDEX_URL).toBeUndefined();
         expect(env.PIP_PYPI_URL).toBeUndefined();
         expect(env.PIP_EXTRA_INDEX_URL).toBeUndefined();
    @@ -470,6 +535,110 @@ describe("sanitizeHostExecEnv", () => {
         expect(env.ZDOTDIR).toBe("/tmp/trusted-zdotdir");
       });
     
    +  it("drops inherited vars blocked by either policy bucket and keeps explicit inherited allowlist keys", () => {
    +    const env = sanitizeHostExecEnv({
    +      baseEnv: {
    +        PATH: "/usr/bin:/bin",
    +        HTTPS_PROXY: "http://trusted-proxy.example.test:8443",
    +        KUBECONFIG: "/tmp/trusted-kubeconfig",
    +        GOOGLE_APPLICATION_CREDENTIALS: "/tmp/trusted-gcp.json",
    +        AWS_SHARED_CREDENTIALS_FILE: "/tmp/trusted-aws-credentials",
    +        AWS_WEB_IDENTITY_TOKEN_FILE: "/tmp/trusted-aws-web-token",
    +        AWS_CONFIG_FILE: "/tmp/trusted-aws-config",
    +        AZURE_AUTH_LOCATION: "/tmp/trusted-azure-auth.json",
    +        SSH_AUTH_SOCK: "/tmp/trusted-ssh-agent.sock",
    +        DOCKER_CONTEXT: "trusted-remote",
    +        VIMINIT: ":!touch /tmp/pwned",
    +        EXINIT: "silent !touch /tmp/pwned",
    +        LUA_INIT_5_4: "os.execute('touch /tmp/pwned')",
    +        HOSTALIASES: "/tmp/evil-hostaliases",
    +        AWS_CONTAINER_CREDENTIALS_FULL_URI: "http://169.254.170.2/credentials",
    +        AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/v2/credentials/abcd",
    +        CONFIG_SITE: "/tmp/evil-config-site",
    +        ANSIBLE_CONFIG: "/tmp/evil-ansible.cfg",
    +        R_PROFILE_USER: "/tmp/evil-Rprofile",
    +        ERL_AFLAGS: "-eval 'os:cmd(\"id\")'",
    +        TF_CLI_CONFIG_FILE: "/tmp/evil-terraformrc",
    +        TF_VAR_admin_cidr: "10.0.0.0/24",
    +        SAFE: "1",
    +      },
    +    });
    +
    +    expect(env.PATH).toBe("/usr/bin:/bin");
    +    expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
    +    expect(env.VIMINIT).toBeUndefined();
    +    expect(env.EXINIT).toBeUndefined();
    +    expect(env.LUA_INIT_5_4).toBeUndefined();
    +    expect(env.HOSTALIASES).toBeUndefined();
    +    expect(env.HTTPS_PROXY).toBe("http://trusted-proxy.example.test:8443");
    +    expect(env.KUBECONFIG).toBe("/tmp/trusted-kubeconfig");
    +    expect(env.GOOGLE_APPLICATION_CREDENTIALS).toBe("/tmp/trusted-gcp.json");
    +    expect(env.AWS_SHARED_CREDENTIALS_FILE).toBe("/tmp/trusted-aws-credentials");
    +    expect(env.AWS_WEB_IDENTITY_TOKEN_FILE).toBe("/tmp/trusted-aws-web-token");
    +    expect(env.AWS_CONFIG_FILE).toBe("/tmp/trusted-aws-config");
    +    expect(env.AZURE_AUTH_LOCATION).toBe("/tmp/trusted-azure-auth.json");
    +    expect(env.SSH_AUTH_SOCK).toBe("/tmp/trusted-ssh-agent.sock");
    +    expect(env.DOCKER_CONTEXT).toBe("trusted-remote");
    +    expect(env.AWS_CONTAINER_CREDENTIALS_FULL_URI).toBeUndefined();
    +    expect(env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI).toBeUndefined();
    +    expect(env.CONFIG_SITE).toBeUndefined();
    +    expect(env.ANSIBLE_CONFIG).toBeUndefined();
    +    expect(env.R_PROFILE_USER).toBeUndefined();
    +    expect(env.ERL_AFLAGS).toBeUndefined();
    +    expect(env.TF_CLI_CONFIG_FILE).toBeUndefined();
    +    expect(env.TF_VAR_admin_cidr).toBe("10.0.0.0/24");
    +    expect(env.SAFE).toBe("1");
    +  });
    +
    +  it("drops newly blocked override credential and startup vars", () => {
    +    const env = sanitizeHostExecEnv({
    +      baseEnv: {
    +        PATH: "/usr/bin:/bin",
    +      },
    +      overrides: {
    +        VIMINIT: ":!touch /tmp/pwned",
    +        HOSTALIASES: "/tmp/evil-hostaliases",
    +        AWS_CONTAINER_CREDENTIALS_FULL_URI: "http://attacker/credentials",
    +        AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/attacker-credentials",
    +        ANSIBLE_CONFIG: "/tmp/override-ansible.cfg",
    +        ANSIBLE_REMOTE_TEMP: "/tmp/evil-ansible-remote",
    +        R_LIBS_USER: "/tmp/evil-r-libs-user",
    +        TF_CLI_CONFIG_FILE: "/tmp/override-terraformrc",
    +        TF_PLUGIN_CACHE_DIR: "/tmp/evil-tf-plugin-cache",
    +        CFLAGS: "-I/attacker/include",
    +        LDFLAGS: "-L/attacker/lib",
    +        XDG_CONFIG_DIRS: "/tmp/evil-config-dirs",
    +        TF_VAR_admin_cidr: "10.0.0.0/24",
    +        GITHUB_TOKEN: "ghp-test",
    +        DATABASE_URL: "postgres://attacker",
    +        NPM_TOKEN: "npm-test",
    +        SSH_AUTH_SOCK: "/tmp/evil-agent.sock",
    +        SAFE: "ok",
    +      },
    +    });
    +
    +    expect(env.PATH).toBe("/usr/bin:/bin");
    +    expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
    +    expect(env.VIMINIT).toBeUndefined();
    +    expect(env.HOSTALIASES).toBeUndefined();
    +    expect(env.AWS_CONTAINER_CREDENTIALS_FULL_URI).toBeUndefined();
    +    expect(env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI).toBeUndefined();
    +    expect(env.ANSIBLE_CONFIG).toBeUndefined();
    +    expect(env.ANSIBLE_REMOTE_TEMP).toBeUndefined();
    +    expect(env.R_LIBS_USER).toBeUndefined();
    +    expect(env.TF_CLI_CONFIG_FILE).toBeUndefined();
    +    expect(env.TF_PLUGIN_CACHE_DIR).toBeUndefined();
    +    expect(env.CFLAGS).toBeUndefined();
    +    expect(env.LDFLAGS).toBeUndefined();
    +    expect(env.XDG_CONFIG_DIRS).toBeUndefined();
    +    expect(env.TF_VAR_admin_cidr).toBeUndefined();
    +    expect(env.GITHUB_TOKEN).toBeUndefined();
    +    expect(env.DATABASE_URL).toBeUndefined();
    +    expect(env.NPM_TOKEN).toBeUndefined();
    +    expect(env.SSH_AUTH_SOCK).toBeUndefined();
    +    expect(env.SAFE).toBe("ok");
    +  });
    +
       it("keeps trusted inherited proxy and TLS env while blocking overrides", () => {
         const env = sanitizeHostExecEnv({
           baseEnv: {
    @@ -658,16 +827,53 @@ describe("isDangerousHostEnvOverrideVarName", () => {
         expect(isDangerousHostEnvOverrideVarName("cargo_build_rustc_wrapper")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("CARGO_HOME")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("cargo_home")).toBe(true);
    +    expect(isDangerousHostEnvOverrideVarName("TF_VAR_admin_cidr")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("CORECLR_PROFILER_PATH")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("coreclr_profiler_path")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("XDG_CONFIG_HOME")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("xdg_config_home")).toBe(true);
    +    expect(isDangerousHostEnvOverrideVarName("XDG_CONFIG_DIRS")).toBe(true);
    +    expect(isDangerousHostEnvOverrideVarName("xdg_config_dirs")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("AWS_CONFIG_FILE")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("aws_config_file")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("yarn_rc_filename")).toBe(true);
         expect(isDangerousHostEnvOverrideVarName("BASH_ENV")).toBe(false);
         expect(isDangerousHostEnvOverrideVarName("FOO")).toBe(false);
       });
    +
    +  it("blocks newly added credential and build influence keys", () => {
    +    const keys = [
    +      "GITHUB_TOKEN",
    +      "GH_TOKEN",
    +      "GITLAB_TOKEN",
    +      "NPM_TOKEN",
    +      "NODE_AUTH_TOKEN",
    +      "AWS_ACCESS_KEY_ID",
    +      "AWS_CONTAINER_CREDENTIALS_FULL_URI",
    +      "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
    +      "ANSIBLE_CONFIG",
    +      "ANSIBLE_LIBRARY",
    +      "ANSIBLE_REMOTE_TEMP",
    +      "R_LIBS_USER",
    +      "TF_CLI_CONFIG_FILE",
    +      "TF_PLUGIN_CACHE_DIR",
    +      "CFLAGS",
    +      "LDFLAGS",
    +      "XDG_CONFIG_DIRS",
    +      "AWS_SECRET_ACCESS_KEY",
    +      "AZURE_CLIENT_SECRET",
    +      "DATABASE_URL",
    +      "REDIS_URL",
    +      "MONGODB_URI",
    +      "AMQP_URL",
    +      "SSH_AUTH_SOCK",
    +    ] as const;
    +
    +    for (const key of keys) {
    +      expect(isDangerousHostEnvOverrideVarName(key)).toBe(true);
    +      expect(isDangerousHostEnvOverrideVarName(key.toLowerCase())).toBe(true);
    +    }
    +  });
     });
     
     describe("sanitizeHostExecEnvWithDiagnostics", () => {
    @@ -886,6 +1092,65 @@ describe("sanitizeHostExecEnvWithDiagnostics", () => {
         expect(result.env.YARN_RC_FILENAME).toBeUndefined();
       });
     
    +  it("reports newly blocked keys from everywhere and override buckets", () => {
    +    const result = sanitizeHostExecEnvWithDiagnostics({
    +      baseEnv: {
    +        PATH: "/usr/bin:/bin",
    +      },
    +      overrides: {
    +        VIMINIT: ":!touch /tmp/pwned",
    +        LUA_INIT_5_4: "os.execute('touch /tmp/pwned')",
    +        HOSTALIASES: "/tmp/evil-hostaliases",
    +        ANSIBLE_CONFIG: "/tmp/evil-ansible.cfg",
    +        ANSIBLE_REMOTE_TEMP: "/tmp/evil-ansible-remote",
    +        R_LIBS_USER: "/tmp/evil-r-libs-user",
    +        TF_CLI_CONFIG_FILE: "/tmp/evil-terraformrc",
    +        TF_PLUGIN_CACHE_DIR: "/tmp/evil-tf-plugin-cache",
    +        AWS_CONTAINER_CREDENTIALS_FULL_URI: "http://attacker/credentials",
    +        AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "/attacker-credentials",
    +        GITHUB_TOKEN: "ghp-test",
    +        DATABASE_URL: "postgres://attacker",
    +        R_PROFILE_USER: "/tmp/evil-Rprofile",
    +        XDG_CONFIG_DIRS: "/tmp/evil-config-dirs",
    +        TF_VAR_admin_cidr: "10.0.0.0/24",
    +        SAFE_KEY: "ok",
    +      },
    +    });
    +
    +    expect(result.rejectedOverrideBlockedKeys).toEqual([
    +      "ANSIBLE_CONFIG",
    +      "ANSIBLE_REMOTE_TEMP",
    +      "AWS_CONTAINER_CREDENTIALS_FULL_URI",
    +      "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
    +      "DATABASE_URL",
    +      "GITHUB_TOKEN",
    +      "HOSTALIASES",
    +      "LUA_INIT_5_4",
    +      "R_LIBS_USER",
    +      "R_PROFILE_USER",
    +      "TF_CLI_CONFIG_FILE",
    +      "TF_PLUGIN_CACHE_DIR",
    +      "TF_VAR_ADMIN_CIDR",
    +      "VIMINIT",
    +      "XDG_CONFIG_DIRS",
    +    ]);
    +    expect(result.rejectedOverrideInvalidKeys).toEqual([]);
    +    expect(result.env.SAFE_KEY).toBe("ok");
    +    expect(result.env.VIMINIT).toBeUndefined();
    +    expect(result.env.LUA_INIT_5_4).toBeUndefined();
    +    expect(result.env.HOSTALIASES).toBeUndefined();
    +    expect(result.env.ANSIBLE_CONFIG).toBeUndefined();
    +    expect(result.env.ANSIBLE_REMOTE_TEMP).toBeUndefined();
    +    expect(result.env.R_LIBS_USER).toBeUndefined();
    +    expect(result.env.TF_CLI_CONFIG_FILE).toBeUndefined();
    +    expect(result.env.TF_PLUGIN_CACHE_DIR).toBeUndefined();
    +    expect(result.env.GITHUB_TOKEN).toBeUndefined();
    +    expect(result.env.DATABASE_URL).toBeUndefined();
    +    expect(result.env.R_PROFILE_USER).toBeUndefined();
    +    expect(result.env.XDG_CONFIG_DIRS).toBeUndefined();
    +    expect(result.env.TF_VAR_admin_cidr).toBeUndefined();
    +  });
    +
       it("allows Windows-style override names while still rejecting invalid keys", () => {
         const result = sanitizeHostExecEnvWithDiagnostics({
           baseEnv: {
    
  • src/infra/host-env-security.ts+22 1 modified
    @@ -10,6 +10,12 @@ export const HOST_DANGEROUS_ENV_KEY_VALUES: readonly string[] = Object.freeze([
     export const HOST_DANGEROUS_ENV_PREFIXES: readonly string[] = Object.freeze([
       ...HOST_ENV_SECURITY_POLICY.blockedPrefixes,
     ]);
    +export const HOST_DANGEROUS_INHERITED_ENV_KEY_VALUES: readonly string[] = Object.freeze([
    +  ...HOST_ENV_SECURITY_POLICY.blockedInheritedKeys,
    +]);
    +export const HOST_DANGEROUS_INHERITED_ENV_PREFIXES: readonly string[] = Object.freeze([
    +  ...HOST_ENV_SECURITY_POLICY.blockedInheritedPrefixes,
    +]);
     export const HOST_DANGEROUS_OVERRIDE_ENV_KEY_VALUES: readonly string[] = Object.freeze([
       ...HOST_ENV_SECURITY_POLICY.blockedOverrideKeys,
     ]);
    @@ -27,6 +33,9 @@ export const HOST_SHELL_WRAPPER_ALLOWED_OVERRIDE_ENV_KEY_VALUES: readonly string
       "FORCE_COLOR",
     ]);
     export const HOST_DANGEROUS_ENV_KEYS = new Set<string>(HOST_DANGEROUS_ENV_KEY_VALUES);
    +export const HOST_DANGEROUS_INHERITED_ENV_KEYS = new Set<string>(
    +  HOST_DANGEROUS_INHERITED_ENV_KEY_VALUES,
    +);
     export const HOST_DANGEROUS_OVERRIDE_ENV_KEYS = new Set<string>(
       HOST_DANGEROUS_OVERRIDE_ENV_KEY_VALUES,
     );
    @@ -82,6 +91,18 @@ export function isDangerousHostEnvVarName(rawKey: string): boolean {
       return HOST_DANGEROUS_ENV_PREFIXES.some((prefix) => upper.startsWith(prefix));
     }
     
    +export function isDangerousHostInheritedEnvVarName(rawKey: string): boolean {
    +  const key = normalizeEnvVarKey(rawKey);
    +  if (!key) {
    +    return false;
    +  }
    +  const upper = key.toUpperCase();
    +  if (HOST_DANGEROUS_INHERITED_ENV_KEYS.has(upper)) {
    +    return true;
    +  }
    +  return HOST_DANGEROUS_INHERITED_ENV_PREFIXES.some((prefix) => upper.startsWith(prefix));
    +}
    +
     export function isDangerousHostEnvOverrideVarName(rawKey: string): boolean {
       const key = normalizeEnvVarKey(rawKey);
       if (!key) {
    @@ -178,7 +199,7 @@ export function sanitizeHostExecEnvWithDiagnostics(params?: {
     
       const merged: Record<string, string> = {};
       for (const [key, value] of listNormalizedEnvEntries(baseEnv)) {
    -    if (isDangerousHostEnvVarName(key)) {
    +    if (isDangerousHostInheritedEnvVarName(key)) {
           continue;
         }
         merged[key] = value;
    

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

5

News mentions

0

No linked articles in our index yet.