mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 01:21:36 +00:00
config: skip empty string in raw redaction to avoid corrupting snapshot (#28214)
Merged via squash.
Prepared head SHA: 07ec5b77b1
Co-authored-by: solodmd <51304754+solodmd@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
@@ -89,6 +89,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack/app manifest: set `bot_user.always_online` to `true` in the onboarding and example Slack app manifest so the Slack app appears ready to respond.
|
||||
- Gateway/websocket auth: refresh auth on new websocket connects after secrets reload so rotated gateway tokens take effect immediately without requiring a restart. (#60323) Thanks @mappel-nv.
|
||||
- Agents/workspace: respect `agents.defaults.workspace` for non-default agents by resolving them under the configured base path instead of falling back to `workspace-<id>`. (#59858) Thanks @joelnishanth.
|
||||
- Config/All Settings: keep the raw config view intact when sensitive fields are blank instead of corrupting or dropping the snapshot during redaction. (#28214) thanks @solodmd.
|
||||
|
||||
## 2026.4.2
|
||||
|
||||
|
||||
44
src/config/redact-snapshot.raw.test.ts
Normal file
44
src/config/redact-snapshot.raw.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { REDACTED_SENTINEL } from "./redact-snapshot.js";
|
||||
import { replaceSensitiveValuesInRaw } from "./redact-snapshot.raw.js";
|
||||
|
||||
describe("replaceSensitiveValuesInRaw", () => {
|
||||
it("ignores empty string replacement tokens", () => {
|
||||
const raw = '{ "gateway": { "auth": { "token": "" } }, "other": "" }';
|
||||
|
||||
const result = replaceSensitiveValuesInRaw({
|
||||
raw,
|
||||
sensitiveValues: [""],
|
||||
redactedSentinel: REDACTED_SENTINEL,
|
||||
});
|
||||
|
||||
expect(result).toBe(raw);
|
||||
});
|
||||
|
||||
it("redacts non-empty values while preserving blank strings", () => {
|
||||
const raw = '{ "token": "", "secret": "abc123", "other": "" }';
|
||||
|
||||
const result = replaceSensitiveValuesInRaw({
|
||||
raw,
|
||||
sensitiveValues: ["", "abc123"],
|
||||
redactedSentinel: REDACTED_SENTINEL,
|
||||
});
|
||||
|
||||
expect(result).toContain('"token": ""');
|
||||
expect(result).toContain('"other": ""');
|
||||
expect(result).not.toContain("abc123");
|
||||
expect(result).toContain(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("replaces longest values first for overlapping matches", () => {
|
||||
const raw = '{ "token": "abcd", "prefix": "ab" }';
|
||||
|
||||
const result = replaceSensitiveValuesInRaw({
|
||||
raw,
|
||||
sensitiveValues: ["ab", "abcd", "abcd"],
|
||||
redactedSentinel: REDACTED_SENTINEL,
|
||||
});
|
||||
|
||||
expect(result).toBe(`{ "token": "${REDACTED_SENTINEL}", "prefix": "${REDACTED_SENTINEL}" }`);
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,11 @@ export function replaceSensitiveValuesInRaw(params: {
|
||||
sensitiveValues: string[];
|
||||
redactedSentinel: string;
|
||||
}): string {
|
||||
const values = [...params.sensitiveValues].toSorted((a, b) => b.length - a.length);
|
||||
// Empty string is not a valid replacement token here: replaceAll("", x)
|
||||
// matches every character boundary and corrupts the whole raw snapshot.
|
||||
const values = [...new Set(params.sensitiveValues)]
|
||||
.filter((value) => value !== "")
|
||||
.toSorted((a, b) => b.length - a.length);
|
||||
let result = params.raw;
|
||||
for (const value of values) {
|
||||
result = result.replaceAll(value, params.redactedSentinel);
|
||||
|
||||
@@ -545,6 +545,19 @@ describe("redactConfigSnapshot", () => {
|
||||
expect(restored).toEqual(snapshot.config);
|
||||
});
|
||||
|
||||
it("does not mangle raw when a sensitive field value is empty string", () => {
|
||||
const config = {
|
||||
gateway: { auth: { token: "" } },
|
||||
other: "",
|
||||
};
|
||||
const raw = '{ "gateway": { "auth": { "token": "" } }, "other": "" }';
|
||||
const snapshot = makeSnapshot(config, raw);
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
expect(result.config.gateway?.auth?.token).toBe(REDACTED_SENTINEL);
|
||||
expect(result.raw).toBe(raw);
|
||||
expect((result.raw ?? "").split(REDACTED_SENTINEL).length).toBe(1);
|
||||
});
|
||||
|
||||
it("redacts parsed and resolved objects", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
channels: { discord: { token: "MTIzNDU2Nzg5MDEyMzQ1Njc4.GaBcDe.FgH" } },
|
||||
|
||||
Reference in New Issue
Block a user