mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:40:43 +00:00
fix(gateway): redact secrets in skills.update response
skills.update was returning the full updated SkillConfig entry including plaintext apiKey and env values in the RPC response. This could leak credentials into Control UI WebSocket traffic, client logs, or session transcripts. Fix by passing the response config through the existing redactConfigObject() utility, which already handles all sensitive paths (apiKey, tokens, passwords, secret-named env keys, etc.) via isSensitiveConfigPath(). Config is still written to disk in full — only the response payload is redacted. Fixes #66769
This commit is contained in:
@@ -14,6 +14,7 @@ import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js";
|
||||
import { loadWorkspaceSkillEntries, type SkillEntry } from "../../agents/skills.js";
|
||||
import { listAgentWorkspaceDirs } from "../../agents/workspace-dirs.js";
|
||||
import { loadConfig, writeConfigFile } from "../../config/config.js";
|
||||
import { redactConfigObject } from "../../config/redact-snapshot.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { fetchClawHubSkillDetail } from "../../infra/clawhub.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
@@ -341,6 +342,10 @@ export const skillsHandlers: GatewayRequestHandlers = {
|
||||
skills,
|
||||
};
|
||||
await writeConfigFile(nextConfig);
|
||||
respond(true, { ok: true, skillKey: p.skillKey, config: current }, undefined);
|
||||
respond(
|
||||
true,
|
||||
{ ok: true, skillKey: p.skillKey, config: redactConfigObject(current) },
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -50,4 +50,50 @@ describe("skills.update", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("redacts apiKey and secret env values from the response but writes full values to config", async () => {
|
||||
writtenConfig = null;
|
||||
|
||||
let responseResult: unknown = null;
|
||||
await skillsHandlers["skills.update"]({
|
||||
params: {
|
||||
skillKey: "demo-skill",
|
||||
apiKey: "secret-api-key-123",
|
||||
env: {
|
||||
GEMINI_API_KEY: "secret-env-key-456",
|
||||
BRAVE_REGION: "us",
|
||||
},
|
||||
},
|
||||
req: {} as never,
|
||||
client: null as never,
|
||||
isWebchatConnect: () => false,
|
||||
context: {} as never,
|
||||
respond: (_success, result, _err) => {
|
||||
responseResult = result;
|
||||
},
|
||||
});
|
||||
|
||||
// Full values must be persisted to config
|
||||
expect(writtenConfig).toMatchObject({
|
||||
skills: {
|
||||
entries: {
|
||||
"demo-skill": {
|
||||
apiKey: "secret-api-key-123",
|
||||
env: {
|
||||
GEMINI_API_KEY: "secret-env-key-456",
|
||||
BRAVE_REGION: "us",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Response must not expose plaintext secrets
|
||||
const config = (responseResult as { config: Record<string, unknown> }).config;
|
||||
expect(config.apiKey).not.toBe("secret-api-key-123");
|
||||
const env = config.env as Record<string, string>;
|
||||
expect(env.GEMINI_API_KEY).not.toBe("secret-env-key-456");
|
||||
// Non-secret env values should still be present
|
||||
expect(env.BRAVE_REGION).toBe("us");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user