diff --git a/CHANGELOG.md b/CHANGELOG.md index efdffbe5594..910a00ad26d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Subagents/Models: preserve `agents.defaults.model.fallbacks` when subagent sessions carry a model override, so subagent runs fail over to configured fallback models instead of retrying only the overridden primary model. +- Config/Gateway: make sensitive-key whitelist suffix matching case-insensitive while preserving `passwordFile` path exemptions, preventing accidental redaction of non-secret config values like `maxTokens` and IRC password-file paths. (#16042) Thanks @akramcodez. - Group chats: always inject group chat context (name, participants, reply guidance) into the system prompt on every turn, not just the first. Prevents the model from losing awareness of which group it's in and incorrectly using the message tool to send to the same group. (#14447) Thanks @tyler6204. - TUI: make searchable-select filtering and highlight rendering ANSI-aware so queries ignore hidden escape codes and no longer corrupt ANSI styling sequences during match highlighting. (#4519) Thanks @bee4come. - TUI/Windows: coalesce rapid single-line submit bursts in Git Bash into one multiline message as a fallback when bracketed paste is unavailable, preventing pasted multiline text from being split into multiple sends. (#4986) Thanks @adamkane. diff --git a/src/config/schema.hints.test.ts b/src/config/schema.hints.test.ts index 0df9cf123a1..b44bcfeb227 100644 --- a/src/config/schema.hints.test.ts +++ b/src/config/schema.hints.test.ts @@ -1,11 +1,26 @@ import { describe, expect, it } from "vitest"; import { z } from "zod"; -import { __test__ } from "./schema.hints.js"; +import { __test__, isSensitiveConfigPath } from "./schema.hints.js"; import { OpenClawSchema } from "./zod-schema.js"; import { sensitive } from "./zod-schema.sensitive.js"; const { mapSensitivePaths } = __test__; +describe("isSensitiveConfigPath", () => { + it("matches whitelist suffixes case-insensitively", () => { + expect(isSensitiveConfigPath("maxTokens")).toBe(false); + expect(isSensitiveConfigPath("MAXTOKENS")).toBe(false); + expect(isSensitiveConfigPath("channels.irc.nickserv.passwordFile")).toBe(false); + expect(isSensitiveConfigPath("channels.irc.nickserv.PASSWORDFILE")).toBe(false); + }); + + it("keeps true sensitive keys redacted", () => { + expect(isSensitiveConfigPath("channels.slack.token")).toBe(true); + expect(isSensitiveConfigPath("models.providers.openai.apiKey")).toBe(true); + expect(isSensitiveConfigPath("channels.irc.nickserv.password")).toBe(true); + }); +}); + describe("mapSensitivePaths", () => { it("should detect sensitive fields nested inside all structural Zod types", () => { const GrandSchema = z.object({ diff --git a/src/config/schema.hints.ts b/src/config/schema.hints.ts index 10db8a1da3d..5eb5dd722fe 100644 --- a/src/config/schema.hints.ts +++ b/src/config/schema.hints.ts @@ -91,7 +91,7 @@ const FIELD_PLACEHOLDERS: Record = { * These are explicitly excluded from redaction (plugin config) and * warnings about not being marked sensitive (base config). */ -const SENSITIVE_KEY_WHITELIST = new Set([ +const SENSITIVE_KEY_WHITELIST_SUFFIXES = [ "maxtokens", "maxoutputtokens", "maxinputtokens", @@ -101,17 +101,15 @@ const SENSITIVE_KEY_WHITELIST = new Set([ "tokencount", "tokenlimit", "tokenbudget", - "passwordFile", -]); + "passwordfile", +] as const; const SENSITIVE_PATTERNS = [/token$/i, /password/i, /secret/i, /api.?key/i]; export function isSensitiveConfigPath(path: string): boolean { const lowerPath = path.toLowerCase(); - return ( - !Array.from(SENSITIVE_KEY_WHITELIST).some((suffix) => lowerPath.endsWith(suffix)) && - SENSITIVE_PATTERNS.some((pattern) => pattern.test(path)) - ); + const whitelisted = SENSITIVE_KEY_WHITELIST_SUFFIXES.some((suffix) => lowerPath.endsWith(suffix)); + return !whitelisted && SENSITIVE_PATTERNS.some((pattern) => pattern.test(path)); } export function buildBaseHints(): ConfigUiHints {