@@ -126,6 +126,40 @@ read_pack_tarball_filename "$pack_json_file"`,
|
126 | 126 | ); |
127 | 127 | } |
128 | 128 | |
| 129 | +function extractResolvePackTarballPath(): string { |
| 130 | +const script = readFileSync(BUN_GLOBAL_SMOKE_PATH, "utf8"); |
| 131 | +const match = script.match(/(resolve_pack_tarball_path\(\) \{[\s\S]*?\n\})\n\nrestore_dist/u); |
| 132 | +if (!match) { |
| 133 | +throw new Error("resolve_pack_tarball_path helper was not found"); |
| 134 | +} |
| 135 | +return match[1]; |
| 136 | +} |
| 137 | + |
| 138 | +function runResolvePackTarballPath(filename: string) { |
| 139 | +return spawnSync( |
| 140 | +"bash", |
| 141 | +[ |
| 142 | +"--noprofile", |
| 143 | +"--norc", |
| 144 | +"-c", |
| 145 | +`${extractResolvePackTarballPath()} |
| 146 | +pack_dir="$(mktemp -d)" |
| 147 | +pack_json_file="$pack_dir/pack.json" |
| 148 | +trap 'rm -rf "$pack_dir"' EXIT |
| 149 | +printf '%s' "$PACK_JSON" >"$pack_json_file" |
| 150 | +resolve_pack_tarball_path "$pack_json_file" "$pack_dir"`, |
| 151 | +], |
| 152 | +{ |
| 153 | +encoding: "utf8", |
| 154 | +env: { |
| 155 | +HOME: "/tmp", |
| 156 | +PACK_JSON: JSON.stringify([{ filename }]), |
| 157 | +PATH: process.env.PATH ?? "", |
| 158 | +}, |
| 159 | +}, |
| 160 | +); |
| 161 | +} |
| 162 | + |
129 | 163 | describe("test-install-sh-docker", () => { |
130 | 164 | it("defaults ARM hosts to native arm64 while keeping x64 CI on amd64", () => { |
131 | 165 | expect(runDefaultSmokePlatform({ CI: "true" }, "aarch64")).toBe("linux/arm64"); |
@@ -542,6 +576,41 @@ describe("bun global install smoke", () => {
|
542 | 576 | expect(script).not.toContain('\n rm -rf "$ROOT_DIR/dist"\n'); |
543 | 577 | }); |
544 | 578 | |
| 579 | +it("keeps npm pack tarball paths inside the Bun smoke pack directory", () => { |
| 580 | +const script = readFileSync(BUN_GLOBAL_SMOKE_PATH, "utf8"); |
| 581 | + |
| 582 | +expect(script).toContain("resolve_pack_tarball_path()"); |
| 583 | +expect(script).toContain( |
| 584 | +'PACKAGE_TGZ="$(resolve_pack_tarball_path "$pack_json_file" "$PACK_DIR")"', |
| 585 | +); |
| 586 | +expect(script).toContain("filename !== path.basename(filename)"); |
| 587 | +expect(script).toContain("filename !== path.win32.basename(filename)"); |
| 588 | +expect(script).toContain("npm pack reported unsafe tarball filename"); |
| 589 | +}); |
| 590 | + |
| 591 | +it("rejects path-like npm pack tarball filenames in Bun smoke metadata", () => { |
| 592 | +const safeResult = runResolvePackTarballPath("openclaw-2026.6.17.tgz"); |
| 593 | + |
| 594 | +expect(safeResult.status).toBe(0); |
| 595 | +expect(safeResult.stdout).toMatch(/\/openclaw-2026\.6\.17\.tgz$/u); |
| 596 | + |
| 597 | +const unsafeFilenames = [ |
| 598 | +"../openclaw.tgz", |
| 599 | +"nested/openclaw.tgz", |
| 600 | +"nested\\openclaw.tgz", |
| 601 | +"/tmp/openclaw.tgz", |
| 602 | +"C:\\temp\\openclaw.tgz", |
| 603 | +"openclaw.tar.gz", |
| 604 | +]; |
| 605 | + |
| 606 | +for (const filename of unsafeFilenames) { |
| 607 | +const result = runResolvePackTarballPath(filename); |
| 608 | + |
| 609 | +expect(result.status, filename).not.toBe(0); |
| 610 | +expect(result.stderr, filename).toContain("npm pack reported unsafe tarball filename"); |
| 611 | +} |
| 612 | +}); |
| 613 | + |
545 | 614 | it("gates workflow Bun install smoke to scheduled and release-check runs", () => { |
546 | 615 | const workflow = readFileSync(INSTALL_SMOKE_WORKFLOW_PATH, "utf8"); |
547 | 616 | const releaseChecks = readFileSync(RELEASE_CHECKS_WORKFLOW_PATH, "utf8"); |
|