mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-21 13:24:46 +00:00
fix(backup): hide manifest parser internals (#82539)
This commit is contained in:
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/sessions: keep reachable transcript history when imported tree transcripts reference missing or legacy parent rows, preventing session history reads from going empty after a partial import.
|
||||
- Trajectory export: report incomplete transcript parent chains and stop cyclic branch walks so malformed imports cannot hang `/export-trajectory`.
|
||||
- Session replay: skip malformed user/assistant-shaped transcript rows during silent session resets instead of copying invalid entries into the fresh transcript.
|
||||
- Backup verify: report malformed archive manifests with a stable error instead of leaking raw JSON parser details.
|
||||
- Providers: reject malformed successful Runway, BytePlus, and Ollama embedding responses with provider-owned errors instead of raw parser/type failures, silent bad vectors, or long bogus polling.
|
||||
- Providers/images: reject malformed successful OpenAI-compatible, OpenAI, Google, fal, and OpenRouter image responses with provider-owned errors instead of raw shape failures, silent invalid base64 skips, or empty image results.
|
||||
- Providers/videos: reject malformed successful xAI, OpenRouter, and fal video create, poll, and result responses with provider-owned errors instead of raw parser failures or long bogus polling.
|
||||
|
||||
@@ -32,6 +32,47 @@ function createBackupManifest(assetArchivePath: string, archiveRoot = TEST_ARCHI
|
||||
};
|
||||
}
|
||||
|
||||
async function createArchiveWithManifestContent(
|
||||
options: {
|
||||
tempPrefix: string;
|
||||
manifestContent: string;
|
||||
payloadArchivePath?: string;
|
||||
},
|
||||
run: (archivePath: string) => Promise<void>,
|
||||
) {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempPrefix));
|
||||
const archivePath = path.join(tempDir, "broken.tar.gz");
|
||||
const manifestPath = path.join(tempDir, "manifest.json");
|
||||
const payloadPath = path.join(tempDir, "payload.txt");
|
||||
const payloadArchivePath =
|
||||
options.payloadArchivePath ?? `${TEST_ARCHIVE_ROOT}/payload/posix/tmp/.openclaw/payload.txt`;
|
||||
try {
|
||||
await fs.writeFile(manifestPath, options.manifestContent, "utf8");
|
||||
await fs.writeFile(payloadPath, "payload\n", "utf8");
|
||||
await tar.c(
|
||||
{
|
||||
file: archivePath,
|
||||
gzip: true,
|
||||
portable: true,
|
||||
preservePaths: true,
|
||||
onWriteEntry: (entry) => {
|
||||
if (entry.path === manifestPath) {
|
||||
entry.path = `${TEST_ARCHIVE_ROOT}/manifest.json`;
|
||||
return;
|
||||
}
|
||||
if (entry.path === payloadPath) {
|
||||
entry.path = payloadArchivePath;
|
||||
}
|
||||
},
|
||||
},
|
||||
[manifestPath, payloadPath],
|
||||
);
|
||||
await run(archivePath);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function withBrokenArchiveFixture(
|
||||
options: {
|
||||
tempPrefix: string;
|
||||
@@ -196,6 +237,24 @@ describe("backupVerifyCommand", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("reports malformed manifest JSON without leaking parser internals", async () => {
|
||||
await createArchiveWithManifestContent(
|
||||
{
|
||||
tempPrefix: "openclaw-backup-bad-manifest-json-",
|
||||
manifestContent: '{"schemaVersion":1,',
|
||||
},
|
||||
async (archivePath) => {
|
||||
const runtime = createBackupVerifyRuntime();
|
||||
await expect(backupVerifyCommand(runtime, { archive: archivePath })).rejects.toThrow(
|
||||
/^Backup manifest is not valid JSON\.$/u,
|
||||
);
|
||||
await expect(backupVerifyCommand(runtime, { archive: archivePath })).rejects.not.toThrow(
|
||||
/position|Unexpected|Expected|SyntaxError/u,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects unsafe archive paths", async () => {
|
||||
for (const { tempPrefix, archivePath, error } of [
|
||||
{
|
||||
|
||||
@@ -96,7 +96,7 @@ function parseManifest(raw: string): BackupManifest {
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch (err) {
|
||||
throw new Error(`Backup manifest is not valid JSON: ${String(err)}`, { cause: err });
|
||||
throw new Error("Backup manifest is not valid JSON.", { cause: err });
|
||||
}
|
||||
|
||||
if (!isRecord(parsed)) {
|
||||
|
||||
Reference in New Issue
Block a user