diff --git a/src/config/redact-snapshot.test.ts b/src/config/redact-snapshot.test.ts index 41ae0ba1c88..559089571e6 100644 --- a/src/config/redact-snapshot.test.ts +++ b/src/config/redact-snapshot.test.ts @@ -50,7 +50,10 @@ describe("redactConfigSnapshot", () => { signingSecret: "slack-signing-secret-value-1234", token: "secret-slack-token-value-here", }, - feishu: { appSecret: "feishu-app-secret-value-here-1234" }, + feishu: { + appSecret: "feishu-app-secret-value-here-1234", + encryptKey: "feishu-encrypt-key-value-here-1234", + }, }, models: { providers: { @@ -70,6 +73,7 @@ describe("redactConfigSnapshot", () => { expect(cfg.channels.slack.signingSecret).toBe(REDACTED_SENTINEL); expect(cfg.channels.slack.token).toBe(REDACTED_SENTINEL); expect(cfg.channels.feishu.appSecret).toBe(REDACTED_SENTINEL); + expect(cfg.channels.feishu.encryptKey).toBe(REDACTED_SENTINEL); expect(cfg.models.providers.openai.apiKey).toBe(REDACTED_SENTINEL); expect(cfg.models.providers.openai.baseUrl).toBe("https://api.openai.com"); expect(cfg.shortSecret.token).toBe(REDACTED_SENTINEL); diff --git a/src/config/schema.hints.test.ts b/src/config/schema.hints.test.ts index e21a330f2e6..0713e63c35d 100644 --- a/src/config/schema.hints.test.ts +++ b/src/config/schema.hints.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { z } from "zod"; +import { buildSecretInputSchema } from "../plugin-sdk/secret-input-schema.js"; import { __test__, isSensitiveConfigPath } from "./schema.hints.js"; import { OpenClawSchema } from "./zod-schema.js"; import { sensitive } from "./zod-schema.sensitive.js"; @@ -30,6 +31,8 @@ describe("isSensitiveConfigPath", () => { expect(isSensitiveConfigPath("channels.slack.token")).toBe(true); expect(isSensitiveConfigPath("models.providers.openai.apiKey")).toBe(true); expect(isSensitiveConfigPath("channels.irc.nickserv.password")).toBe(true); + expect(isSensitiveConfigPath("channels.feishu.encryptKey")).toBe(true); + expect(isSensitiveConfigPath("channels.feishu.accounts.default.encryptKey")).toBe(true); }); }); @@ -138,4 +141,19 @@ describe("mapSensitivePaths", () => { expect(hints["models.providers.*.headers.*"]?.sensitive).toBe(true); expect(hints["skills.entries.*.apiKey"]?.sensitive).toBe(true); }); + + it("marks buildSecretInputSchema fields as sensitive via registry", () => { + const schema = z.object({ + encryptKey: buildSecretInputSchema().optional(), + appSecret: buildSecretInputSchema().optional(), + nested: z.object({ + verificationToken: buildSecretInputSchema().optional(), + }), + }); + const hints = mapSensitivePaths(schema, "", {}); + + expect(hints["encryptKey"]?.sensitive).toBe(true); + expect(hints["appSecret"]?.sensitive).toBe(true); + expect(hints["nested.verificationToken"]?.sensitive).toBe(true); + }); }); diff --git a/src/config/schema.hints.ts b/src/config/schema.hints.ts index 665a547844b..0f630d044d6 100644 --- a/src/config/schema.hints.ts +++ b/src/config/schema.hints.ts @@ -113,6 +113,7 @@ const SENSITIVE_PATTERNS = [ /password/i, /secret/i, /api.?key/i, + /encrypt.?key/i, /serviceaccount(?:ref)?$/i, ]; diff --git a/src/plugin-sdk/secret-input-schema.ts b/src/plugin-sdk/secret-input-schema.ts index 96d5247c767..7bf76b4f9ee 100644 --- a/src/plugin-sdk/secret-input-schema.ts +++ b/src/plugin-sdk/secret-input-schema.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { ENV_SECRET_REF_ID_RE } from "../config/types.secrets.js"; +import { sensitive } from "../config/zod-schema.sensitive.js"; import { formatExecSecretRefIdValidationMessage, isValidExecSecretRefId, @@ -7,16 +8,17 @@ import { SECRET_PROVIDER_ALIAS_PATTERN, } from "../secrets/ref-contract.js"; -/** Build the shared zod schema for secret inputs accepted by plugin auth/config surfaces. */ -export function buildSecretInputSchema() { - const providerSchema = z - .string() - .regex( - SECRET_PROVIDER_ALIAS_PATTERN, - 'Secret reference provider must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").', - ); +const providerSchema = z + .string() + .regex( + SECRET_PROVIDER_ALIAS_PATTERN, + 'Secret reference provider must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").', + ); - return z.union([ +// Singleton registered with the sensitive registry so that mapSensitivePaths +// marks every config field using this schema as sensitive (redacted). +const secretInputSchema = z + .union([ z.string(), z.discriminatedUnion("source", [ z.object({ @@ -45,5 +47,10 @@ export function buildSecretInputSchema() { id: z.string().refine(isValidExecSecretRefId, formatExecSecretRefIdValidationMessage()), }), ]), - ]); + ]) + .register(sensitive); + +/** Build the shared zod schema for secret inputs accepted by plugin auth/config surfaces. */ +export function buildSecretInputSchema() { + return secretInputSchema; }