Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/install/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3352,7 +3352,10 @@ impl Lockfile {
};
let hash = SemverStringBuilder::string_hash(trusted_name) as u32;
let name_is_trusted = match trusted_dependencies.get(&hash) {
Some(name) => !name.is_empty() && **name == *trusted_name,
// Empty stored name is the legacy bun.lockb sentinel (the
// binary format stores only truncated hashes, no names); it
// means "hash-only match" and must accept.
Some(name) => name.is_empty() || **name == *trusted_name,
None => false,
};
if !name_is_trusted {
Expand Down
68 changes: 68 additions & 0 deletions test/cli/install/bun-lockb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,71 @@ it("rejects a binary lockfile whose git resolved tag contains path separators",
expect(await exists(join(packageDir, "node_modules", "dep"))).toBe(false);
expect(code).not.toBe(0);
});

it("honors trustedDependencies loaded from a binary lockfile in `bun pm untrusted`/`trust`", async () => {
const { packageDir, packageJson } = await registry.createTestDir({ bunfigOpts: { saveTextLockfile: false } });

// The postinstall appends a line so we can count how many times it ran.
const ran = join(packageDir, "ran.txt");
await write(
join(packageDir, "dep", "package.json"),
JSON.stringify({
name: "dep",
version: "1.0.0",
scripts: { postinstall: `${bunExe()} -e "require('fs').appendFileSync('../../ran.txt', 'RAN\\n')"` },
}),
);
await write(
packageJson,
JSON.stringify({
name: "lockb-trusted",
version: "1.0.0",
dependencies: { dep: "file:./dep" },
trustedDependencies: ["dep"],
}),
);

await runBunInstall(env, packageDir);
expect(await exists(join(packageDir, "bun.lockb"))).toBe(true);
expect(await exists(join(packageDir, "bun.lock"))).toBe(false);
expect(await file(ran).text()).toBe("RAN\n");

// `bun pm untrusted` must report zero: the only script-bearing package is
// listed in trustedDependencies. The binary lockfile stores only truncated
// name hashes; the loader's empty-name sentinel must be treated as a match.
{
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "pm", "untrusted"],
cwd: packageDir,
stdout: "pipe",
stderr: "pipe",
env,
});
const [out, rawErr, code] = await Promise.all([stdout.text(), stderr.text(), exited]);
const err = stderrForInstall(rawErr);
expect(err).not.toContain("error:");
expect(out).toContain("Found 0 untrusted dependencies with scripts");
expect(out).not.toContain("node_modules/dep");
expect(code).toBe(0);
}

// `bun pm trust dep` must refuse (already trusted) and must not re-run the
// postinstall.
{
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "pm", "trust", "dep"],
cwd: packageDir,
stdout: "pipe",
stderr: "pipe",
env,
});
const [out, rawErr, code] = await Promise.all([stdout.text(), stderr.text(), exited]);
const err = stderrForInstall(rawErr);
expect(err).toContain("0 scripts ran");
expect(err).toContain("already trusted");
expect(out).not.toContain("postinstall");
expect(code).toBe(1);
}

expect(await file(ran).text()).toBe("RAN\n");
});
Loading