mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:10:51 +00:00
Harden update environment path resolution (#77470)
* Harden update environment path resolution * docs(changelog): credit windows update env path hardening Adds the user-facing Unreleased Fixes entry for the workspace LOCALAPPDATA blocklist + portable Git path-prepend hardening change in this PR.
This commit is contained in:
@@ -210,6 +210,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/Windows: validate `SystemRoot`/`WINDIR` env values through the Windows install-root validator and add them to the dangerous-host-env policy when resolving `icacls.exe`/`whoami.exe` for `openclaw security audit`, so workspace `.env` overrides and bare command names cannot redirect Windows ACL helpers to attacker-controlled binaries. (#74458) Thanks @mmaps.
|
||||
- Security/Windows: pin Windows registry-probe `reg.exe` resolution to the canonical Windows install root in install-root probing, so `SystemRoot`/`WINDIR` env overrides cannot redirect registry queries during Windows host detection. (#74454) Thanks @mmaps.
|
||||
- QQBot: preserve the framework command authorization decision when converting framework command contexts into engine slash command contexts, so downstream slash handlers see `commandAuthorized` matching the channel's resolved `isAuthorizedSender` instead of a hardcoded `true`. (#77453) Thanks @drobison00.
|
||||
- Security/Windows: block `LOCALAPPDATA` from workspace `.env` and resolve Windows update-flow portable Git path prepends from the trusted process-local `LOCALAPPDATA` only, so workspace-supplied values cannot redirect `git` discovery during `openclaw update`. (#77470) Thanks @drobison00.
|
||||
|
||||
## 2026.5.3-1
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ const BUNDLED_TRUST_ROOT_ENV_KEYS = BUNDLED_TRUST_ROOT_ENV_LINES.map(
|
||||
const WINDOWS_SHELL_TRUST_ROOT_ENV_KEYS = [
|
||||
"ComSpec",
|
||||
"COMSPEC",
|
||||
"LocalAppData",
|
||||
"LOCALAPPDATA",
|
||||
"ProgramFiles",
|
||||
"PROGRAMFILES",
|
||||
"ProgramW6432",
|
||||
@@ -338,6 +340,8 @@ describe("loadDotEnv", () => {
|
||||
[
|
||||
"ComSpec=.\\evil-comspec",
|
||||
"COMSPEC=.\\evil-comspec-upper",
|
||||
"LocalAppData=.\\evil-local-app-data",
|
||||
"LOCALAPPDATA=.\\evil-local-app-data-upper",
|
||||
"ProgramFiles=.\\evil-pfiles",
|
||||
"PROGRAMFILES=.\\evil-pfiles-upper",
|
||||
"ProgramW6432=.\\evil-pw6432",
|
||||
@@ -715,6 +719,7 @@ describe("workspace .env blocklist completeness", () => {
|
||||
"HOMEBREW_BREW_FILE",
|
||||
"HOMEBREW_PREFIX",
|
||||
"IRC_HOST",
|
||||
"LOCALAPPDATA",
|
||||
"MATTERMOST_URL",
|
||||
"MATRIX_HOMESERVER",
|
||||
"MINIMAX_API_HOST",
|
||||
|
||||
@@ -29,6 +29,7 @@ const BLOCKED_WORKSPACE_DOTENV_KEYS = new Set([
|
||||
"HOMEBREW_BREW_FILE",
|
||||
"HOMEBREW_PREFIX",
|
||||
"IRC_HOST",
|
||||
"LOCALAPPDATA",
|
||||
"MATTERMOST_URL",
|
||||
"MATRIX_HOMESERVER",
|
||||
"MINIMAX_API_HOST",
|
||||
|
||||
@@ -150,6 +150,50 @@ describe("update global helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves portable Git paths from process-local app data only", async () => {
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
try {
|
||||
await withTempDir({ prefix: "openclaw-update-portable-git-" }, async (base) => {
|
||||
envSnapshot = captureEnv(["LOCALAPPDATA"]);
|
||||
const injectedLocalAppData = path.join(base, "injected-local-app-data");
|
||||
const trustedLocalAppData = path.join(base, "trusted-local-app-data");
|
||||
const injectedGitDir = path.join(
|
||||
injectedLocalAppData,
|
||||
"OpenClaw",
|
||||
"deps",
|
||||
"portable-git",
|
||||
"cmd",
|
||||
);
|
||||
const trustedGitDir = path.join(
|
||||
trustedLocalAppData,
|
||||
"OpenClaw",
|
||||
"deps",
|
||||
"portable-git",
|
||||
"cmd",
|
||||
);
|
||||
await fs.mkdir(injectedGitDir, { recursive: true });
|
||||
await fs.mkdir(trustedGitDir, { recursive: true });
|
||||
|
||||
delete process.env.LOCALAPPDATA;
|
||||
const injectedOnlyEnv = await createGlobalInstallEnv({
|
||||
LOCALAPPDATA: injectedLocalAppData,
|
||||
PATH: "base-bin",
|
||||
});
|
||||
expect(injectedOnlyEnv?.PATH).not.toContain(injectedGitDir);
|
||||
|
||||
process.env.LOCALAPPDATA = trustedLocalAppData;
|
||||
const trustedEnv = await createGlobalInstallEnv({
|
||||
LOCALAPPDATA: injectedLocalAppData,
|
||||
PATH: "base-bin",
|
||||
});
|
||||
expect(trustedEnv?.PATH).toContain(trustedGitDir);
|
||||
expect(trustedEnv?.PATH).not.toContain(injectedGitDir);
|
||||
});
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("classifies main and raw install specs separately from registry selectors", () => {
|
||||
expect(isMainPackageTarget("main")).toBe(true);
|
||||
expect(isMainPackageTarget(" MAIN ")).toBe(true);
|
||||
|
||||
@@ -274,13 +274,11 @@ export function canResolveRegistryVersionForPackageTarget(value: string): boolea
|
||||
return !isMainPackageTarget(trimmed) && !isExplicitPackageInstallSpec(trimmed);
|
||||
}
|
||||
|
||||
async function resolvePortableGitPathPrepend(
|
||||
env: NodeJS.ProcessEnv | undefined,
|
||||
): Promise<string[]> {
|
||||
async function resolvePortableGitPathPrepend(): Promise<string[]> {
|
||||
if (process.platform !== "win32") {
|
||||
return [];
|
||||
}
|
||||
const localAppData = env?.LOCALAPPDATA?.trim() || process.env.LOCALAPPDATA?.trim();
|
||||
const localAppData = process.env.LOCALAPPDATA?.trim();
|
||||
if (!localAppData) {
|
||||
return [];
|
||||
}
|
||||
@@ -341,7 +339,7 @@ export function resolveGlobalInstallSpec(params: {
|
||||
export async function createGlobalInstallEnv(
|
||||
env?: NodeJS.ProcessEnv,
|
||||
): Promise<NodeJS.ProcessEnv | undefined> {
|
||||
const pathPrepend = await resolvePortableGitPathPrepend(env);
|
||||
const pathPrepend = await resolvePortableGitPathPrepend();
|
||||
const sourceEnv = env ?? process.env;
|
||||
const hasCorepackDownloadPromptSetting = Boolean(
|
||||
sourceEnv.COREPACK_ENABLE_DOWNLOAD_PROMPT?.trim(),
|
||||
|
||||
Reference in New Issue
Block a user