mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 12:17:40 +00:00
* 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:
@@ -60,6 +60,49 @@ describe("resolveAcpClientSpawnEnv", () => {
|
||||
});
|
||||
expect(env.OPENCLAW_SHELL).toBe("acp-client");
|
||||
});
|
||||
|
||||
it("strips skill-injected env keys when stripKeys is provided", () => {
|
||||
const stripKeys = new Set(["OPENAI_API_KEY", "ELEVENLABS_API_KEY"]);
|
||||
const env = resolveAcpClientSpawnEnv(
|
||||
{
|
||||
PATH: "/usr/bin",
|
||||
OPENAI_API_KEY: "sk-leaked-from-skill",
|
||||
ELEVENLABS_API_KEY: "el-leaked",
|
||||
ANTHROPIC_API_KEY: "sk-keep-this",
|
||||
},
|
||||
{ stripKeys },
|
||||
);
|
||||
|
||||
expect(env.PATH).toBe("/usr/bin");
|
||||
expect(env.OPENCLAW_SHELL).toBe("acp-client");
|
||||
expect(env.ANTHROPIC_API_KEY).toBe("sk-keep-this");
|
||||
expect(env.OPENAI_API_KEY).toBeUndefined();
|
||||
expect(env.ELEVENLABS_API_KEY).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not modify the original baseEnv when stripping keys", () => {
|
||||
const baseEnv: NodeJS.ProcessEnv = {
|
||||
OPENAI_API_KEY: "sk-original",
|
||||
PATH: "/usr/bin",
|
||||
};
|
||||
const stripKeys = new Set(["OPENAI_API_KEY"]);
|
||||
resolveAcpClientSpawnEnv(baseEnv, { stripKeys });
|
||||
|
||||
expect(baseEnv.OPENAI_API_KEY).toBe("sk-original");
|
||||
});
|
||||
|
||||
it("preserves OPENCLAW_SHELL even when stripKeys contains it", () => {
|
||||
const env = resolveAcpClientSpawnEnv(
|
||||
{
|
||||
OPENCLAW_SHELL: "skill-overridden",
|
||||
OPENAI_API_KEY: "sk-leaked",
|
||||
},
|
||||
{ stripKeys: new Set(["OPENCLAW_SHELL", "OPENAI_API_KEY"]) },
|
||||
);
|
||||
|
||||
expect(env.OPENCLAW_SHELL).toBe("acp-client");
|
||||
expect(env.OPENAI_API_KEY).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveAcpClientSpawnInvocation", () => {
|
||||
|
||||
@@ -348,8 +348,16 @@ function buildServerArgs(opts: AcpClientOptions): string[] {
|
||||
|
||||
export function resolveAcpClientSpawnEnv(
|
||||
baseEnv: NodeJS.ProcessEnv = process.env,
|
||||
options?: { stripKeys?: ReadonlySet<string> },
|
||||
): NodeJS.ProcessEnv {
|
||||
return { ...baseEnv, OPENCLAW_SHELL: "acp-client" };
|
||||
const env: NodeJS.ProcessEnv = { ...baseEnv };
|
||||
if (options?.stripKeys) {
|
||||
for (const key of options.stripKeys) {
|
||||
delete env[key];
|
||||
}
|
||||
}
|
||||
env.OPENCLAW_SHELL = "acp-client";
|
||||
return env;
|
||||
}
|
||||
|
||||
type AcpSpawnRuntime = {
|
||||
@@ -450,7 +458,10 @@ export async function createAcpClient(opts: AcpClientOptions = {}): Promise<AcpC
|
||||
const entryPath = resolveSelfEntryPath();
|
||||
const serverCommand = opts.serverCommand ?? (entryPath ? process.execPath : "openclaw");
|
||||
const effectiveArgs = opts.serverCommand || !entryPath ? serverArgs : [entryPath, ...serverArgs];
|
||||
const spawnEnv = resolveAcpClientSpawnEnv();
|
||||
const { getActiveSkillEnvKeys } = await import("../agents/skills/env-overrides.runtime.js");
|
||||
const spawnEnv = resolveAcpClientSpawnEnv(process.env, {
|
||||
stripKeys: getActiveSkillEnvKeys(),
|
||||
});
|
||||
const spawnInvocation = resolveAcpClientSpawnInvocation(
|
||||
{ serverCommand, serverArgs: effectiveArgs },
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user