Bound diagnostic config file read with size cap (#110591)

* 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 <chen.xianbiao@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
cxbAsDev
2026-07-19 05:48:29 +08:00
committed by GitHub
parent 8ff1512454
commit 87de81a5fd
2 changed files with 55 additions and 1 deletions

View File

@@ -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");
});
});

View File

@@ -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> = T | Promise<T>;
@@ -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({