You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
OpenClaw's host-env sanitizer (sanitizeHostExecEnv() in src/infra/host-env-security.ts) strips XDG_CONFIG_HOME and XDG_CONFIG_DIRS from the environment of subprocesses it spawns, even when the parent gateway inherited those vars normally from its own launch environment. This violates the freedesktop.org XDG Base Directory Specification, which is built on the contract that $XDG_CONFIG_HOME (and the other XDG Base Directory vars) propagate to child processes so the whole process tree resolves the same config root. XDG-aware tools spawned by OpenClaw (gogcli, mcporter, and any other CLI that follows the spec) silently fall back to ~/.config/<tool>/ instead of the operator-intended location. Verified against openclaw/openclaw at tag v2026.5.19.
Steps to reproduce
Start OpenClaw with XDG_CONFIG_HOME=/some/custom/dir set in the gateway's environment (e.g. via container entrypoint, systemd unit, or shell export before openclaw launches).
From an agent, invoke any XDG-aware tool. The simplest is a one-liner subprocess that prints what it sees:
sh -c 'echo "XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-<unset>}"'
Observe: the subprocess prints XDG_CONFIG_HOME=<unset> even though the parent gateway has it exported. Same result for XDG_CONFIG_DIRS. XDG_DATA_HOME, XDG_STATE_HOME, XDG_CACHE_HOME, XDG_RUNTIME_DIR and XDG_DATA_DIRS all pass through correctly.
Concrete failure mode in our deployment: gogcli's only path-control env var is XDG_CONFIG_HOME (see Dir() in internal/config/paths.go at gogcli v0.17.0). With the parent setting it to a persisted-state directory, gogcli's encrypted OAuth keyring should land there. Instead it lands at ~/.config/gogcli/keyring/, which on container restart is wiped, taking refresh tokens with it.
Expected behavior
The XDG Base Directory Spec defines five environment variables — XDG_CONFIG_HOME, XDG_CONFIG_DIRS, XDG_DATA_HOME, XDG_DATA_DIRS, XDG_STATE_HOME, XDG_CACHE_HOME, XDG_RUNTIME_DIR — and they are by-spec process-inheritable. The whole point of the spec is that a parent process can declare, "here is where config / data / state / cache / runtime files live for this process tree," and every descendant tool that follows the spec resolves to the same roots. This is why containers, sandboxes, dev environments, and per-tenant deployments routinely set these vars at the top of the process tree and rely on inheritance to do the rest.
A security policy that strips XDG Base Directory vars from inheritance (as distinct from blocking agent-supplied overrides at spawn time) breaks that contract. Override-blocking is sound — an agent should not be able to redirect a tool's config path per-call. Inheritance-stripping is different: the operator already controls the parent's environment by definition, and stripping the value adds no security boundary the operator didn't already cross when they set the var on the parent.
The same principle is already applied elsewhere in this policy: HOME, KUBECONFIG, AWS_CONFIG_FILE, GOOGLE_APPLICATION_CREDENTIALS, HTTPS_PROXY, etc. are all in blockedOverrideOnlyKeysandallowedInheritedOverrideOnlyKeys — blocked as agent-supplied overrides, allowed through as inherited values. XDG Base Directory vars belong in exactly the same bucket and for exactly the same reason.
Actual behavior
sanitizeHostExecEnvWithDiagnostics() in src/infra/host-env-security.ts iterates the parent baseEnv and skips any key matching isDangerousHostInheritedEnvVarName(key), which consults the derived blockedInheritedKeys set. Two XDG Base Directory vars (XDG_CONFIG_HOME, XDG_CONFIG_DIRS) end up in that set; the other XDG vars do not.
Root cause traced through source:
src/infra/host-env-security-policy.json lists XDG_CONFIG_HOME and XDG_CONFIG_DIRS in blockedOverrideOnlyKeys. Neither is in allowedInheritedOverrideOnlyKeys.
Because the two XDG vars are in blockedOverrideOnlyKeys and not in the allow-list, they fall through into blockedInheritedKeys.
sanitizeHostExecEnvWithDiagnostics() then drops them from the inherited env going to subprocesses.
The intent of adding XDG vars to blockedOverrideOnlyKeys was almost certainly to harden override handling — they were introduced in PR #51207 ("Exec: harden host env override enforcement and fail closed"). Stripping them from inheritance appears to be an unintended derivation side-effect, not a deliberate policy decision.
Audit of every XDG Base Directory env var's policy membership at OpenClaw v2026.5.19, inheritance effect, and concrete downstream impact on gogcli v0.17.0 and mcporter v0.11.1:
XDG var
blockedOverrideOnlyKeys
allowedInheritedOverrideOnlyKeys
Effect on inheritance
Used by gogcli?
Used by mcporter?
Net downstream impact
XDG_CONFIG_HOME
✅ yes
❌ no
stripped
✅ config + keyring root (paths.go) — the only path-control knob
The asymmetry confirms the policy treats two XDG vars one way and the other five another way, with no apparent justification for the split. The XDG spec treats them as a coherent set. Note also the partial-functionality artifact: mcporter's vault (data) works correctly because XDG_DATA_HOME inherits, but its config file (config) is broken because XDG_CONFIG_HOME is stripped — within a single tool, half the XDG surface works and half doesn't, depending only on which kind each file belongs to.
OpenClaw version
v2026.5.19
Operating system
Ubuntu 24.04
Install method
npm global (npm install -g openclaw@${OPENCLAW_VERSION} in a Docker image)
Model
Not applicable — process-spawn-layer bug, model-agnostic.
Provider / routing chain
Not applicable.
Additional provider/model setup details
Not applicable.
Logs, screenshots, and evidence
$ git show v2026.5.19:src/infra/host-env-security-policy.json | jq ' { XDG_CONFIG_HOME_in_blockedOverrideOnlyKeys: (.blockedOverrideOnlyKeys | index("XDG_CONFIG_HOME") != null), XDG_CONFIG_HOME_in_allowedInheritedOverrideOnlyKeys: (.allowedInheritedOverrideOnlyKeys | index("XDG_CONFIG_HOME") != null), XDG_DATA_HOME_anywhere: (.blockedOverrideOnlyKeys + .allowedInheritedOverrideOnlyKeys + .blockedEverywhereKeys | index("XDG_DATA_HOME") != null) }'
{
"XDG_CONFIG_HOME_in_blockedOverrideOnlyKeys": true,
"XDG_CONFIG_HOME_in_allowedInheritedOverrideOnlyKeys": false,
"XDG_DATA_HOME_anywhere": false
}
$ # Subprocess view: parent has it, child does not.
$ XDG_CONFIG_HOME=/some/custom/dir openclaw # start gateway
$ # then from an agent:
$ sh -c 'echo "XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-<unset>}"'
XDG_CONFIG_HOME=<unset>
Impact and severity
Affected: Any OpenClaw deployment that relies on XDG Base Directory inheritance to redirect XDG-aware subprocess tooling. This includes essentially every containerized / multi-tenant / per-instance-persisted-state deployment topology.
Severity: High. In our case the gogcli encrypted OAuth keyring lands at ~/.config/gogcli/keyring/, which is not under the container's persisted state directory and is lost on every container restart. Users have to re-auth via OAuth after every restart. Same failure shape applies to mcporter's mcporter.json (vault is XDG_DATA_HOME-keyed so it survives, but the config file is XDG_CONFIG_HOME-keyed and gets lost).
Frequency: Every subprocess invocation, deterministic.
Consequence: Silent loss of credentials and config across instance lifecycle events; no diagnostic at the OpenClaw layer (subprocess looks like it succeeded). The failure is invisible until a tool tries to read state that should have been persisted and isn't there.
Additional information
Affected versions / environment notes
Verified at v2026.5.19, latest stable at time of report. Bug present continuously since the policy commit c1da0ddd54 first shipped in v2026.5.4. The XDG_CONFIG_HOME entry in blockedOverrideOnlyKeys was introduced earlier in PR #51207.
The repro environment is a containerized Ubuntu 24.04 OpenClaw deployment. XDG Base Directory Spec is a Linux/freedesktop convention; the behavior is most consequential on Linux but the policy is platform-agnostic, so any environment that sets XDG vars (some macOS dev shells via Homebrew, Nix, etc.) is affected the same way.
Why XDG inheritance is fundamentally different from XDG overrides
The override-blocking property is sound and should be kept:
Override = an agent or skill supplies XDG_CONFIG_HOME=/attacker/path in the overrides arg when calling sanitizeHostExecEnv(). This is a real attack surface — the agent could redirect tool config writes to a directory it controls. ✅ block.
Inheritance = the operator set XDG_CONFIG_HOME=/some/dir on the parent openclaw process at launch, and the spawn-time env is being constructed from baseEnv = process.env. The value is already in scope of the OpenClaw process; the question is only whether to pass it down. The operator already crossed the trust boundary when they set the value at parent launch.
The XDG Base Directory Spec requires the inheritance side to work, by design. Most XDG-aware tools have no other path-control knob (gogcli is one example; there's no GOG_CONFIG_DIR), so without inheritance there is literally no way to redirect them in a deployment.
Other env vars in the policy that share this property — operator-set at parent launch, dangerous as agent override, safe as inherited value — are already in allowedInheritedOverrideOnlyKeys: HOME, KUBECONFIG, AWS_CONFIG_FILE, GOOGLE_APPLICATION_CREDENTIALS, HTTPS_PROXY, NODE_EXTRA_CA_CERTS, etc. XDG vars belong in the same group.
Proposed fix
Add all seven XDG Base Directory vars to allowedInheritedOverrideOnlyKeys — not just the two currently in blockedOverrideOnlyKeys. This encodes the XDG-spec inheritance contract once, explicitly, instead of relying on each future override-hardening decision to remember to re-assert it.
Allow-listing a var that's not currently in blockedOverrideOnlyKeys is a no-op today — the .filter() step has nothing to filter for it, so blockedInheritedKeys doesn't change. But if a future override-hardening PR adds (say) XDG_DATA_HOME to blockedOverrideOnlyKeys, then without the proactive allow-list entry, XDG_DATA_HOME silently drops out of inheritance the same way XDG_CONFIG_HOME does today. Allow-listing the full spec up front means the next override-hardening decision can be made in isolation, without having to re-derive the inheritance side-effect each time.
This matches the existing pattern in the policy: HOME, KUBECONFIG, AWS_CONFIG_FILE, GOOGLE_APPLICATION_CREDENTIALS, HTTPS_PROXY, NODE_EXTRA_CA_CERTS, etc. are all in allowedInheritedOverrideOnlyKeys regardless of whether they're in blockedOverrideOnlyKeys today — the allow-list asserts the inheritance contract as a property, not a reaction.
It's also more truthful to the XDG spec, which defines the seven vars as a coherent set, not as independent knobs.
Optional broader hardening (separate discussion)
The fix above closes the inheritance side of the contract. The maintainer may separately want to consider:
Adding the other XDG Base Directory vars to blockedOverrideOnlyKeys for symmetric override-blocking. Today XDG_DATA_HOME etc. can be set by agents at spawn time because they're absent from the policy entirely — the same "redirect tool data writes" attack that motivated blocking XDG_CONFIG_HOME as an override applies to data and state too. With the proposed fix above already in place, this becomes a single-list edit (blockedOverrideOnlyKeys) that doesn't have to think about inheritance — the inheritance contract is already declared.
Documenting in code comments which vars are operator-controlled (inheritance-safe) vs. agent-controlled (override risk) — the current policy structure has the right shape but the rationale is implicit.
These are independent of the main fix and can be punted to a follow-up.
Related references
Commit/PR that introduced the bug:
PR #51207 ("Exec: harden host env override handling across gateway and node") — merged 2026-03-20, commit 7abfff756d. This is where XDG_CONFIG_HOME and XDG_CONFIG_DIRS were first added to blockedOverrideOnlyKeys. The intent (per the PR title and commit message) was override hardening; the inheritance side effect appears to have been unintended.
Commit c1da0ddd54 ("fix(security): block workspace env from overriding Windows system root paths") — first stable release containing the current shape of the policy file (v2026.5.4).
#53628 — ${XDG_CONFIG_HOME} placeholder is not expanded when installing a skill via npx clawhub@latest install. Different layer (template expansion vs. env inheritance), but same root user expectation: "when I set XDG_CONFIG_HOME, every OpenClaw-spawned component should respect it." Affects Docker deployments.
#63069 — shell completion writes to wrong profile path when ZDOTDIR or XDG_CONFIG_HOME is set. Different code path (getShellProfilePath), same XDG-disrespect pattern. In-flight fix: PR #81298 ("fix(cli): honor ZDOTDIR/XDG_CONFIG_HOME in completion install").
#54415 — gateway install fails when XDG_RUNTIME_DIR is not set. Closed; cited here as evidence the codebase already has multiple XDG-handling gaps in different subsystems.
#80329 — "Allow per-call sandbox env injection from operator-trusted callers." General request for trusted-source env passing — overlaps with the policy boundary this issue touches.
Sibling-project precedent for the XDG-spec framing:
openclaw/mcporter#155 ("Honor XDG Base Directory Spec for config, vault, cache, and daemon paths") — closed as completed, shipped in mcporter v0.10.0. Same spec, same framing, same conclusion: XDG vars are the standard, operator-supplied path-control surface and tooling needs to honor them end-to-end. The mcporter issue closed the producer side (mcporter now writes to XDG paths); this OpenClaw issue closes the inheritance side (so the producer actually receives the operator's chosen path).
openclaw/mcporter#184 — interesting consequence of partial XDG support across the stack: after mcporter 0.10.x began honoring XDG_CONFIG_HOME, embedders (including OpenClaw's qmd-manager) that had been setting XDG vars for unrelated downstream tools accidentally redirected mcporter too. Reinforces that XDG inheritance needs to be consistent and intentional from the top of the process tree down — partial honoring causes silent regressions.
Related downstream tools that hit this:
openclaw/gogcli — confirmed broken at v0.17.0. Sole path-control env var is XDG_CONFIG_HOME (see Dir() in internal/config/paths.go). No GOG_CONFIG_DIR equivalent exists (separate feature request will be filed against openclaw/gogcli).
openclaw/mcporter — partially affected at v0.11.1: vault uses XDG_DATA_HOME (passes through, fine; see oauth-vault.ts and the data-kind branch in paths.ts), but mcporter.json resolution uses XDG_CONFIG_HOME and falls back to ~/.mcporter/ (see mcporterConfigCandidates() in paths.ts). MCPORTER_CONFIG env var is a tool-specific escape hatch but operators shouldn't have to learn one per tool.
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
OpenClaw's host-env sanitizer (
sanitizeHostExecEnv()insrc/infra/host-env-security.ts) stripsXDG_CONFIG_HOMEandXDG_CONFIG_DIRSfrom the environment of subprocesses it spawns, even when the parent gateway inherited those vars normally from its own launch environment. This violates the freedesktop.org XDG Base Directory Specification, which is built on the contract that$XDG_CONFIG_HOME(and the other XDG Base Directory vars) propagate to child processes so the whole process tree resolves the same config root. XDG-aware tools spawned by OpenClaw (gogcli, mcporter, and any other CLI that follows the spec) silently fall back to~/.config/<tool>/instead of the operator-intended location. Verified againstopenclaw/openclawat tagv2026.5.19.Steps to reproduce
XDG_CONFIG_HOME=/some/custom/dirset in the gateway's environment (e.g. via container entrypoint, systemd unit, or shell export beforeopenclawlaunches).XDG_CONFIG_HOME=<unset>even though the parent gateway has it exported. Same result forXDG_CONFIG_DIRS.XDG_DATA_HOME,XDG_STATE_HOME,XDG_CACHE_HOME,XDG_RUNTIME_DIRandXDG_DATA_DIRSall pass through correctly.Concrete failure mode in our deployment: gogcli's only path-control env var is
XDG_CONFIG_HOME(seeDir()ininternal/config/paths.goat gogcliv0.17.0). With the parent setting it to a persisted-state directory, gogcli's encrypted OAuth keyring should land there. Instead it lands at~/.config/gogcli/keyring/, which on container restart is wiped, taking refresh tokens with it.Expected behavior
The XDG Base Directory Spec defines five environment variables —
XDG_CONFIG_HOME,XDG_CONFIG_DIRS,XDG_DATA_HOME,XDG_DATA_DIRS,XDG_STATE_HOME,XDG_CACHE_HOME,XDG_RUNTIME_DIR— and they are by-spec process-inheritable. The whole point of the spec is that a parent process can declare, "here is where config / data / state / cache / runtime files live for this process tree," and every descendant tool that follows the spec resolves to the same roots. This is why containers, sandboxes, dev environments, and per-tenant deployments routinely set these vars at the top of the process tree and rely on inheritance to do the rest.A security policy that strips XDG Base Directory vars from inheritance (as distinct from blocking agent-supplied overrides at spawn time) breaks that contract. Override-blocking is sound — an agent should not be able to redirect a tool's config path per-call. Inheritance-stripping is different: the operator already controls the parent's environment by definition, and stripping the value adds no security boundary the operator didn't already cross when they set the var on the parent.
The same principle is already applied elsewhere in this policy:
HOME,KUBECONFIG,AWS_CONFIG_FILE,GOOGLE_APPLICATION_CREDENTIALS,HTTPS_PROXY, etc. are all inblockedOverrideOnlyKeysandallowedInheritedOverrideOnlyKeys— blocked as agent-supplied overrides, allowed through as inherited values. XDG Base Directory vars belong in exactly the same bucket and for exactly the same reason.Actual behavior
sanitizeHostExecEnvWithDiagnostics()insrc/infra/host-env-security.tsiterates the parentbaseEnvand skips any key matchingisDangerousHostInheritedEnvVarName(key), which consults the derivedblockedInheritedKeysset. Two XDG Base Directory vars (XDG_CONFIG_HOME,XDG_CONFIG_DIRS) end up in that set; the other XDG vars do not.Root cause traced through source:
src/infra/host-env-security-policy.jsonlistsXDG_CONFIG_HOMEandXDG_CONFIG_DIRSinblockedOverrideOnlyKeys. Neither is inallowedInheritedOverrideOnlyKeys.derivePolicyArraysinsrc/infra/host-env-security-policy.jscomputes:blockedOverrideOnlyKeysand not in the allow-list, they fall through intoblockedInheritedKeys.sanitizeHostExecEnvWithDiagnostics()then drops them from the inherited env going to subprocesses.The intent of adding XDG vars to
blockedOverrideOnlyKeyswas almost certainly to harden override handling — they were introduced in PR #51207 ("Exec: harden host env override enforcement and fail closed"). Stripping them from inheritance appears to be an unintended derivation side-effect, not a deliberate policy decision.Audit of every XDG Base Directory env var's policy membership at OpenClaw
v2026.5.19, inheritance effect, and concrete downstream impact on gogcliv0.17.0and mcporterv0.11.1:blockedOverrideOnlyKeysallowedInheritedOverrideOnlyKeysXDG_CONFIG_HOMEpaths.go) — the only path-control knobmcporter.jsonserver list (paths.tsconfigkind)~/.config/gogcli/, lost on restart); mcporter.json falls back to~/.mcporter/XDG_CONFIG_DIRSXDG_DATA_HOMEoauth-vault.ts→paths.tsdatakind)XDG_DATA_DIRSXDG_STATE_HOMEpaths.tsstatekind)XDG_CACHE_HOMEpaths.tscachekind)XDG_RUNTIME_DIRThe asymmetry confirms the policy treats two XDG vars one way and the other five another way, with no apparent justification for the split. The XDG spec treats them as a coherent set. Note also the partial-functionality artifact: mcporter's vault (data) works correctly because
XDG_DATA_HOMEinherits, but its config file (config) is broken becauseXDG_CONFIG_HOMEis stripped — within a single tool, half the XDG surface works and half doesn't, depending only on which kind each file belongs to.OpenClaw version
v2026.5.19
Operating system
Ubuntu 24.04
Install method
npm global (
npm install -g openclaw@${OPENCLAW_VERSION}in a Docker image)Model
Not applicable — process-spawn-layer bug, model-agnostic.
Provider / routing chain
Not applicable.
Additional provider/model setup details
Not applicable.
Logs, screenshots, and evidence
Impact and severity
~/.config/gogcli/keyring/, which is not under the container's persisted state directory and is lost on every container restart. Users have to re-auth via OAuth after every restart. Same failure shape applies to mcporter'smcporter.json(vault isXDG_DATA_HOME-keyed so it survives, but the config file isXDG_CONFIG_HOME-keyed and gets lost).Additional information
Affected versions / environment notes
Verified at
v2026.5.19, latest stable at time of report. Bug present continuously since the policy commitc1da0ddd54first shipped inv2026.5.4. TheXDG_CONFIG_HOMEentry inblockedOverrideOnlyKeyswas introduced earlier in PR #51207.The repro environment is a containerized Ubuntu 24.04 OpenClaw deployment. XDG Base Directory Spec is a Linux/freedesktop convention; the behavior is most consequential on Linux but the policy is platform-agnostic, so any environment that sets XDG vars (some macOS dev shells via Homebrew, Nix, etc.) is affected the same way.
Why XDG inheritance is fundamentally different from XDG overrides
The override-blocking property is sound and should be kept:
XDG_CONFIG_HOME=/attacker/pathin theoverridesarg when callingsanitizeHostExecEnv(). This is a real attack surface — the agent could redirect tool config writes to a directory it controls. ✅ block.XDG_CONFIG_HOME=/some/diron the parentopenclawprocess at launch, and the spawn-time env is being constructed frombaseEnv = process.env. The value is already in scope of the OpenClaw process; the question is only whether to pass it down. The operator already crossed the trust boundary when they set the value at parent launch.The XDG Base Directory Spec requires the inheritance side to work, by design. Most XDG-aware tools have no other path-control knob (gogcli is one example; there's no
GOG_CONFIG_DIR), so without inheritance there is literally no way to redirect them in a deployment.Other env vars in the policy that share this property — operator-set at parent launch, dangerous as agent override, safe as inherited value — are already in
allowedInheritedOverrideOnlyKeys:HOME,KUBECONFIG,AWS_CONFIG_FILE,GOOGLE_APPLICATION_CREDENTIALS,HTTPS_PROXY,NODE_EXTRA_CA_CERTS, etc. XDG vars belong in the same group.Proposed fix
Add all seven XDG Base Directory vars to
allowedInheritedOverrideOnlyKeys— not just the two currently inblockedOverrideOnlyKeys. This encodes the XDG-spec inheritance contract once, explicitly, instead of relying on each future override-hardening decision to remember to re-assert it.Minimal diff:
"allowedInheritedOverrideOnlyKeys": [ ... "SYSTEMROOT", "WINDIR", + "XDG_CACHE_HOME", + "XDG_CONFIG_DIRS", + "XDG_CONFIG_HOME", + "XDG_DATA_DIRS", + "XDG_DATA_HOME", + "XDG_RUNTIME_DIR", + "XDG_STATE_HOME", "ZDOTDIR" ],No code changes outside the policy JSON required — the derivation logic in
host-env-security-policy.jspicks the change up automatically.Why all seven, not just the two currently broken. Recall the derivation from
host-env-security-policy.js(shown earlier under "Actual behavior"):Allow-listing a var that's not currently in
blockedOverrideOnlyKeysis a no-op today — the.filter()step has nothing to filter for it, soblockedInheritedKeysdoesn't change. But if a future override-hardening PR adds (say)XDG_DATA_HOMEtoblockedOverrideOnlyKeys, then without the proactive allow-list entry,XDG_DATA_HOMEsilently drops out of inheritance the same wayXDG_CONFIG_HOMEdoes today. Allow-listing the full spec up front means the next override-hardening decision can be made in isolation, without having to re-derive the inheritance side-effect each time.This matches the existing pattern in the policy:
HOME,KUBECONFIG,AWS_CONFIG_FILE,GOOGLE_APPLICATION_CREDENTIALS,HTTPS_PROXY,NODE_EXTRA_CA_CERTS, etc. are all inallowedInheritedOverrideOnlyKeysregardless of whether they're inblockedOverrideOnlyKeystoday — the allow-list asserts the inheritance contract as a property, not a reaction.It's also more truthful to the XDG spec, which defines the seven vars as a coherent set, not as independent knobs.
Optional broader hardening (separate discussion)
The fix above closes the inheritance side of the contract. The maintainer may separately want to consider:
blockedOverrideOnlyKeysfor symmetric override-blocking. TodayXDG_DATA_HOMEetc. can be set by agents at spawn time because they're absent from the policy entirely — the same "redirect tool data writes" attack that motivated blockingXDG_CONFIG_HOMEas an override applies to data and state too. With the proposed fix above already in place, this becomes a single-list edit (blockedOverrideOnlyKeys) that doesn't have to think about inheritance — the inheritance contract is already declared.These are independent of the main fix and can be punted to a follow-up.
Related references
Commit/PR that introduced the bug:
7abfff756d. This is whereXDG_CONFIG_HOMEandXDG_CONFIG_DIRSwere first added toblockedOverrideOnlyKeys. The intent (per the PR title and commit message) was override hardening; the inheritance side effect appears to have been unintended.c1da0ddd54("fix(security): block workspace env from overriding Windows system root paths") — first stable release containing the current shape of the policy file (v2026.5.4).Related OpenClaw issues:
qmd-managerleaks its ownXDG_CONFIG_HOMEto all child spawns, including mcporter, breaking thememory.backend: qmdpath. Same XDG-Spec-violating shape, opposite symptom: ours is "operator-set XDG vars don't reach the child"; qmd-manager leaks XDG_CONFIG_HOME / XDG_CACHE_HOME to all child spawns, breaking mcporter ≥ 0.10 integration #79847 is "qmd-internal XDG vars do reach a child that shouldn't see them." Together, the two issues frame the underlying root cause: OpenClaw does not have a coherent XDG inheritance model. Fixing qmd-manager leaks XDG_CONFIG_HOME / XDG_CACHE_HOME to all child spawns, breaking mcporter ≥ 0.10 integration #79847 (per-spawn env construction in qmd-manager) and the issue filed here (policy allow-listing for inheritance) is the complete fix; either alone is partial. Closed-as-not-merged PR #79981 ("fix(memory/qmd): scope XDG env vars to qmd spawns; use clean env for mcporter") was an attempt at qmd-manager leaks XDG_CONFIG_HOME / XDG_CACHE_HOME to all child spawns, breaking mcporter ≥ 0.10 integration #79847.${XDG_CONFIG_HOME}placeholder is not expanded when installing a skill vianpx clawhub@latest install. Different layer (template expansion vs. env inheritance), but same root user expectation: "when I setXDG_CONFIG_HOME, every OpenClaw-spawned component should respect it." Affects Docker deployments.ZDOTDIRorXDG_CONFIG_HOMEis set. Different code path (getShellProfilePath), same XDG-disrespect pattern. In-flight fix: PR #81298 ("fix(cli): honor ZDOTDIR/XDG_CONFIG_HOME in completion install").XDG_RUNTIME_DIRis not set. Closed; cited here as evidence the codebase already has multiple XDG-handling gaps in different subsystems.Sibling-project precedent for the XDG-spec framing:
openclaw/mcporter#155("Honor XDG Base Directory Spec for config, vault, cache, and daemon paths") — closed as completed, shipped in mcporterv0.10.0. Same spec, same framing, same conclusion: XDG vars are the standard, operator-supplied path-control surface and tooling needs to honor them end-to-end. The mcporter issue closed the producer side (mcporter now writes to XDG paths); this OpenClaw issue closes the inheritance side (so the producer actually receives the operator's chosen path).openclaw/mcporter#184— interesting consequence of partial XDG support across the stack: after mcporter0.10.xbegan honoringXDG_CONFIG_HOME, embedders (including OpenClaw's qmd-manager) that had been setting XDG vars for unrelated downstream tools accidentally redirected mcporter too. Reinforces that XDG inheritance needs to be consistent and intentional from the top of the process tree down — partial honoring causes silent regressions.Related downstream tools that hit this:
openclaw/gogcli— confirmed broken atv0.17.0. Sole path-control env var isXDG_CONFIG_HOME(seeDir()ininternal/config/paths.go). NoGOG_CONFIG_DIRequivalent exists (separate feature request will be filed againstopenclaw/gogcli).openclaw/mcporter— partially affected atv0.11.1: vault usesXDG_DATA_HOME(passes through, fine; seeoauth-vault.tsand thedata-kind branch inpaths.ts), butmcporter.jsonresolution usesXDG_CONFIG_HOMEand falls back to~/.mcporter/(seemcporterConfigCandidates()inpaths.ts).MCPORTER_CONFIGenv var is a tool-specific escape hatch but operators shouldn't have to learn one per tool.XDG Base Directory Specification: