Skip to content

Commit 4610b17

Browse files
author
OpenClaw Bot
committed
fix(models): avoid serializing plaintext keys from non-env SecretRefs
1 parent ed8e0a8 commit 4610b17

File tree

2 files changed

+83
-8
lines changed

2 files changed

+83
-8
lines changed

src/agents/models-config.providers.normalize-keys.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,71 @@ describe("normalizeProviders", () => {
7373
await fs.rm(agentDir, { recursive: true, force: true });
7474
}
7575
});
76+
77+
it("does not copy plaintext api keys from non-env SecretRefs into providers", async () => {
78+
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
79+
try {
80+
await fs.writeFile(
81+
path.join(agentDir, "auth-profiles.json"),
82+
JSON.stringify({
83+
version: 1,
84+
profiles: {
85+
"openai:default": {
86+
id: "openai:default",
87+
provider: "openai",
88+
type: "api_key",
89+
key: "sk-live-from-exec",
90+
keyRef: { source: "exec", provider: "default", id: "keychain:openai" },
91+
},
92+
},
93+
providerOrder: { openai: ["openai:default"] },
94+
}),
95+
);
96+
const providers: NonNullable<NonNullable<OpenClawConfig["models"]>["providers"]> = {
97+
openai: {
98+
baseUrl: "https://api.openai.com/v1",
99+
api: "openai-completions",
100+
models: [{ id: "gpt-4.1-mini", name: "GPT", input: ["text"], reasoning: false }],
101+
},
102+
};
103+
104+
const normalized = normalizeProviders({ providers, agentDir });
105+
expect(normalized?.openai?.apiKey).toBeUndefined();
106+
} finally {
107+
await fs.rm(agentDir, { recursive: true, force: true });
108+
}
109+
});
110+
111+
it("uses env var name when auth profile uses env SecretRef", async () => {
112+
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
113+
try {
114+
await fs.writeFile(
115+
path.join(agentDir, "auth-profiles.json"),
116+
JSON.stringify({
117+
version: 1,
118+
profiles: {
119+
"openai:default": {
120+
id: "openai:default",
121+
provider: "openai",
122+
type: "api_key",
123+
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
124+
},
125+
},
126+
providerOrder: { openai: ["openai:default"] },
127+
}),
128+
);
129+
const providers: NonNullable<NonNullable<OpenClawConfig["models"]>["providers"]> = {
130+
openai: {
131+
baseUrl: "https://api.openai.com/v1",
132+
api: "openai-completions",
133+
models: [{ id: "gpt-4.1-mini", name: "GPT", input: ["text"], reasoning: false }],
134+
},
135+
};
136+
137+
const normalized = normalizeProviders({ providers, agentDir });
138+
expect(normalized?.openai?.apiKey).toBe("OPENAI_API_KEY");
139+
} finally {
140+
await fs.rm(agentDir, { recursive: true, force: true });
141+
}
142+
});
76143
});

src/agents/models-config.providers.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -414,23 +414,31 @@ function resolveApiKeyFromProfiles(params: {
414414
continue;
415415
}
416416
if (cred.type === "api_key") {
417+
const keyRef = coerceSecretRef(cred.keyRef);
418+
if (keyRef) {
419+
if (keyRef.source === "env" && keyRef.id.trim()) {
420+
return keyRef.id.trim();
421+
}
422+
// Non-env SecretRefs (exec/file) must not be serialized into models.json.
423+
continue;
424+
}
417425
if (cred.key?.trim()) {
418426
return cred.key;
419427
}
420-
const keyRef = coerceSecretRef(cred.keyRef);
421-
if (keyRef?.source === "env" && keyRef.id.trim()) {
422-
return keyRef.id.trim();
423-
}
424428
continue;
425429
}
426430
if (cred.type === "token") {
431+
const tokenRef = coerceSecretRef(cred.tokenRef);
432+
if (tokenRef) {
433+
if (tokenRef.source === "env" && tokenRef.id.trim()) {
434+
return tokenRef.id.trim();
435+
}
436+
// Non-env SecretRefs (exec/file) must not be serialized into models.json.
437+
continue;
438+
}
427439
if (cred.token?.trim()) {
428440
return cred.token;
429441
}
430-
const tokenRef = coerceSecretRef(cred.tokenRef);
431-
if (tokenRef?.source === "env" && tokenRef.id.trim()) {
432-
return tokenRef.id.trim();
433-
}
434442
continue;
435443
}
436444
}

0 commit comments

Comments
 (0)