Exec: mark child command env with OPENCLAW_CLI (#41411)

This commit is contained in:
Vincent Koc
2026-03-09 16:14:08 -07:00
committed by GitHub
parent 4790e40ac6
commit b48291e01e
8 changed files with 41 additions and 5 deletions

View File

@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { OPENCLAW_CLI_ENV_VALUE } from "../infra/openclaw-exec-env.js";
import { buildSandboxCreateArgs } from "./sandbox/docker.js";
import type { SandboxDockerConfig } from "./sandbox/types.js";
@@ -113,7 +114,14 @@ describe("buildSandboxCreateArgs", () => {
"1.5",
]),
);
expect(args).toEqual(expect.arrayContaining(["--env", "LANG=C.UTF-8"]));
expect(args).toEqual(
expect.arrayContaining([
"--env",
"LANG=C.UTF-8",
"--env",
`OPENCLAW_CLI=${OPENCLAW_CLI_ENV_VALUE}`,
]),
);
const ulimitValues: string[] = [];
for (let i = 0; i < args.length; i += 1) {

View File

@@ -162,6 +162,7 @@ export function execDockerRaw(
}
import { formatCliCommand } from "../../cli/command-format.js";
import { markOpenClawExecEnv } from "../../infra/openclaw-exec-env.js";
import { defaultRuntime } from "../../runtime.js";
import { computeSandboxConfigHash } from "./config-hash.js";
import { DEFAULT_SANDBOX_IMAGE } from "./constants.js";
@@ -365,7 +366,7 @@ export function buildSandboxCreateArgs(params: {
if (params.cfg.user) {
args.push("--user", params.cfg.user);
}
const envSanitization = sanitizeEnvVars(params.cfg.env ?? {});
const envSanitization = sanitizeEnvVars(markOpenClawExecEnv(params.cfg.env ?? {}));
if (envSanitization.blocked.length > 0) {
log.warn(`Blocked sensitive environment variables: ${envSanitization.blocked.join(", ")}`);
}

View File

@@ -9,6 +9,7 @@ import { shouldSkipRespawnForArgv } from "./cli/respawn-policy.js";
import { normalizeWindowsArgv } from "./cli/windows-argv.js";
import { isTruthyEnvValue, normalizeEnv } from "./infra/env.js";
import { isMainModule } from "./infra/is-main.js";
import { ensureOpenClawExecMarkerOnProcess } from "./infra/openclaw-exec-env.js";
import { installProcessWarningFilter } from "./infra/warning-filter.js";
import { attachChildProcessBridge } from "./process/child-process-bridge.js";
@@ -41,6 +42,7 @@ if (
// Imported as a dependency — skip all entry-point side effects.
} else {
process.title = "openclaw";
ensureOpenClawExecMarkerOnProcess();
installProcessWarningFilter();
normalizeEnv();
if (!isTruthyEnvValue(process.env.NODE_DISABLE_COMPILE_CACHE)) {

View File

@@ -10,6 +10,7 @@ import {
sanitizeHostExecEnv,
sanitizeSystemRunEnvOverrides,
} from "./host-env-security.js";
import { OPENCLAW_CLI_ENV_VALUE } from "./openclaw-exec-env.js";
describe("isDangerousHostEnvVarName", () => {
it("matches dangerous keys and prefixes case-insensitively", () => {
@@ -40,6 +41,7 @@ describe("sanitizeHostExecEnv", () => {
});
expect(env).toEqual({
OPENCLAW_CLI: OPENCLAW_CLI_ENV_VALUE,
PATH: "/usr/bin:/bin",
OK: "1",
});
@@ -68,6 +70,7 @@ describe("sanitizeHostExecEnv", () => {
});
expect(env.PATH).toBe("/usr/bin:/bin");
expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
expect(env.BASH_ENV).toBeUndefined();
expect(env.GIT_SSH_COMMAND).toBeUndefined();
expect(env.EDITOR).toBeUndefined();
@@ -91,6 +94,7 @@ describe("sanitizeHostExecEnv", () => {
});
expect(env.PATH).toBe("/usr/bin:/bin");
expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
expect(env.OK).toBe("1");
expect(env.SHELLOPTS).toBeUndefined();
expect(env.PS4).toBeUndefined();
@@ -109,6 +113,7 @@ describe("sanitizeHostExecEnv", () => {
});
expect(env.GOOD_KEY).toBe("ok");
expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
expect(env[" BAD KEY"]).toBeUndefined();
expect(env["NOT-PORTABLE"]).toBeUndefined();
});

View File

@@ -1,4 +1,5 @@
import HOST_ENV_SECURITY_POLICY_JSON from "./host-env-security-policy.json" with { type: "json" };
import { markOpenClawExecEnv } from "./openclaw-exec-env.js";
const PORTABLE_ENV_VAR_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -101,7 +102,7 @@ export function sanitizeHostExecEnv(params?: {
}
if (!overrides) {
return merged;
return markOpenClawExecEnv(merged);
}
for (const [rawKey, value] of Object.entries(overrides)) {
@@ -124,7 +125,7 @@ export function sanitizeHostExecEnv(params?: {
merged[key] = value;
}
return merged;
return markOpenClawExecEnv(merged);
}
export function sanitizeSystemRunEnvOverrides(params?: {

View File

@@ -0,0 +1,16 @@
export const OPENCLAW_CLI_ENV_VAR = "OPENCLAW_CLI";
export const OPENCLAW_CLI_ENV_VALUE = "1";
export function markOpenClawExecEnv<T extends Record<string, string | undefined>>(env: T): T {
return {
...env,
[OPENCLAW_CLI_ENV_VAR]: OPENCLAW_CLI_ENV_VALUE,
};
}
export function ensureOpenClawExecMarkerOnProcess(
env: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
env[OPENCLAW_CLI_ENV_VAR] = OPENCLAW_CLI_ENV_VALUE;
return env;
}

View File

@@ -3,6 +3,7 @@ import { EventEmitter } from "node:events";
import fs from "node:fs";
import process from "node:process";
import { describe, expect, it, vi } from "vitest";
import { OPENCLAW_CLI_ENV_VALUE } from "../infra/openclaw-exec-env.js";
import { attachChildProcessBridge } from "./child-process-bridge.js";
import { resolveCommandEnv, runCommandWithTimeout, shouldSpawnWithShell } from "./exec.js";
@@ -31,6 +32,7 @@ describe("runCommandWithTimeout", () => {
expect(resolved.OPENCLAW_BASE_ENV).toBe("base");
expect(resolved.OPENCLAW_TEST_ENV).toBe("ok");
expect(resolved.OPENCLAW_TO_REMOVE).toBeUndefined();
expect(resolved.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
});
it("suppresses npm fund prompts for npm argv", async () => {

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import process from "node:process";
import { promisify } from "node:util";
import { danger, shouldLogVerbose } from "../globals.js";
import { markOpenClawExecEnv } from "../infra/openclaw-exec-env.js";
import { logDebug, logError } from "../logger.js";
import { resolveCommandStdio } from "./spawn-utils.js";
@@ -213,7 +214,7 @@ export function resolveCommandEnv(params: {
resolvedEnv.npm_config_fund = "false";
}
}
return resolvedEnv;
return markOpenClawExecEnv(resolvedEnv);
}
export async function runCommandWithTimeout(