fix: strip skill-injected env vars from ACP harness spawn env (#36280) (#36316)

* fix: strip skill-injected env vars from ACP harness spawn env

Skill apiKey entries (e.g., openai-image-gen with primaryEnv=OPENAI_API_KEY)
are set on process.env during agent runs and only reverted after the run
completes. ACP harnesses like Codex CLI inherit these vars, causing them
to silently use API billing instead of their own auth (e.g., OAuth).

The fix tracks which env vars are actively injected by skill overrides in
a module-level Set (activeSkillEnvKeys) and strips them in
resolveAcpClientSpawnEnv() before spawning ACP child processes.

Fixes #36280

* ACP: type spawn env for stripped keys

* Skills: cover active env key lifecycle

* Changelog: note ACP skill env isolation

* ACP: preserve shell marker after env stripping

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Drew Wagner
2026-03-06 18:18:13 -05:00
committed by GitHub
parent 03b9abab84
commit ae96a81916
6 changed files with 76 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ import {
buildWorkspaceSkillSnapshot,
loadWorkspaceSkillEntries,
} from "./skills.js";
import { getActiveSkillEnvKeys } from "./skills/env-overrides.js";
const fixtureSuite = createFixtureSuite("openclaw-skills-suite-");
let tempHome: TempHomeEnv | null = null;
@@ -256,9 +257,11 @@ describe("applySkillEnvOverrides", () => {
try {
expect(process.env.ENV_KEY).toBe("injected");
expect(getActiveSkillEnvKeys().has("ENV_KEY")).toBe(true);
} finally {
restore();
expect(process.env.ENV_KEY).toBeUndefined();
expect(getActiveSkillEnvKeys().has("ENV_KEY")).toBe(false);
}
});
});

View File

@@ -0,0 +1 @@
export { getActiveSkillEnvKeys } from "./env-overrides.js";

View File

@@ -12,6 +12,19 @@ const log = createSubsystemLogger("env-overrides");
type EnvUpdate = { key: string; prev: string | undefined };
type SkillConfig = NonNullable<ReturnType<typeof resolveSkillConfig>>;
/**
* Tracks env var keys that are currently injected by skill overrides.
* Used by ACP harness spawn to strip skill-injected keys so they don't
* leak to child processes (e.g., OPENAI_API_KEY leaking to Codex CLI).
* @see https://github.com/openclaw/openclaw/issues/36280
*/
const activeSkillEnvKeys = new Set<string>();
/** Returns a snapshot of env var keys currently injected by skill overrides. */
export function getActiveSkillEnvKeys(): ReadonlySet<string> {
return activeSkillEnvKeys;
}
type SanitizedSkillEnvOverrides = {
allowed: Record<string, string>;
blocked: string[];
@@ -135,12 +148,14 @@ function applySkillConfigEnvOverrides(params: {
}
updates.push({ key: envKey, prev: process.env[envKey] });
process.env[envKey] = envValue;
activeSkillEnvKeys.add(envKey);
}
}
function createEnvReverter(updates: EnvUpdate[]) {
return () => {
for (const update of updates) {
activeSkillEnvKeys.delete(update.key);
if (update.prev === undefined) {
delete process.env[update.key];
} else {