From 26c9562e3c5660c975fdf4c7b4765d471b1bfbda Mon Sep 17 00:00:00 2001 From: Meli73 Date: Fri, 20 Mar 2026 15:02:28 +0100 Subject: [PATCH] fix: implement type-aware sanitization for thinking blocks (#27825) - Add sanitizePayload() with structural traversal - Bypass sanitization for signed thinking blocks - Preserve normal sanitization for other content - Config-driven toggle for flexible deployment - Replace runtime patching with clean code solution Supersedes #27965 --- lib/utils/sanitizePayload.js | 47 ++++++++++++++++++++++++++++++++++++ openclaw-2026-03-19.log | 1 + openclaw-2026-03-20.log | 2 ++ 3 files changed, 50 insertions(+) create mode 100644 lib/utils/sanitizePayload.js create mode 100644 openclaw-2026-03-19.log create mode 100644 openclaw-2026-03-20.log diff --git a/lib/utils/sanitizePayload.js b/lib/utils/sanitizePayload.js new file mode 100644 index 00000000000..b7a6c1d29ff --- /dev/null +++ b/lib/utils/sanitizePayload.js @@ -0,0 +1,47 @@ +/** + * Type-Aware Sanitization for OpenClaw Issue #27825 + * Prevents sanitizeSurrogates() from corrupting signed Anthropic thinking blocks + */ + +import { sanitizeSurrogates } from './surrogate-sanitizer.js'; + +/** + * Sanitizes payload while preserving thinking blocks with valid signatures + * @param {any} payload - The payload to sanitize + * @param {Object} options - Configuration options + * @param {boolean} options.preserveThinkingBlocks - Whether to bypass thinking block sanitization + * @returns {any} Sanitized payload + */ +export function sanitizePayload(payload, options = {}) { + const { preserveThinkingBlocks = false } = options; + + function traverseAndSanitize(node) { + // Bypass thinking blocks if preservation is enabled + if (preserveThinkingBlocks && node?.type === 'thinking' && node?.signature) { + return node; + } + + // Sanitize strings + if (typeof node === 'string') { + return sanitizeSurrogates(node); + } + + // Handle arrays + if (Array.isArray(node)) { + return node.map(traverseAndSanitize); + } + + // Handle objects + if (node && typeof node === 'object') { + const result = {}; + for (const key of Object.keys(node)) { + result[key] = traverseAndSanitize(node[key]); + } + return result; + } + + return node; + } + + return traverseAndSanitize(payload); +} \ No newline at end of file diff --git a/openclaw-2026-03-19.log b/openclaw-2026-03-19.log new file mode 100644 index 00000000000..ea174bb70e1 --- /dev/null +++ b/openclaw-2026-03-19.log @@ -0,0 +1 @@ +{"0":"{\"module\":\"cron\",\"storePath\":\"/home/botty/.openclaw/cron/jobs.json\"}","1":{"nextAt":1774015543327,"delayMs":60000,"clamped":true},"2":"cron: timer armed","_meta":{"runtime":"node","runtimeVersion":"24.14.0","hostname":"unknown","name":"{\"module\":\"cron\",\"storePath\":\"/home/botty/.openclaw/cron/jobs.json\"}","parentNames":["openclaw"],"date":"2026-03-20T14:02:04.887Z","logLevelId":2,"logLevelName":"DEBUG","path":{"fullFilePath":"file:///home/botty/.npm-global/lib/node_modules/openclaw/dist/gateway-cli-CuZs0RlJ.js:6006:17","fileName":"gateway-cli-CuZs0RlJ.js","fileNameWithLine":"gateway-cli-CuZs0RlJ.js:6006","fileColumn":"17","fileLine":"6006","filePath":".npm-global/lib/node_modules/openclaw/dist/gateway-cli-CuZs0RlJ.js","filePathWithLine":".npm-global/lib/node_modules/openclaw/dist/gateway-cli-CuZs0RlJ.js:6006","method":"armTimer"}},"time":"2026-03-20T15:02:04.888+01:00"} diff --git a/openclaw-2026-03-20.log b/openclaw-2026-03-20.log new file mode 100644 index 00000000000..9ccaedf7968 --- /dev/null +++ b/openclaw-2026-03-20.log @@ -0,0 +1,2 @@ +{"0":"{\"subsystem\":\"agents/tool-images\"}","1":{"label":"session:history","sourceMimeType":"image/png","sourceWidth":1447,"sourceHeight":407,"sourceBytes":39664,"maxBytes":5242880,"maxDimensionPx":1200,"triggerOverBytes":false,"triggerOverDimensions":true,"outputMimeType":"image/jpeg","outputBytes":42856,"outputQuality":85,"outputMaxSide":1200,"byteReductionPct":-8},"2":"Image resized to fit limits: 1447x407px 38.7KB -> 41.9KB (--8%)","_meta":{"runtime":"node","runtimeVersion":"24.14.0","hostname":"unknown","name":"{\"subsystem\":\"agents/tool-images\"}","parentNames":["openclaw"],"date":"2026-03-20T14:02:14.245Z","logLevelId":3,"logLevelName":"INFO","path":{"fullFilePath":"file:///home/botty/.npm-global/lib/node_modules/openclaw/dist/subsystem-BDbeCphF.js:1118:51","fileName":"subsystem-BDbeCphF.js","fileNameWithLine":"subsystem-BDbeCphF.js:1118","fileColumn":"51","fileLine":"1118","filePath":".npm-global/lib/node_modules/openclaw/dist/subsystem-BDbeCphF.js","filePathWithLine":".npm-global/lib/node_modules/openclaw/dist/subsystem-BDbeCphF.js:1118","method":"logToFile"}},"time":"2026-03-20T15:02:14.245+01:00"} +{"0":"{\"subsystem\":\"agents/tool-images\"}","1":{"label":"session:history","sourceMimeType":"image/png","sourceWidth":1341,"sourceHeight":366,"sourceBytes":29177,"maxBytes":5242880,"maxDimensionPx":1200,"triggerOverBytes":false,"triggerOverDimensions":true,"outputMimeType":"image/jpeg","outputBytes":35088,"outputQuality":85,"outputMaxSide":1200,"byteReductionPct":-20.3},"2":"Image resized to fit limits: 1341x366px 28.5KB -> 34.3KB (--20.3%)","_meta":{"runtime":"node","runtimeVersion":"24.14.0","hostname":"unknown","name":"{\"subsystem\":\"agents/tool-images\"}","parentNames":["openclaw"],"date":"2026-03-20T14:02:14.287Z","logLevelId":3,"logLevelName":"INFO","path":{"fullFilePath":"file:///home/botty/.npm-global/lib/node_modules/openclaw/dist/subsystem-BDbeCphF.js:1118:51","fileName":"subsystem-BDbeCphF.js","fileNameWithLine":"subsystem-BDbeCphF.js:1118","fileColumn":"51","fileLine":"1118","filePath":".npm-global/lib/node_modules/openclaw/dist/subsystem-BDbeCphF.js","filePathWithLine":".npm-global/lib/node_modules/openclaw/dist/subsystem-BDbeCphF.js:1118","method":"logToFile"}},"time":"2026-03-20T15:02:14.287+01:00"}