Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
dd19c4c7f2 fix(install): handle invalid binary path assertions gracefully
Replace panic-inducing assertions with graceful error handling in binary linking code.
The previous code would panic if relative paths from .bin directory to target binary
didn't start with ".." or "..\\" (on Windows), which could happen with malformed
packages or unusual filesystem layouts.

Changes:
- Replace bun.assertWithLocation() with conditional error handling
- Return error.InvalidBinPath instead of panicking
- Add regression test to prevent future panics

Fixes Windows AddCommand panic at install/bin.zig:704:83

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 22:41:43 +00:00
2 changed files with 106 additions and 2 deletions

View File

@@ -704,7 +704,13 @@ pub const Bin = extern struct {
defer bunx_file.close();
const rel_target = path.relativeBufZ(this.rel_buf, path.dirname(abs_dest, .auto), abs_target);
bun.assertWithLocation(strings.hasPrefixComptime(rel_target, "..\\"), @src());
if (!strings.hasPrefixComptime(rel_target, "..\\")) {
// This could happen if the target is not in a parent directory relative to the .bin directory
// which might indicate a malformed package or filesystem corruption
this.err = error.InvalidBinPath;
return;
}
const rel_target_w = strings.toWPathNormalized(&target_buf, rel_target["..\\".len..]);
@@ -772,7 +778,12 @@ pub const Bin = extern struct {
const abs_dest_dir = path.dirname(abs_dest, .auto);
const rel_target = path.relativeBufZ(this.rel_buf, abs_dest_dir, abs_target);
bun.assertWithLocation(strings.hasPrefixComptime(rel_target, ".."), @src());
if (!strings.hasPrefixComptime(rel_target, "..")) {
// This could happen if the target is not in a parent directory relative to the .bin directory
// which might indicate a malformed package or filesystem corruption
this.err = error.InvalidBinPath;
return;
}
switch (bun.sys.symlink(rel_target, abs_dest)) {
.err => |err| {

View File

@@ -0,0 +1,93 @@
import { describe, expect, it } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { join } from "path";
describe("binary linking with invalid paths", () => {
it("should handle malformed packages without panic", async () => {
// Create a test directory with a malformed package that could cause
// the relative path to not start with ".." or "..\\"
const dir = tempDirWithFiles("malformed-bin-test", {
"package.json": JSON.stringify({
name: "test-app",
dependencies: {
"malformed-bin": "file:./malformed-bin",
},
}),
"malformed-bin/package.json": JSON.stringify({
name: "malformed-bin",
version: "1.0.0",
bin: {
"malformed-bin": "../../evil-script.js", // Path traversal that could escape
},
}),
"malformed-bin/evil-script.js": "#!/usr/bin/env node\nconsole.log('evil');\n",
"evil-script.js": "#!/usr/bin/env node\nconsole.log('should not be accessible');\n",
});
// This should not panic but should handle the error gracefully
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// The installation should not panic (this is the main fix)
expect(stderr).not.toContain("panic");
expect(stderr).not.toContain("Internal assertion failure");
// Print output for debugging
console.log("Exit code:", exitCode);
console.log("STDOUT:", stdout);
console.log("STDERR:", stderr);
// The important thing is no panic occurred
// The exit code behavior may vary based on how Bun handles the error
});
it("should handle packages with normal binary references (baseline)", async () => {
// Normal case that should work fine
const dir = tempDirWithFiles("normal-bin-test", {
"package.json": JSON.stringify({
name: "test-app",
dependencies: {
"normal-bin": "file:./normal-bin",
},
}),
"normal-bin/package.json": JSON.stringify({
name: "normal-bin",
version: "1.0.0",
bin: {
"normal-bin": "./script.js",
},
}),
"normal-bin/script.js": "#!/usr/bin/env node\nconsole.log('normal');\n",
});
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// Should not panic and should succeed
expect(stderr).not.toContain("panic");
expect(stderr).not.toContain("Internal assertion failure");
expect(exitCode).toBe(0);
});
});