From 87de81a5fd4a2d4e3ef8bff9af22a3ed2ab8b4e8 Mon Sep 17 00:00:00 2001 From: cxbAsDev Date: Sun, 19 Jul 2026 05:48:29 +0800 Subject: [PATCH] Bound diagnostic config file read with size cap (#110591) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bound diagnostic config read with size cap * fix: use buffer.toString for readRegularFileSync result in diagnostic export * test: verify normal and oversized diagnostic config export behavior PR #110591 — Add focused regression coverage for the bounded diagnostic config read: - Test: normal config file (<8MB) produces parseOk: true in config/shape.json - Test: oversized config file (>8MB) produces parseOk: false with graceful error handling - Both tests verify the support bundle zip is still written with all expected contents * refactor(logging): clarify support export config limit Co-authored-by: 陈宪彪0668000387 --------- Co-authored-by: Peter Steinberger --- src/logging/diagnostic-support-export.test.ts | 46 +++++++++++++++++++ src/logging/diagnostic-support-export.ts | 10 +++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/logging/diagnostic-support-export.test.ts b/src/logging/diagnostic-support-export.test.ts index c33a40adc2ea..1463e5c758bb 100644 --- a/src/logging/diagnostic-support-export.test.ts +++ b/src/logging/diagnostic-support-export.test.ts @@ -912,4 +912,50 @@ describe("diagnostic support export", () => { expect(combined).toContain("config stat failed with token"); expect(combined).toContain("Attach this zip to the bug report"); }); + + it("finishes the support export when the config exceeds its read limit", async () => { + const configPath = path.join(tempDir, "openclaw.json"); + const outputPath = path.join(tempDir, "support-oversized-config.zip"); + fs.writeFileSync(configPath, Buffer.alloc(8 * 1024 * 1024 + 1, "{")); + + await writeDiagnosticSupportExport({ + env: { + ...process.env, + HOME: tempDir, + OPENCLAW_CONFIG_PATH: configPath, + OPENCLAW_STATE_DIR: tempDir, + }, + stateDir: tempDir, + outputPath, + now: new Date("2026-07-18T12:00:01.000Z"), + readLogTail: async () => ({ + file: path.join(tempDir, "logs", "openclaw.log"), + cursor: 0, + size: 0, + truncated: false, + reset: false, + lines: [], + }), + }); + + const entries = await readZipTextEntries(outputPath); + const configShape = JSON.parse(entries["config/shape.json"] ?? "{}") as { + parseOk?: boolean; + error?: string; + }; + expect(configShape.parseOk).toBe(false); + expect(configShape.error).toContain("File exceeds 8388608 bytes"); + expect(entries["config/sanitized.json"]).toBe("null\n"); + expect(Object.keys(entries).toSorted()).toEqual([ + "config/sanitized.json", + "config/shape.json", + "diagnostics.json", + "logs/openclaw-sanitized.jsonl", + "manifest.json", + "summary.md", + ]); + + const combined = Object.values(entries).join("\n"); + expect(combined).toContain("Attach this zip to the bug report"); + }); }); diff --git a/src/logging/diagnostic-support-export.ts b/src/logging/diagnostic-support-export.ts index 82e9775e6d56..40b53e219ddf 100644 --- a/src/logging/diagnostic-support-export.ts +++ b/src/logging/diagnostic-support-export.ts @@ -8,6 +8,7 @@ import { resolveConfigPath, resolveStateDir } from "../config/paths.js"; import { redactConfigObject } from "../config/redact-snapshot.js"; import { buildConfigSchema } from "../config/schema.js"; import { resolveHomeRelativePath } from "../infra/home-dir.js"; +import { readRegularFileSync } from "../infra/regular-file.js"; import { VERSION } from "../version.js"; import { readDiagnosticStabilityBundleFileSync, @@ -38,6 +39,9 @@ const DIAGNOSTIC_SUPPORT_EXPORT_VERSION = 1; const DEFAULT_LOG_LIMIT = 5000; const DEFAULT_LOG_MAX_BYTES = 1_000_000; +// Support export must remain usable when the config is corrupt or unexpectedly +// large. This defensive ceiling is not the product's general config-file limit. +const SUPPORT_EXPORT_CONFIG_MAX_BYTES = 8 * 1024 * 1024; const SUPPORT_EXPORT_PREFIX = "openclaw-diagnostics-"; const SUPPORT_EXPORT_SUFFIX = ".zip"; type Awaitable = T | Promise; @@ -343,7 +347,11 @@ function readConfigExport(options: { let stat: fs.Stats | undefined; try { stat = fs.statSync(options.configPath); - const parsed = parseConfigJson5(fs.readFileSync(options.configPath, "utf8")); + const { buffer } = readRegularFileSync({ + filePath: options.configPath, + maxBytes: SUPPORT_EXPORT_CONFIG_MAX_BYTES, + }); + const parsed = parseConfigJson5(buffer.toString("utf8")); if (!parsed.ok) { return { shape: configShapeReadFailure({