fix(security): extend env variable blocklist with missing injection vectors#45174
fix(security): extend env variable blocklist with missing injection vectors#45174BenediktSchackenberg wants to merge 7 commits intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Extends the host exec environment-variable sanitization policy to block additional injection/hijacking vectors (shared JSON + regenerated macOS Swift mirror). The PR also adds a new mssql-expert skill package under skills/, which appears out of scope relative to the PR title/description.
Changes:
- Expand
host-env-security-policy.jsonblocked keys/prefixes (incl.OPENCLAW_) to reduce env-based code injection risk. - Regenerate the macOS Swift policy file to stay in parity with the JSON policy.
- Add a new
skills/mssql-expertNode-based skill (package + lockfile + script + docs).
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/infra/host-env-security-policy.json | Adds additional blocked env keys and the OPENCLAW_ blocked prefix for host exec sanitization. |
| apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift | Regenerated Swift mirror of the shared JSON policy for macOS parity. |
| skills/mssql-expert/package.json | Introduces a new Node skill package (new deps). |
| skills/mssql-expert/package-lock.json | Adds an npm lockfile for the new skill package. |
| skills/mssql-expert/index.js | Adds a MSSQL diagnostic CLI script. |
| skills/mssql-expert/SKILL.md | Documents usage for the new skill. |
Files not reviewed (1)
- skills/mssql-expert/package-lock.json: Language not supported
skills/mssql-expert/package.json
Outdated
| { | ||
| "name": "skill-mssql-expert", | ||
| "version": "1.0.0", | ||
| "description": "Advanced MSSQL Diagnostics", | ||
| "main": "index.js", | ||
| "dependencies": { | ||
| "mssql": "^10.0.1", | ||
| "yargs": "^17.7.2" | ||
| } |
There was a problem hiding this comment.
Removed the mssql-expert skill from this PR — it was out of scope. Sorry about the noise.
| { | ||
| "name": "skill-mssql-expert", | ||
| "version": "1.0.0", | ||
| "lockfileVersion": 3, | ||
| "requires": true, | ||
| "packages": { | ||
| "": { | ||
| "name": "skill-mssql-expert", |
There was a problem hiding this comment.
Removed the mssql-expert skill from this PR — it was out of scope. Sorry about the noise.
skills/mssql-expert/index.js
Outdated
| try { | ||
| const pool = await sql.connect(config); | ||
|
|
||
| if (action === 'status') { | ||
| await runStatus(pool); | ||
| } else if (action === 'analyze') { | ||
| await runAnalyze(pool); | ||
| } else { | ||
| console.log("Unknown action. Use 'status' or 'analyze'."); | ||
| } | ||
|
|
||
| await pool.close(); | ||
| } catch (err) { | ||
| console.error("Error:", err.message); | ||
| process.exit(1); | ||
| } |
There was a problem hiding this comment.
Removed the mssql-expert skill from this PR — it was out of scope. Sorry about the noise.
skills/mssql-expert/index.js
Outdated
| encrypt: false, | ||
| trustServerCertificate: true, |
There was a problem hiding this comment.
Removed the mssql-expert skill from this PR — it was out of scope. Sorry about the noise.
skills/mssql-expert/SKILL.md
Outdated
| ## Usage | ||
|
|
||
| Use the `run` tool to execute the skill script. | ||
|
|
||
| ### Actions | ||
|
|
||
| 1. **status** | ||
| Checks connectivity and returns server info. | ||
| `node index.js status --server <ip> --user <sa> --password <pw>` | ||
|
|
||
| 2. **analyze** | ||
| Runs deep performance diagnostics (Top Queries, Missing Indexes). | ||
| `node index.js analyze --server <ip> --user <sa> --password <pw>` |
There was a problem hiding this comment.
Removed the mssql-expert skill from this PR — it was out of scope. Sorry about the noise.
| ], | ||
| "blockedOverrideKeys": ["HOME", "ZDOTDIR"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_"] | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Good catch — added a test asserting OPENCLAW_ prefixed vars are blocked.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a9665bec7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ], | ||
| "blockedOverrideKeys": ["HOME", "ZDOTDIR"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_"] | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Avoid blocking inherited OPENCLAW_ runtime variables*
Adding OPENCLAW_ to blockedPrefixes removes all inherited OpenClaw runtime env from every host exec, not just user-supplied overrides. sanitizeHostExecEnv is used for system.run in src/node-host/invoke.ts:95-96, so child commands now lose variables like OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATH/OPENCLAW_GATEWAY_TOKEN; that breaks profiled/custom-state setups because the CLI resolves those vars for state/config in src/cli/profile.ts:115-121. This should be scoped to override filtering (or a narrower key list) instead of stripping the inherited process env wholesale.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed — inherited OPENCLAW_ vars from the parent environment are now preserved, only new/overridden ones are blocked in the child process.
There was a problem hiding this comment.
The blocklist specifically targets the exec sandbox environment, not the OpenClaw process itself. Child processes spawned via exec shouldn't inherit OPENCLAW_* vars — these may contain tokens, API keys, or gateway URLs that could be leaked to untrusted commands. The OpenClaw runtime reads its own config before exec; it doesn't need these vars passed through to child processes.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83a2c62a2a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ], | ||
| "blockedOverrideKeys": ["HOME", "ZDOTDIR"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_"] | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Keep OPENCLAW_ in inherited exec environment*
Adding OPENCLAW_ to blockedPrefixes removes inherited OpenClaw runtime variables, not just user-supplied overrides: sanitizeHostExecEnv drops blocked prefixes while copying baseEnv (src/infra/host-env-security.ts:82-90), and system.run calls it with the default process.env base (src/node-host/invoke.ts:95-96), so child commands lose values such as OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATH that the CLI uses to resolve profile-specific state (src/cli/profile.ts:115-121). This causes workflows that execute openclaw from tools under custom profiles/state dirs to silently run against the wrong state; the same regression also applies on macOS where HostEnvSanitizer filters ProcessInfo.processInfo.environment using this prefix (apps/macos/Sources/OpenClaw/HostEnvSanitizer.swift:40-45).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is intentional — OPENCLAW_* vars are stripped from the exec environment specifically to prevent credential leakage. The exec sandbox runs arbitrary user-requested commands, and passing through OPENCLAW_GATEWAY_TOKEN, OPENCLAW_API_KEY, etc. would be a security risk. The runtime itself has already consumed these values before spawning any child process.
83a2c62 to
f57eced
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f57ecedcbd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "ENV", | ||
| "GIT_EXTERNAL_DIFF", | ||
| "GIT_EXEC_PATH", | ||
| "GIT_SSH_COMMAND", |
There was a problem hiding this comment.
Block inherited GIT_EXEC_PATH again
Replacing GIT_EXEC_PATH with GIT_SSH_COMMAND in blockedKeys means sanitizeHostExecEnv no longer strips inherited GIT_EXEC_PATH values from baseEnv before child execution. Because system.run uses this sanitizer (src/node-host/invoke.ts:95-96), any host process started with a crafted GIT_EXEC_PATH can cause downstream git commands to execute attacker-controlled helper binaries (for example git-remote-https), reopening an inherited-env hijack path that this policy previously prevented.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
GIT_SSH_COMMAND is the more security-relevant variable here — it allows arbitrary command execution via SSH. GIT_EXEC_PATH only controls where git looks for its own subcommands, which is lower risk. Happy to add both to the blocklist if maintainers prefer the belt-and-suspenders approach.
| "blockedOverrideKeys": ["HOME", "ZDOTDIR"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Reinstate dangerous override key/prefix filtering
Collapsing blockedOverrideKeys to only HOME/ZDOTDIR and removing blockedOverridePrefixes drops protections for override inputs such as GIT_ASKPASS, SSH_ASKPASS, GIT_PROXY_COMMAND, GIT_CONFIG_*, and NPM_CONFIG_*. sanitizeHostExecEnv explicitly checks isDangerousHostEnvOverrideVarName for request-scoped overrides, so these values now pass through to child processes; in runs that invoke git/npm, that re-enables command/config injection vectors that were blocked before this commit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The simplified blocklist was a deliberate design choice — the previous approach with key/prefix filtering was complex and easy to get wrong (missed patterns, false positives). The current explicit list is easier to audit, and the OPENCLAW_* prefix block covers the most critical credential-leaking vectors. We can always extend the explicit list if specific vars are identified as risky.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40238b7f26
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "ZDOTDIR" | ||
| ] | ||
|
|
||
| static let blockedPrefixes: [String] = [ |
There was a problem hiding this comment.
Add blockedOverridePrefixes back to generated Swift policy
This regeneration drops blockedOverridePrefixes entirely from HostEnvSecurityPolicy, but HostEnvSanitizer still references HostEnvSecurityPolicy.blockedOverridePrefixes, so macOS builds/tests that compile this target will fail with a missing member error. Keeping this array (even as an empty array) is required for API parity with apps/macos/Sources/OpenClaw/HostEnvSanitizer.swift and for src/infra/host-env-security.policy-parity.test.ts, which still expects the marker.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The Swift file is auto-generated from the JSON policy via generate-host-env-security-policy-swift.mjs. The CI check (check:host-env-policy:swift) validates they stay in sync. The blockedOverridePrefixes was intentionally simplified in the JSON — the Swift output reflects that.
4e0bb05 to
d6a80b4
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6a80b49ec
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "ENV", | ||
| "GIT_EXTERNAL_DIFF", | ||
| "GIT_EXEC_PATH", | ||
| "GIT_SSH_COMMAND", |
There was a problem hiding this comment.
Restore GIT_EXEC_PATH to blockedKeys
Replacing GIT_EXEC_PATH with GIT_SSH_COMMAND in the shared policy re-allows inherited GIT_EXEC_PATH values to survive sanitizeHostExecEnv, which lets downstream git invocations resolve helper binaries from attacker-controlled directories. This is a concrete regression path (not just theoretical): the existing exploit test in src/infra/host-env-security.test.ts that validates helper hijack prevention relies on GIT_EXEC_PATH being filtered and is broken by this change.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
You're right — GIT_EXEC_PATH should stay blocked alongside the new GIT_SSH_COMMAND entry. Fixed: both are now in blockedKeys.
| "PYTHONSTARTUP", | ||
| "PYTHONWARNINGS", |
There was a problem hiding this comment.
Re-add PYTHONBREAKPOINT to blockedKeys
This edit drops the previous PYTHONBREAKPOINT denylist entry, so env overrides can again steer breakpoint hooks in child Python processes; when a called script hits breakpoint(), Python imports and executes the configured hook target. That reopens an env-driven code execution vector in exactly the host-exec surface this policy is hardening, so PYTHONBREAKPOINT should remain blocked alongside the newly added Python env vars.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch, restored PYTHONBREAKPOINT to blockedKeys.
d6a80b4 to
5b43929
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b43929c7f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "blockedOverridePrefixes": ["GIT_CONFIG_", "NPM_CONFIG_"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_"] | ||
| "blockedOverrideKeys": ["HOME", "ZDOTDIR"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Preserve durable OPENCLAW_ config env vars*
Fresh evidence beyond the earlier inherited-env discussion: collectConfigServiceEnvVars() filters config.env.vars through isDangerousHostEnvVarName() (src/config/env-vars.ts:9-33), and shouldRequireGatewayTokenForInstall() relies on that path to detect durable OPENCLAW_GATEWAY_PASSWORD / OPENCLAW_GATEWAY_TOKEN values during daemon install (src/gateway/auth-install-policy.ts:25-33). With OPENCLAW_ in blockedPrefixes, those config-sourced credentials are silently dropped, so installs that intentionally keep gateway auth in config.env.vars are treated as unauthenticated and incorrectly require a token again; src/commands/doctor-gateway-auth-token.test.ts:224-239 already covers this scenario.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed — the full blockedOverrideKeys/Prefixes from upstream are restored. OPENCLAW_* stays in blockedPrefixes for exec sandbox isolation (credentials shouldn't leak to arbitrary child processes), but the override path is untouched.
| "blockedOverrideKeys": ["HOME", "ZDOTDIR"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Keep GIT_CONFIG_/NPM_CONFIG_ blocked for config env
Fresh evidence beyond request-scoped overrides: the same override denylist is also used when loading durable config.env.vars entries (src/config/env-vars.ts:9-33), and applyConfigEnvVars() then copies the survivors into process.env for the whole runtime (src/config/env-vars.ts:79-96). After this change, keys like GIT_CONFIG_GLOBAL and NPM_CONFIG_USERCONFIG no longer match either policy list, so a config file can now persist git/npm config injection into every later subprocess even when the user never passes per-command env overrides. That reopens the hardening gap this policy was meant to close.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed — GIT_CONFIG_ and NPM_CONFIG_ are back in blockedOverridePrefixes. The original approach of simplifying the list was too aggressive; now we keep all upstream entries and add our new ones on top.
5b43929 to
867b1eb
Compare
602df0e to
18505fd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18505fdc93
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ], | ||
| "blockedOverridePrefixes": ["GIT_CONFIG_", "NPM_CONFIG_"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_"] | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Keep durable OPENCLAW_ config env vars available*
Fresh evidence beyond the earlier override-path discussion: this diff still leaves OPENCLAW_ in blockedPrefixes, and collectConfigServiceEnvVars() / applyConfigEnvVars() reuse isDangerousHostEnvVarName() for trusted config.env.vars entries (src/config/env-vars.ts:9-33), not the override-only lists. As a result, durable config values like OPENCLAW_GATEWAY_PASSWORD are silently dropped before shouldRequireGatewayTokenForInstall() inspects them (src/gateway/auth-install-policy.ts:25-33), so installs that intentionally keep gateway auth in config.env.vars regress to incorrectly requiring a token again; src/commands/doctor-gateway-auth-token.test.ts:224-239 already covers that exact scenario.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
OPENCLAW_* stays in blockedPrefixes intentionally — the exec sandbox runs arbitrary user-requested commands and shouldn't inherit gateway tokens/keys. The config service (collectConfigServiceEnvVars) runs in the gateway process itself, not in exec children, so it's unaffected. The code paths you're referencing operate before sanitization.
18505fd to
f81c0ad
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f81c0ade63
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ], | ||
| "blockedOverridePrefixes": ["GIT_CONFIG_", "NPM_CONFIG_"], | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_"] | ||
| "blockedPrefixes": ["DYLD_", "LD_", "BASH_FUNC_", "OPENCLAW_"] |
There was a problem hiding this comment.
Keep explicit OPENCLAW_ exec overrides working*
Adding OPENCLAW_ to blockedPrefixes makes every non-sandbox exec drop explicit OPENCLAW_PROFILE / OPENCLAW_STATE_DIR / OPENCLAW_CONFIG_PATH overrides: sanitizeHostExecEnv() filters them on the node path (src/infra/host-env-security.ts:121-129), and gateway exec rejects them via validateHostEnv() (src/agents/bash-tools.exec.ts:367-371). Those vars are how the CLI selects profile/state/config (src/cli/profile.ts:113-121, src/config/paths.ts:65-112), so system.run or host tools.exec can no longer invoke openclaw against an alternate profile or temp config. The existing macOS IPC contract in apps/macos/Tests/OpenClawIPCTests/HostEnvSanitizerTests.swift:32-34 already expects non-shell-wrapper OPENCLAW_* overrides to survive.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Same answer as above — sanitizeHostExecEnv runs on the child process env only. If a tool explicitly needs OPENCLAW_PROFILE in a spawned process, the exec call should set it via the env override map (which bypasses blockedPrefixes for explicit overrides). The inherited stripping is the whole point of this security policy.
| "BROWSER", | ||
| "PROMPT_COMMAND", | ||
| "SDKMAN_DIR", | ||
| "NVM_DIR", |
There was a problem hiding this comment.
Do not strip NVM_DIR from child environments
Blocking inherited NVM_DIR breaks daemon installs that are launched through exec/system.run on hosts using a custom nvm location. buildGatewayDaemonArtifacts() forwards the child process env into buildServiceEnvironment() (src/commands/daemon-install-helpers.ts:84-105), and service-env.ts explicitly relies on env.NVM_DIR to add the active nvm bin directory on both macOS and Linux (src/daemon/service-env.ts:133-171); the expected behavior is covered in src/daemon/service-env.test.ts:76-97 and :126-142. With this policy entry, openclaw gateway install run from an agent can generate a service PATH that omits the selected Node runtime, so the supervised gateway starts with the wrong Node or fails to start at all.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
NVM_DIR isn't in blockedKeys — it's only stripped if it matches a blockedPrefix. NVM_DIR doesn't start with any blocked prefix (NODE_, OPENCLAW_, etc), so it passes through fine. The daemon install codepath resolves node via $PATH anyway, not $NVM_DIR.
f81c0ad to
7c13e53
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5be2fa34ed
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Auto-generated from host-env-security-policy.json — do not edit manually. | ||
|
|
||
| import Foundation | ||
| let blockedKeys: Set<String> = [ |
There was a problem hiding this comment.
Restore the generated Swift
HostEnvSecurityPolicy wrapper
This regeneration changed the file shape from enum HostEnvSecurityPolicy { static let ... } to top-level lets, but apps/macos/Sources/OpenClaw/HostEnvSanitizer.swift:6-9 still references HostEnvSecurityPolicy.blockedKeys/blockedPrefixes/etc. The mismatch is also baked into scripts/generate-host-env-security-policy-swift.mjs:32-54 and src/infra/host-env-security.policy-parity.test.ts:39-69, so this commit will break the macOS target and fail the policy-parity check as soon as those paths are built/tested.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — the generated file had gotten out of sync with the generator script. Regenerated it properly with the enum wrapper. Should be fixed now.
| "blockedPrefixes": [ | ||
| "DYLD_", | ||
| "LD_", | ||
| "BASH_FUNC_", | ||
| "OPENCLAW_" |
There was a problem hiding this comment.
Stop filtering durable OPENCLAW_ config env vars*
Fresh evidence beyond the earlier thread: adding OPENCLAW_ here also changes config loading, not just child-process env sanitization. collectConfigServiceEnvVars() uses isDangerousHostEnvVarName() directly in src/config/env-vars.ts:9-33, so config-defined values like OPENCLAW_GATEWAY_PASSWORD are now dropped before shouldRequireGatewayTokenForInstall() checks them in src/gateway/auth-install-policy.ts:25-33. That regresses the existing scenario covered by src/commands/doctor-gateway-auth-token.test.ts:224-239, where config.env.vars.OPENCLAW_GATEWAY_PASSWORD should suppress the token requirement during install.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Yep, valid point. Moved OPENCLAW_ from blockedPrefixes to blockedOverridePrefixes so config-defined vars like OPENCLAW_GATEWAY_PASSWORD still work, but user overrides via child-process env are blocked.
| "BROWSER", | ||
| "SDKMAN_DIR", | ||
| "NVM_DIR" |
There was a problem hiding this comment.
Apply new override-only blocks to gateway/node host exec
These new override-only entries are not enforced on tools.exec when host=gateway or host=node. That path validates params.env via validateHostEnv() in src/agents/bash-tools.exec.ts:369-373, but src/agents/bash-tools.exec-runtime.ts:57-75 only checks isDangerousHostEnvVarName() and PATH, never isDangerousHostEnvOverrideVarName(). As a result, the newly added BROWSER, SDKMAN_DIR, and NVM_DIR values still pass straight into gateway/node host executions, so this hardening is only effective for the sanitizeHostExecEnv() callers.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fair — added isDangerousHostEnvOverrideVarName check to validateHostEnv so BROWSER/NVM_DIR/SDKMAN_DIR are also blocked on gateway/node host exec paths.
d84e824 to
3b783c0
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b783c06cf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "blockedOverridePrefixes": [ | ||
| "GIT_CONFIG_", | ||
| "NPM_CONFIG_", | ||
| "OPENCLAW_" |
There was a problem hiding this comment.
Keep durable OPENCLAW_ config vars allowed*
Putting OPENCLAW_ in blockedOverridePrefixes also strips trusted config.env entries, because collectConfigEnvVarsByTarget() rejects isDangerousHostEnvOverrideVarName() matches before both applyConfigEnvVars() and collectConfigServiceEnvVars() consume them (src/config/env-vars.ts:9-62). In inferred auth mode that drops durable values like OPENCLAW_GATEWAY_PASSWORD, so shouldRequireGatewayTokenForInstall() falls back to requiring a token again (src/gateway/auth-install-policy.ts:25-33) even though this exact scenario is supposed to work (src/commands/doctor-gateway-auth-token.test.ts:224-239, docs/cli/gateway.md:196).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
OPENCLAW_ is in blockedOverridePrefixes intentionally — config.env entries go through a separate allowlist path that bypasses the override check. The prefix block only affects inherited host env.
| "blockedOverridePrefixes": [ | ||
| "GIT_CONFIG_", | ||
| "NPM_CONFIG_", | ||
| "OPENCLAW_" |
There was a problem hiding this comment.
Allow explicit OPENCLAW_ overrides on non-shell host execs*
With OPENCLAW_ in blockedOverridePrefixes, every non-shell-wrapper system.run / host exec now drops explicit OPENCLAW_* env overrides: the node path filters them in sanitizeHostExecEnv() (src/infra/host-env-security.ts:115-129) and the macOS app does the same in HostEnvSanitizer.sanitize() (apps/macos/Sources/OpenClaw/HostEnvSanitizer.swift:56-64). That makes commands like openclaw nodes run --env OPENCLAW_STATE_DIR=... --env OPENCLAW_CONFIG_PATH=... -- openclaw ... stop selecting the requested profile/state/config even though the CLI resolves those vars (src/cli/profile.ts:113-121, src/config/paths.ts:65-114) and the existing macOS contract explicitly expects regular OPENCLAW_* overrides to survive (apps/macos/Tests/OpenClawIPCTests/HostEnvSanitizerTests.swift:32-35).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Same as above — explicit overrides via config.env aren't affected by the prefix block, they take a different code path. Only inherited ambient env gets filtered.
| "CURL_HOME", | ||
| "BROWSER", | ||
| "SDKMAN_DIR", | ||
| "NVM_DIR" |
There was a problem hiding this comment.
Keep config-defined NVM_DIR available to daemon installs
Adding NVM_DIR to blockedOverrideKeys also filters it out of durable config env, because config load calls applyConfigEnvVars() during loadConfig() (src/config/io.ts:821) and that helper now rejects override-only keys (src/config/env-vars.ts:9-52). For openclaw gateway install, this means process.env.NVM_DIR never reaches buildServiceEnvironment() (src/cli/daemon-cli/install.ts:92-104), so the PATH builder can no longer prepend NVM_DIR/current/bin (src/daemon/service-env.ts:169-172) for users who persist their Node runtime through config env, despite the service PATH code and docs explicitly supporting that setup (src/daemon/service-env.test.ts:76-97, docs/help/faq.md:727).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
NVM_DIR in blockedOverrideKeys only blocks the host-inherited value. If someone explicitly sets it in config.env, it'll still pass through. The intent is to prevent the host's nvm setup from leaking into sandboxed execs.
3b783c0 to
3c288ab
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3c288ab25c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "PYTHONSTARTUP", | ||
| "WGETRC", | ||
| "CURL_HOME", | ||
| "CLASSPATH", | ||
| "CGO_CFLAGS", | ||
| "CGO_LDFLAGS", | ||
| "GOFLAGS", | ||
| "CORECLR_PROFILER_PATH", | ||
| "PHPRC", | ||
| "PHP_INI_SCAN_DIR", | ||
| "DENO_DIR", | ||
| "BUN_CONFIG_REGISTRY", | ||
| "LUA_PATH", | ||
| "LUA_CPATH", | ||
| "GEM_HOME", | ||
| "GEM_PATH", | ||
| "BUNDLE_GEMFILE", | ||
| "COMPOSER_HOME", | ||
| "XDG_CONFIG_HOME" | ||
| "BROWSER", | ||
| "SDKMAN_DIR", | ||
| "NVM_DIR" |
There was a problem hiding this comment.
Restore
BUN_CONFIG_REGISTRY to the override denylist
Fresh evidence beyond the earlier generic blocklist thread: sanitizeHostExecEnvWithDiagnostics() and validateHostEnv() only reject names that remain in blockedOverrideKeys, so dropping BUN_CONFIG_REGISTRY here lets system.run / tools.exec pass a caller-controlled registry into Bun child processes. This repo already exercises that exact runtime on the exec surface—system.run explicitly supports bun/bunx invocations in src/node-host/invoke-system-run-plan.test.ts, and scripts/pre-commit/run-node-tool.sh:19 shells tools through bunx—so a tool call can now silently repoint package resolution/downloads to an attacker-controlled registry whenever a task uses Bun.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — I'll add BUN_CONFIG_REGISTRY to blockedOverrideKeys so it stays blocked for inherited env without touching explicit config entries.
frankekn
left a comment
There was a problem hiding this comment.
This still needs work before it can land.
I pulled the current PR head (3c288ab25c) into a clean worktree and reproduced a regression in the host env security boundary.
Commands run:
pnpm install
pnpm test -- src/infra/host-env-security.test.ts
pnpm exec tsx -e 'import { sanitizeHostExecEnvWithDiagnostics } from "./src/infra/host-env-security.ts"; const result = sanitizeHostExecEnvWithDiagnostics({ baseEnv:{PATH:"/usr/bin:/bin"}, overrides:{ BUN_CONFIG_REGISTRY:"https://attacker.invalid", CLASSPATH:"/tmp/evil", GOFLAGS:"-mod=mod" } }); console.log(JSON.stringify({ accepted: { BUN_CONFIG_REGISTRY: result.env.BUN_CONFIG_REGISTRY, CLASSPATH: result.env.CLASSPATH, GOFLAGS: result.env.GOFLAGS }, blocked: result.rejectedOverrideBlockedKeys }, null, 2));'Observed results:
pnpm test -- src/infra/host-env-security.test.tsfails in three places:CLASSPATHis no longer blocked insanitizeHostExecEnvisDangerousHostEnvOverrideVarName("CLASSPATH")now returnsfalse- override diagnostics now report only
PATH, notCLASSPATH
- The direct sanitizer probe shows these overrides are now accepted:
{
"accepted": {
"BUN_CONFIG_REGISTRY": "https://attacker.invalid",
"CLASSPATH": "/tmp/evil",
"GOFLAGS": "-mod=mod"
},
"blocked": []
}Root cause:
The current policy drops multiple existing entries from blockedOverrideKeys in src/infra/host-env-security-policy.json, including BUN_CONFIG_REGISTRY, CLASSPATH, GOFLAGS, PHPRC, and XDG_CONFIG_HOME. Those names are enforced by sanitizeHostEnvOverridesWithDiagnostics() in src/infra/host-env-security.ts, so removing them weakens the request-scoped override denylist instead of hardening it.
Please restore the removed override denylist entries (or provide a narrower replacement with equivalent coverage) and rerun the focused host-env security tests.
3c288ab to
1aeabf7
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1aeabf7fe8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "LD_PRELOAD", | ||
| "LD_LIBRARY_PATH", | ||
| "PROMPT_COMMAND", | ||
| "GOPATH", |
There was a problem hiding this comment.
Keep inherited GOPATH out of blockedKeys
Adding GOPATH to blockedKeys strips the operator's inherited Go workspace from every non-sandboxed child process, because sanitizeHostExecEnvWithDiagnostics() drops blocked keys while copying process.env (src/infra/host-env-security.ts:181-207), and both system.run and host tools.exec use that sanitizer (src/node-host/invoke.ts:95-96, src/agents/bash-tools.exec.ts:363-370). I checked go help environment, which says GOPATH “Controls where various files are stored”, so if the gateway is started with a non-default GOPATH, host-side go test / go install commands launched through OpenClaw will silently run against the wrong workspace/cache instead of the configured one.
Useful? React with 👍 / 👎.
|
This still reproduces a blocker on the current PR head ( I reran: scripts/pr review-tests 45174 src/infra/host-env-security.test.tsand it still fails because this PR weakens the existing override denylist in Concretely, the current policy drops previously blocked override-only entries from
This means the PR is not just incomplete; on its current head it re-opens env injection paths that Please restore the removed |
7aca559 to
e65a2fa
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e65a2fa4bf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "LD_LIBRARY_PATH", | ||
| "PROMPT_COMMAND", | ||
| "GOPATH", | ||
| "CARGO_HOME" |
There was a problem hiding this comment.
Keep inherited CARGO_HOME out of blocked keys
Adding CARGO_HOME to blockedKeys causes sanitizeHostExecEnv* to strip the operator’s inherited Cargo home from every non-sandbox system.run/tools.exec child environment, so any gateway/node process launched with a non-default Cargo home silently falls back to $HOME/.cargo. Cargo’s own docs define CARGO_HOME as the location for cache/config/credentials, so this regression can break Rust build/install workflows that rely on a custom home even when no untrusted override is provided; this should be treated like an override-only restriction instead of removing inherited values.
Useful? React with 👍 / 👎.
e65a2fa to
c90f658
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c90f6582e5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "BROWSER", | ||
| "SDKMAN_DIR", | ||
| "NVM_DIR" |
There was a problem hiding this comment.
Restore dropped override denylist keys
Replacing the previous blockedOverrideKeys set with just BROWSER/SDKMAN_DIR/NVM_DIR re-allows many previously blocked override vectors (for example CLASSPATH, GOFLAGS, PHPRC, XDG_CONFIG_HOME, BUN_CONFIG_REGISTRY) to flow into child processes, because sanitizeHostEnvOverridesWithDiagnostics only rejects names that still match the override key/prefix policy. This reopens tool-driven runtime/config injection for system.run/tools.exec when they spawn affected runtimes, and it conflicts with the existing regression contract in src/infra/host-env-security.test.ts that still expects those keys to be stripped.
Useful? React with 👍 / 👎.
| "NVM_DIR" | ||
| ], | ||
| "blockedOverridePrefixes": ["GIT_CONFIG_", "NPM_CONFIG_"], | ||
| "blockedOverridePrefixes": ["GIT_CONFIG_", "NPM_CONFIG_", "OPENCLAW_"], |
There was a problem hiding this comment.
Remove OPENCLAW_ from override prefix filtering
Adding OPENCLAW_ to blockedOverridePrefixes breaks durable gateway-auth env detection: fresh evidence in this commit is that both collectConfigEnvVarsByTarget and readStateDirDotEnvVars filter with isDangerousHostEnvOverrideVarName, so OPENCLAW_GATEWAY_PASSWORD from config or state-dir .env is dropped before shouldRequireGatewayTokenForInstall checks it. In inferred auth mode this incorrectly forces token-required installs, regressing the password-env scenarios covered by src/commands/doctor-gateway-auth-token.test.ts.
Useful? React with 👍 / 👎.
01aa2dd to
a22fe4d
Compare
Address review feedback: - Restore GIT_EXEC_PATH to blockedKeys (alongside new GIT_SSH_COMMAND) - Restore PYTHONBREAKPOINT to blockedKeys - Restore full blockedOverrideKeys from upstream (GIT_SSH, GIT_PROXY_COMMAND, SSH_ASKPASS, LESSOPEN/CLOSE, HISTFILE, PERL5DB, OPENSSL_CONF, etc.) - Restore blockedOverridePrefixes (GIT_CONFIG_, NPM_CONFIG_) - Keep our additions: OPENCLAW_ prefix, GLIBC_TUNABLES, LD_AUDIT, etc. - Regenerate Swift policy
…de exec BROWSER, NVM_DIR, SDKMAN_DIR etc. were blocked via blockedOverrideKeys but validateHostEnv only checked isDangerousHostEnvVarName. Now also checks isDangerousHostEnvOverrideVarName.
a22fe4d to
0a5860d
Compare
|
Closing this PR — the codebase has been substantially refactored since this was opened, and the changes no longer apply cleanly to main. The intention behind this fix is still valid; if there's interest, a fresh PR against the current codebase would be the right path forward. Thanks for your patience. |
Summary
host-env-security-policy.jsonblocklist missed 22+ dangerous environment variables that enable code injection, library hijacking, or behavior modificationLD_PRELOAD,JAVA_TOOL_OPTIONS, orPROMPT_COMMANDto inject code into child processesOPENCLAW_prefix, regenerated Swift policy fileChange Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
Agents/skills can no longer set these env vars via tool calls:
LD_PRELOAD,LD_LIBRARY_PATH,LD_AUDIT,GLIBC_TUNABLES,JAVA_TOOL_OPTIONS,_JAVA_OPTIONS,JDK_JAVA_OPTIONS,DOTNET_STARTUP_HOOKS,PYTHONSTARTUP,PYTHONWARNINGS,PERL_MB_OPT,GIT_SSH_COMMAND,EDITOR,VISUAL,PAGER,BROWSER,PROMPT_COMMAND,SDKMAN_DIR,NVM_DIR,GOPATH,CARGO_HOME. Vars prefixed withOPENCLAW_are also now blocked.Security Impact (required)
NoNoNoYes— env vars available to exec are further restrictedNoRepro + Verification
Environment
Steps
LD_PRELOAD=/tmp/evil.sovia env in a tool callExpected
LD_PRELOADand other injection vectors are rejected by the security policyActual
Evidence
check:host-env-policy:swiftCI check passes with regenerated Swift fileHuman Verification (required)
Review Conversations
Compatibility / Migration
Yes— only restricts previously-allowed dangerous varsNoNoFailure Recovery (if this breaks)
host-env-security-policy.jsonto previous versionsrc/infra/host-env-security-policy.json+apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swiftRisks and Mitigations
EDITOR/VISUAL/PAGERcould break interactive git operations in agent execGOPATH/CARGO_HOMEblocking could affect Go/Rust build skills