Skip to content

Commit bb1043b

Browse files
authored
fix(scripts): reject unsafe package download lengths
Reject unsafe decimal package_url Content-Length values before streaming response bodies.\n\nValidation:\n- node --check scripts/resolve-openclaw-package-candidate.mjs\n- direct injected downloadUrl repro for unsafe Content-Length\n- git diff --check origin/main...HEAD\n- autoreview clean, overall patch correct 0.9\n- exact-head release gate https://github.com/openclaw/openclaw/actions/runs/27844538401
1 parent 16fba65 commit bb1043b

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

scripts/resolve-openclaw-package-candidate.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1209,7 +1209,10 @@ export async function downloadUrl(url, target, options = {}) {
12091209
const rawContentLength = responseHeader(response, "content-length");
12101210
const contentLength =
12111211
rawContentLength && /^\d+$/u.test(rawContentLength) ? Number(rawContentLength) : undefined;
1212-
if (Number.isSafeInteger(contentLength) && contentLength > maxBytes) {
1212+
if (
1213+
contentLength !== undefined &&
1214+
(!Number.isSafeInteger(contentLength) || contentLength > maxBytes)
1215+
) {
12131216
throw new Error(`package_url exceeds maximum download size of ${maxBytes} bytes`);
12141217
}
12151218
await fs.rm(tempTarget, { force: true });

test/scripts/resolve-openclaw-package-candidate.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,50 @@ describe("resolve-openclaw-package-candidate", () => {
726726
expect(bodyCancelled).toBe(true);
727727
});
728728

729+
it("rejects unsafe decimal package_url content-length values before reading", async () => {
730+
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-package-download-"));
731+
tempDirs.push(dir);
732+
const target = path.join(dir, "openclaw.tgz");
733+
let readStarted = false;
734+
let bodyCancelled = false;
735+
736+
await expect(
737+
downloadUrl("https://packages.example/openclaw.tgz", target, {
738+
fetchImpl: async () =>
739+
({
740+
body: {
741+
cancel() {
742+
bodyCancelled = true;
743+
return Promise.resolve();
744+
},
745+
getReader() {
746+
return {
747+
cancel() {
748+
bodyCancelled = true;
749+
return Promise.resolve();
750+
},
751+
read() {
752+
readStarted = true;
753+
return new Promise(() => {});
754+
},
755+
releaseLock() {},
756+
};
757+
},
758+
},
759+
headers: new Headers({ "content-length": "9007199254740993" }),
760+
status: 200,
761+
}) as Response,
762+
lookupHost: lookupAddresses([{ address: "93.184.216.34", family: 4 }]),
763+
maxBytes: 1024,
764+
timeoutMs: 25,
765+
}),
766+
).rejects.toThrow(/exceeds maximum download size/u);
767+
expect(readStarted).toBe(false);
768+
expect(bodyCancelled).toBe(true);
769+
await expect(missing(target)).resolves.toBe(true);
770+
await expect(missing(`${target}.tmp`)).resolves.toBe(true);
771+
});
772+
729773
it("bounds package_url downloads and writes completed files atomically", async () => {
730774
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-package-download-"));
731775
tempDirs.push(dir);

0 commit comments

Comments
 (0)