@@ -93,6 +93,39 @@ function runDefaultSmokePlatform(env: Record<string, string>, hostArch: string):
|
93 | 93 | return result.stdout; |
94 | 94 | } |
95 | 95 | |
| 96 | +function extractReadPackTarballFilename(): string { |
| 97 | +const script = readFileSync(SCRIPT_PATH, "utf8"); |
| 98 | +const match = script.match(/(read_pack_tarball_filename\(\) \{[\s\S]*?\n\})\n\nSMOKE_IMAGE/u); |
| 99 | +if (!match) { |
| 100 | +throw new Error("read_pack_tarball_filename helper was not found"); |
| 101 | +} |
| 102 | +return match[1]; |
| 103 | +} |
| 104 | + |
| 105 | +function runReadPackTarballFilename(filename: string) { |
| 106 | +return spawnSync( |
| 107 | +"bash", |
| 108 | +[ |
| 109 | +"--noprofile", |
| 110 | +"--norc", |
| 111 | +"-c", |
| 112 | +`${extractReadPackTarballFilename()} |
| 113 | +pack_json_file="$(mktemp)" |
| 114 | +trap 'rm -f "$pack_json_file"' EXIT |
| 115 | +printf '%s' "$PACK_JSON" >"$pack_json_file" |
| 116 | +read_pack_tarball_filename "$pack_json_file"`, |
| 117 | +], |
| 118 | +{ |
| 119 | +encoding: "utf8", |
| 120 | +env: { |
| 121 | +HOME: "/tmp", |
| 122 | +PACK_JSON: JSON.stringify([{ filename }]), |
| 123 | +PATH: process.env.PATH ?? "", |
| 124 | +}, |
| 125 | +}, |
| 126 | +); |
| 127 | +} |
| 128 | + |
96 | 129 | describe("test-install-sh-docker", () => { |
97 | 130 | it("defaults ARM hosts to native arm64 while keeping x64 CI on amd64", () => { |
98 | 131 | expect(runDefaultSmokePlatform({ CI: "true" }, "aarch64")).toBe("linux/arm64"); |
@@ -334,6 +367,42 @@ describe("test-install-sh-docker", () => {
|
334 | 367 | expect(script).toContain("install smoke cannot verify pack budget"); |
335 | 368 | }); |
336 | 369 | |
| 370 | +it("keeps npm pack tarball filenames local before serving update artifacts", () => { |
| 371 | +const script = readFileSync(SCRIPT_PATH, "utf8"); |
| 372 | + |
| 373 | +expect(script).toContain("read_pack_tarball_filename()"); |
| 374 | +expect(script).toContain('UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")"'); |
| 375 | +expect(script).toContain( |
| 376 | +'BASELINE_TGZ_FILE="$(read_pack_tarball_filename "$baseline_pack_json_file")"', |
| 377 | +); |
| 378 | +expect(script).toContain("filename !== path.basename(filename)"); |
| 379 | +expect(script).toContain("filename !== path.win32.basename(filename)"); |
| 380 | +expect(script).toContain("npm pack reported unsafe tarball filename"); |
| 381 | +}); |
| 382 | + |
| 383 | +it("rejects path-like npm pack tarball filenames in update smoke metadata", () => { |
| 384 | +expect(runReadPackTarballFilename("openclaw-2026.6.17.tgz")).toMatchObject({ |
| 385 | +status: 0, |
| 386 | +stdout: "openclaw-2026.6.17.tgz", |
| 387 | +}); |
| 388 | + |
| 389 | +const unsafeFilenames = [ |
| 390 | +"../openclaw.tgz", |
| 391 | +"nested/openclaw.tgz", |
| 392 | +"nested\\openclaw.tgz", |
| 393 | +"/tmp/openclaw.tgz", |
| 394 | +"C:\\temp\\openclaw.tgz", |
| 395 | +"openclaw.tar.gz", |
| 396 | +]; |
| 397 | + |
| 398 | +for (const filename of unsafeFilenames) { |
| 399 | +const result = runReadPackTarballFilename(filename); |
| 400 | + |
| 401 | +expect(result.status, filename).not.toBe(0); |
| 402 | +expect(result.stderr, filename).toContain("npm pack reported unsafe tarball filename"); |
| 403 | +} |
| 404 | +}); |
| 405 | + |
337 | 406 | it("writes the package dist inventory before packing ignore-scripts tarballs", () => { |
338 | 407 | const script = readFileSync(SCRIPT_PATH, "utf8"); |
339 | 408 | |
|