mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Merge branch 'main' into ali/security-scanner-stdin
This commit is contained in:
@@ -383,6 +383,7 @@ const PackageCollector = struct {
|
||||
const root_pkg_id: PackageID = 0;
|
||||
const root_deps = pkg_dependencies[root_pkg_id];
|
||||
|
||||
// collect all npm deps from the root package
|
||||
for (root_deps.begin()..root_deps.end()) |_dep_id| {
|
||||
const dep_id: DependencyID = @intCast(_dep_id);
|
||||
const dep_pkg_id = this.manager.lockfile.buffers.resolutions.items[dep_id];
|
||||
@@ -408,6 +409,39 @@ const PackageCollector = struct {
|
||||
.dep_path = dep_path_buf,
|
||||
});
|
||||
}
|
||||
|
||||
// and collect npm deps from workspace packages
|
||||
for (0..pkgs.len) |pkg_idx| {
|
||||
const pkg_id: PackageID = @intCast(pkg_idx);
|
||||
if (pkg_resolutions[pkg_id].tag != .workspace) continue;
|
||||
|
||||
const workspace_deps = pkg_dependencies[pkg_id];
|
||||
for (workspace_deps.begin()..workspace_deps.end()) |_dep_id| {
|
||||
const dep_id: DependencyID = @intCast(_dep_id);
|
||||
const dep_pkg_id = this.manager.lockfile.buffers.resolutions.items[dep_id];
|
||||
|
||||
if (dep_pkg_id == invalid_package_id) continue;
|
||||
|
||||
const dep_res = pkg_resolutions[dep_pkg_id];
|
||||
if (dep_res.tag != .npm) continue;
|
||||
|
||||
if ((try this.dedupe.getOrPut(dep_pkg_id)).found_existing) continue;
|
||||
|
||||
var pkg_path_buf = std.array_list.Managed(PackageID).init(this.manager.allocator);
|
||||
try pkg_path_buf.append(pkg_id);
|
||||
try pkg_path_buf.append(dep_pkg_id);
|
||||
|
||||
var dep_path_buf = std.array_list.Managed(DependencyID).init(this.manager.allocator);
|
||||
try dep_path_buf.append(dep_id);
|
||||
|
||||
try this.queue.writeItem(.{
|
||||
.pkg_id = dep_pkg_id,
|
||||
.dep_id = dep_id,
|
||||
.pkg_path = pkg_path_buf,
|
||||
.dep_path = dep_path_buf,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collectUpdatePackages(this: *PackageCollector) !void {
|
||||
@@ -427,7 +461,7 @@ const PackageCollector = struct {
|
||||
for (0..pkgs.len) |_pkg_id| update_dep_id: {
|
||||
const pkg_id: PackageID = @intCast(_pkg_id);
|
||||
const pkg_res = pkg_resolutions[pkg_id];
|
||||
if (pkg_res.tag != .root) continue;
|
||||
if (pkg_res.tag != .root and pkg_res.tag != .workspace) continue;
|
||||
|
||||
const pkg_deps = pkg_dependencies[pkg_id];
|
||||
for (pkg_deps.begin()..pkg_deps.end()) |_dep_id| {
|
||||
|
||||
276
test/cli/install/bun-security-scanner-workspaces.test.ts
Normal file
276
test/cli/install/bun-security-scanner-workspaces.test.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
import { join } from "node:path";
|
||||
import { getRegistry, startRegistry, stopRegistry } from "./simple-dummy-registry";
|
||||
|
||||
test("security scanner receives packages from workspace dependencies", async () => {
|
||||
const registryUrl = await startRegistry(false);
|
||||
|
||||
try {
|
||||
const registry = getRegistry();
|
||||
if (!registry) {
|
||||
throw new Error("Registry not found");
|
||||
}
|
||||
|
||||
registry.clearRequestLog();
|
||||
registry.setScannerBehavior("none");
|
||||
|
||||
// Create a workspace setup with root package and multiple workspace packages
|
||||
const files = {
|
||||
"package.json": JSON.stringify(
|
||||
{
|
||||
name: "workspace-root",
|
||||
private: true,
|
||||
workspaces: ["packages/*"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"packages/app1/package.json": JSON.stringify(
|
||||
{
|
||||
name: "app1",
|
||||
dependencies: {
|
||||
"left-pad": "1.3.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"packages/app2/package.json": JSON.stringify(
|
||||
{
|
||||
name: "app2",
|
||||
dependencies: {
|
||||
"is-even": "1.0.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"packages/lib1/package.json": JSON.stringify(
|
||||
{
|
||||
name: "lib1",
|
||||
dependencies: {
|
||||
"is-odd": "1.0.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"scanner.js": `export const scanner = {
|
||||
version: "1",
|
||||
scan: async function(payload) {
|
||||
console.error("SCANNER_RAN: " + payload.packages.length + " packages");
|
||||
return [];
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
const dir = tempDirWithFiles("scanner-workspaces", files);
|
||||
|
||||
await Bun.write(
|
||||
join(dir, "bunfig.toml"),
|
||||
`[install]
|
||||
cache.disable = true
|
||||
registry = "${registryUrl}/"
|
||||
|
||||
[install.security]
|
||||
scanner = "./scanner.js"`,
|
||||
);
|
||||
|
||||
const { stdout, stderr } = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const output = (await stdout.text()) + (await stderr.text());
|
||||
|
||||
// The scanner should receive packages from all workspace dependencies
|
||||
expect(output).toContain("SCANNER_RAN:");
|
||||
|
||||
// Extract the number of packages from the output
|
||||
const match = output.match(/SCANNER_RAN: (\d+) packages/);
|
||||
expect(match).toBeTruthy();
|
||||
|
||||
const packagesScanned = parseInt(match![1], 10);
|
||||
// Exact package count: left-pad, is-even, is-odd (is-even <-> is-odd have circular deps)
|
||||
expect(packagesScanned).toBe(3);
|
||||
} finally {
|
||||
stopRegistry();
|
||||
}
|
||||
});
|
||||
|
||||
test("security scanner receives packages from workspace dependencies with hoisted linker", async () => {
|
||||
const registryUrl = await startRegistry(false);
|
||||
|
||||
try {
|
||||
const registry = getRegistry();
|
||||
if (!registry) {
|
||||
throw new Error("Registry not found");
|
||||
}
|
||||
|
||||
registry.clearRequestLog();
|
||||
registry.setScannerBehavior("none");
|
||||
|
||||
const files = {
|
||||
"package.json": JSON.stringify(
|
||||
{
|
||||
name: "workspace-root",
|
||||
private: true,
|
||||
workspaces: ["packages/*"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"packages/app1/package.json": JSON.stringify(
|
||||
{
|
||||
name: "app1",
|
||||
dependencies: {
|
||||
"left-pad": "1.3.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"packages/app2/package.json": JSON.stringify(
|
||||
{
|
||||
name: "app2",
|
||||
dependencies: {
|
||||
"is-even": "1.0.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"scanner.js": `export const scanner = {
|
||||
version: "1",
|
||||
scan: async function(payload) {
|
||||
console.error("SCANNER_RAN: " + payload.packages.length + " packages");
|
||||
return [];
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
const dir = tempDirWithFiles("scanner-workspaces-hoisted", files);
|
||||
|
||||
await Bun.write(
|
||||
join(dir, "bunfig.toml"),
|
||||
`[install]
|
||||
cache.disable = true
|
||||
linker = "hoisted"
|
||||
registry = "${registryUrl}/"
|
||||
|
||||
[install.security]
|
||||
scanner = "./scanner.js"`,
|
||||
);
|
||||
|
||||
const { stdout, stderr } = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const output = (await stdout.text()) + (await stderr.text());
|
||||
|
||||
expect(output).toContain("SCANNER_RAN:");
|
||||
|
||||
const match = output.match(/SCANNER_RAN: (\d+) packages/);
|
||||
expect(match).toBeTruthy();
|
||||
|
||||
const packagesScanned = parseInt(match![1], 10);
|
||||
// Exact package count: left-pad, is-even, is-odd (is-even <-> is-odd have circular deps)
|
||||
expect(packagesScanned).toBe(3);
|
||||
} finally {
|
||||
stopRegistry();
|
||||
}
|
||||
});
|
||||
|
||||
test("security scanner receives packages from workspace dependencies with isolated linker", async () => {
|
||||
const registryUrl = await startRegistry(false);
|
||||
|
||||
try {
|
||||
const registry = getRegistry();
|
||||
if (!registry) {
|
||||
throw new Error("Registry not found");
|
||||
}
|
||||
|
||||
registry.clearRequestLog();
|
||||
registry.setScannerBehavior("none");
|
||||
|
||||
const files = {
|
||||
"package.json": JSON.stringify(
|
||||
{
|
||||
name: "workspace-root",
|
||||
private: true,
|
||||
workspaces: ["packages/*"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"packages/app1/package.json": JSON.stringify(
|
||||
{
|
||||
name: "app1",
|
||||
dependencies: {
|
||||
"left-pad": "1.3.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"packages/app2/package.json": JSON.stringify(
|
||||
{
|
||||
name: "app2",
|
||||
dependencies: {
|
||||
"is-even": "1.0.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"scanner.js": `export const scanner = {
|
||||
version: "1",
|
||||
scan: async function(payload) {
|
||||
console.error("SCANNER_RAN: " + payload.packages.length + " packages");
|
||||
return [];
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
const dir = tempDirWithFiles("scanner-workspaces-isolated", files);
|
||||
|
||||
await Bun.write(
|
||||
join(dir, "bunfig.toml"),
|
||||
`[install]
|
||||
cache.disable = true
|
||||
linker = "isolated"
|
||||
registry = "${registryUrl}/"
|
||||
|
||||
[install.security]
|
||||
scanner = "./scanner.js"`,
|
||||
);
|
||||
|
||||
const { stdout, stderr } = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const output = (await stdout.text()) + (await stderr.text());
|
||||
|
||||
expect(output).toContain("SCANNER_RAN:");
|
||||
|
||||
const match = output.match(/SCANNER_RAN: (\d+) packages/);
|
||||
expect(match).toBeTruthy();
|
||||
|
||||
const packagesScanned = parseInt(match![1], 10);
|
||||
// Exact package count: left-pad, is-even, is-odd (is-even <-> is-odd have circular deps)
|
||||
expect(packagesScanned).toBe(3);
|
||||
} finally {
|
||||
stopRegistry();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user