Compare commits

...

29 Commits

Author SHA1 Message Date
Alistair Smith
7809d12675 Merge branch 'main' into ali/security-scanner-stdin 2026-01-05 16:06:45 +00:00
Alistair Smith
bec68a5317 Merge branch 'main' of github.com:oven-sh/bun into ali/security-scanner-stdin 2025-12-05 10:00:29 -08:00
Alistair Smith
1cd34c1b17 try to run on windows 2025-12-01 16:10:24 -08:00
Alistair Smith
bc00cf1975 comment 2025-12-01 16:08:47 -08:00
Alistair Smith
36d8ea81c0 fix on posix 2025-12-01 15:17:29 -08:00
Alistair Smith
0702666bef Merge branch 'main' into ali/security-scanner-stdin 2025-12-01 12:09:02 -08:00
Alistair Smith
add3745cc4 try this on windows? 2025-11-25 22:59:08 -08:00
Alistair Smith
47243f203a Refactor IPC handling in scanner-entry for Windows and POSIX
- Updated IPC message handling to differentiate between Windows and POSIX systems.
- Introduced `sendAndExit` function to streamline message sending and process termination.
- Enhanced error handling for package reading and JSON parsing.
- Adjusted `SecurityScanSubprocess` to utilize NODE_CHANNEL_FD for Windows IPC communication.

This refactor improves the robustness and clarity of the IPC implementation in the package manager.
2025-11-25 16:53:33 -08:00
Alistair Smith
4e81b7c443 Merge branch 'main' into ali/security-scanner-stdin 2025-11-25 15:26:11 -08:00
Alistair Smith
dcc6e7ba0c try init as zero 2025-11-25 15:22:24 -08:00
Alistair Smith
432400471a try ipc not bufer 2025-11-24 19:07:05 -08:00
Alistair Smith
0a70826c05 Merge branch 'main' of github.com:oven-sh/bun into ali/security-scanner-stdin 2025-11-24 17:58:53 -08:00
Alistair Smith
aa63354af1 errdefer 2025-11-24 17:58:41 -08:00
Alistair Smith
cded70abdc try this way to fix on windows 2025-11-24 14:31:10 -08:00
Alistair Smith
d2db5f962d migrate from managed 2025-11-21 18:45:52 -08:00
Alistair Smith
d4e0930995 Merge branch 'main' into ali/security-scanner-stdin 2025-11-21 18:37:34 -08:00
Alistair Smith
eb5291ce8a migrate 2025-11-21 18:13:00 -08:00
Alistair Smith
e3c72bfa4c Merge branch 'main' of github.com:oven-sh/bun into ali/security-scanner-stdin 2025-11-21 17:49:30 -08:00
Alistair Smith
dfe70c5b86 try this 2025-11-21 17:48:59 -08:00
Alistair Smith
587551f170 change 2025-10-15 21:28:15 -07:00
Alistair Smith
8fca2a0ca8 try that 2025-10-15 21:27:49 -07:00
Alistair Smith
5cb28aac3f try: use buffer fd on windows 2025-10-15 20:47:04 -07:00
Alistair Smith
35e5aad611 rm 2025-10-15 19:08:45 -07:00
Alistair Smith
e2a9266154 try write to fd 4 2025-10-15 17:54:44 -07:00
Alistair Smith
f177d2d1d2 Merge branch 'main' into ali/security-scanner-stdin 2025-10-15 14:58:54 -07:00
Alistair Smith
2f91c1ff4c use writeAndExit 2025-10-15 14:58:31 -07:00
Alistair Smith
1c083fd75e coderabbit nit 2025-10-15 14:52:24 -07:00
autofix-ci[bot]
94a82d8abf [autofix.ci] apply automated fixes 2025-10-15 21:25:18 +00:00
Alistair Smith
8a8bf552f4 Fix: Receive data in security scanner via stdin not args 2025-10-15 14:23:24 -07:00
5 changed files with 335 additions and 110 deletions

View File

@@ -149,6 +149,7 @@ pub const FilePoll = struct {
const Subprocess = jsc.Subprocess;
const StaticPipeWriter = Subprocess.StaticPipeWriter.Poll;
const ShellStaticPipeWriter = bun.shell.ShellSubprocess.StaticPipeWriter.Poll;
const SecurityScanSubprocessStaticPipeWriter = bun.install.SecurityScanSubprocess.StaticPipeWriter.Poll;
const FileSink = jsc.WebCore.FileSink.Poll;
const TerminalPoll = bun.api.Terminal.Poll;
const DNSResolver = bun.api.dns.Resolver;
@@ -171,6 +172,7 @@ pub const FilePoll = struct {
StaticPipeWriter,
ShellStaticPipeWriter,
SecurityScanSubprocessStaticPipeWriter,
// ShellBufferedWriter,
@@ -373,6 +375,10 @@ pub const FilePoll = struct {
var handler: *StaticPipeWriter = ptr.as(StaticPipeWriter);
handler.onPoll(size_or_offset, poll.flags.contains(.hup));
},
@field(Owner.Tag, @typeName(SecurityScanSubprocessStaticPipeWriter)) => {
var handler: *SecurityScanSubprocessStaticPipeWriter = ptr.as(SecurityScanSubprocessStaticPipeWriter);
handler.onPoll(size_or_offset, poll.flags.contains(.hup));
},
@field(Owner.Tag, @typeName(FileSink)) => {
var handler: *FileSink = ptr.as(FileSink);
handler.onPoll(size_or_offset, poll.flags.contains(.hup));

View File

@@ -1,7 +1,6 @@
import fs from "node:fs";
const scannerModuleName = "__SCANNER_MODULE__";
const packages = __PACKAGES_JSON__;
const suppressError = __SUPPRESS_ERROR__;
type IPCMessage =
@@ -10,26 +9,55 @@ type IPCMessage =
| { type: "error"; code: "INVALID_VERSION"; message: string }
| { type: "error"; code: "SCAN_FAILED"; message: string };
const IPC_PIPE_FD = 3;
// Two pipes for IPC:
// - fd 3: output - child writes response here
// - fd 4: input - child reads JSON package list here (reads until EOF)
const IPC_OUTPUT_FD = 3;
const IPC_INPUT_FD = 4;
function writeAndExit(message: IPCMessage): never {
function sendAndExit(message: IPCMessage): never {
const data = JSON.stringify(message);
for (let remaining = data; remaining.length > 0; ) {
const written = fs.writeSync(IPC_PIPE_FD, remaining);
const written = fs.writeSync(IPC_OUTPUT_FD, remaining);
if (written === 0) {
console.error("Failed to write to IPC pipe");
process.exit(1);
}
remaining = remaining.slice(written);
}
fs.closeSync(IPC_PIPE_FD);
fs.closeSync(IPC_OUTPUT_FD);
process.exit(message.type === "error" ? 1 : 0);
}
// Read packages JSON from fd 4 (reads until EOF when parent closes the pipe)
let packages: Bun.Security.Package[];
let packagesJson: string = "";
try {
packagesJson = await Bun.file(IPC_INPUT_FD).text();
} catch (error) {
const message = `Failed to read packages from FD ${IPC_INPUT_FD}: ${error instanceof Error ? error.message : String(error)}`;
sendAndExit({
type: "error",
code: "SCAN_FAILED",
message,
});
}
try {
packages = JSON.parse(packagesJson);
if (!Array.isArray(packages)) {
throw new Error("Expected packages to be an array");
}
} catch (error) {
const message = `Failed to parse packages JSON: ${error instanceof Error ? error.message : String(error)}`;
sendAndExit({
type: "error",
code: "SCAN_FAILED",
message,
});
}
let scanner: Bun.Security.Scanner;
try {
@@ -41,13 +69,13 @@ try {
console.error(msg);
}
writeAndExit({
sendAndExit({
type: "error",
code: "MODULE_NOT_FOUND",
module: scannerModuleName,
});
} else {
writeAndExit({
sendAndExit({
type: "error",
code: "SCAN_FAILED",
message: error instanceof Error ? error.message : String(error),
@@ -61,7 +89,7 @@ try {
}
if (scanner.version !== "1") {
writeAndExit({
sendAndExit({
type: "error",
code: "INVALID_VERSION",
message: `Security scanner must be version 1, got version ${scanner.version}`,
@@ -78,13 +106,13 @@ try {
throw new Error("Security scanner must return an array of advisories");
}
writeAndExit({ type: "result", advisories: result });
sendAndExit({ type: "result", advisories: result });
} catch (error) {
if (!suppressError) {
console.error(error);
}
writeAndExit({
sendAndExit({
type: "error",
code: "SCAN_FAILED",
message: error instanceof Error ? error.message : "Unknown error occurred",

View File

@@ -350,8 +350,8 @@ const PackageCollector = struct {
const QueueItem = struct {
pkg_id: PackageID,
dep_id: DependencyID,
pkg_path: std.array_list.Managed(PackageID),
dep_path: std.array_list.Managed(DependencyID),
pkg_path: std.ArrayList(PackageID),
dep_path: std.ArrayList(DependencyID),
};
pub fn init(manager: *PackageManager) PackageCollector {
@@ -395,12 +395,12 @@ const PackageCollector = struct {
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(root_pkg_id);
try pkg_path_buf.append(dep_pkg_id);
var pkg_path_buf: std.ArrayList(PackageID) = .{};
try pkg_path_buf.append(this.manager.allocator, root_pkg_id);
try pkg_path_buf.append(this.manager.allocator, dep_pkg_id);
var dep_path_buf = std.array_list.Managed(DependencyID).init(this.manager.allocator);
try dep_path_buf.append(dep_id);
var dep_path_buf: std.ArrayList(DependencyID) = .{};
try dep_path_buf.append(this.manager.allocator, dep_id);
try this.queue.writeItem(.{
.pkg_id = dep_pkg_id,
@@ -427,12 +427,12 @@ const PackageCollector = struct {
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 pkg_path_buf: std.ArrayList(PackageID) = .{};
try pkg_path_buf.append(this.manager.allocator, pkg_id);
try pkg_path_buf.append(this.manager.allocator, dep_pkg_id);
var dep_path_buf = std.array_list.Managed(DependencyID).init(this.manager.allocator);
try dep_path_buf.append(dep_id);
var dep_path_buf: std.ArrayList(DependencyID) = .{};
try dep_path_buf.append(this.manager.allocator, dep_id);
try this.queue.writeItem(.{
.pkg_id = dep_pkg_id,
@@ -479,14 +479,14 @@ const PackageCollector = struct {
if (update_dep_id == invalid_dependency_id) continue;
if ((try this.dedupe.getOrPut(update_pkg_id)).found_existing) continue;
var initial_pkg_path = std.array_list.Managed(PackageID).init(this.manager.allocator);
var initial_pkg_path: std.ArrayList(PackageID) = .{};
if (parent_pkg_id != invalid_package_id) {
try initial_pkg_path.append(parent_pkg_id);
try initial_pkg_path.append(this.manager.allocator, parent_pkg_id);
}
try initial_pkg_path.append(update_pkg_id);
try initial_pkg_path.append(this.manager.allocator, update_pkg_id);
var initial_dep_path = std.array_list.Managed(DependencyID).init(this.manager.allocator);
try initial_dep_path.append(update_dep_id);
var initial_dep_path: std.ArrayList(DependencyID) = .{};
try initial_dep_path.append(this.manager.allocator, update_dep_id);
try this.queue.writeItem(.{
.pkg_id = update_pkg_id,
@@ -504,17 +504,18 @@ const PackageCollector = struct {
const pkg_dependencies = pkgs.items(.dependencies);
while (this.queue.readItem()) |item| {
defer item.pkg_path.deinit();
defer item.dep_path.deinit();
var mutable_item = item;
defer mutable_item.pkg_path.deinit(this.manager.allocator);
defer mutable_item.dep_path.deinit(this.manager.allocator);
const pkg_id = item.pkg_id;
_ = item.dep_id; // Could be useful in the future for dependency-specific processing
const pkg_id = mutable_item.pkg_id;
_ = mutable_item.dep_id; // Could be useful in the future for dependency-specific processing
const pkg_path_copy = try this.manager.allocator.alloc(PackageID, item.pkg_path.items.len);
@memcpy(pkg_path_copy, item.pkg_path.items);
const pkg_path_copy = try this.manager.allocator.alloc(PackageID, mutable_item.pkg_path.items.len);
@memcpy(pkg_path_copy, mutable_item.pkg_path.items);
const dep_path_copy = try this.manager.allocator.alloc(DependencyID, item.dep_path.items.len);
@memcpy(dep_path_copy, item.dep_path.items);
const dep_path_copy = try this.manager.allocator.alloc(DependencyID, mutable_item.dep_path.items.len);
@memcpy(dep_path_copy, mutable_item.dep_path.items);
try this.package_paths.put(pkg_id, .{
.pkg_path = pkg_path_copy,
@@ -533,13 +534,13 @@ const PackageCollector = struct {
if ((try this.dedupe.getOrPut(next_pkg_id)).found_existing) continue;
var extended_pkg_path = std.array_list.Managed(PackageID).init(this.manager.allocator);
try extended_pkg_path.appendSlice(item.pkg_path.items);
try extended_pkg_path.append(next_pkg_id);
var extended_pkg_path: std.ArrayList(PackageID) = .{};
try extended_pkg_path.appendSlice(this.manager.allocator, mutable_item.pkg_path.items);
try extended_pkg_path.append(this.manager.allocator, next_pkg_id);
var extended_dep_path = std.array_list.Managed(DependencyID).init(this.manager.allocator);
try extended_dep_path.appendSlice(item.dep_path.items);
try extended_dep_path.append(next_dep_id);
var extended_dep_path: std.ArrayList(DependencyID) = .{};
try extended_dep_path.appendSlice(this.manager.allocator, mutable_item.dep_path.items);
try extended_dep_path.append(this.manager.allocator, next_dep_id);
try this.queue.writeItem(.{
.pkg_id = next_pkg_id,
@@ -557,8 +558,8 @@ const JSONBuilder = struct {
collector: *PackageCollector,
pub fn buildPackageJSON(this: JSONBuilder) ![]const u8 {
var json_buf = std.array_list.Managed(u8).init(this.manager.allocator);
var writer = json_buf.writer();
var json_buf: std.ArrayList(u8) = .{};
var writer = json_buf.writer(this.manager.allocator);
const pkgs = this.manager.lockfile.packages.slice();
const pkg_names = pkgs.items(.name);
@@ -615,7 +616,7 @@ const JSONBuilder = struct {
}
try writer.writeAll("\n]");
return json_buf.toOwnedSlice();
return json_buf.toOwnedSlice(this.manager.allocator);
}
};
@@ -660,37 +661,26 @@ fn attemptSecurityScanWithRetry(manager: *PackageManager, security_scanner: []co
const json_data = try json_builder.buildPackageJSON();
defer manager.allocator.free(json_data);
var code = std.array_list.Managed(u8).init(manager.allocator);
defer code.deinit();
var code: std.ArrayList(u8) = .{};
defer code.deinit(manager.allocator);
var temp_source: []const u8 = scanner_entry_source;
const scanner_placeholder = "__SCANNER_MODULE__";
if (std.mem.indexOf(u8, temp_source, scanner_placeholder)) |index| {
try code.appendSlice(temp_source[0..index]);
try code.appendSlice(security_scanner);
try code.appendSlice(temp_source[index + scanner_placeholder.len ..]);
temp_source = code.items;
}
const packages_placeholder = "__PACKAGES_JSON__";
if (std.mem.indexOf(u8, temp_source, packages_placeholder)) |index| {
var new_code = std.array_list.Managed(u8).init(manager.allocator);
try new_code.appendSlice(temp_source[0..index]);
try new_code.appendSlice(json_data);
try new_code.appendSlice(temp_source[index + packages_placeholder.len ..]);
code.deinit();
code = new_code;
try code.appendSlice(manager.allocator, temp_source[0..index]);
try code.appendSlice(manager.allocator, security_scanner);
try code.appendSlice(manager.allocator, temp_source[index + scanner_placeholder.len ..]);
temp_source = code.items;
}
const suppress_placeholder = "__SUPPRESS_ERROR__";
if (std.mem.indexOf(u8, temp_source, suppress_placeholder)) |index| {
var new_code = std.array_list.Managed(u8).init(manager.allocator);
try new_code.appendSlice(temp_source[0..index]);
try new_code.appendSlice(if (suppress_error_output) "true" else "false");
try new_code.appendSlice(temp_source[index + suppress_placeholder.len ..]);
code.deinit();
var new_code: std.ArrayList(u8) = .{};
try new_code.appendSlice(manager.allocator, temp_source[0..index]);
try new_code.appendSlice(manager.allocator, if (suppress_error_output) "true" else "false");
try new_code.appendSlice(manager.allocator, temp_source[index + suppress_placeholder.len ..]);
code.deinit(manager.allocator);
code = new_code;
}
@@ -730,28 +720,22 @@ pub const SecurityScanSubprocess = struct {
json_data: []const u8,
process: ?*bun.spawn.Process = null,
ipc_reader: bun.io.BufferedReader = bun.io.BufferedReader.init(@This()),
ipc_data: std.array_list.Managed(u8),
stderr_data: std.array_list.Managed(u8),
ipc_data: std.ArrayList(u8),
stderr_data: std.ArrayList(u8),
has_process_exited: bool = false,
has_received_ipc: bool = false,
exit_status: ?bun.spawn.Status = null,
remaining_fds: i8 = 0,
stdin_writer: ?*StaticPipeWriter = null,
pub const new = bun.TrivialNew(@This());
pub const StaticPipeWriter = jsc.Subprocess.NewStaticPipeWriter(@This());
pub fn spawn(this: *SecurityScanSubprocess) !void {
this.ipc_data = std.array_list.Managed(u8).init(this.manager.allocator);
this.stderr_data = std.array_list.Managed(u8).init(this.manager.allocator);
this.ipc_data = .{};
this.stderr_data = .{};
this.ipc_reader.setParent(this);
const pipe_result = bun.sys.pipe();
const pipe_fds = switch (pipe_result) {
.err => {
return error.IPCPipeFailed;
},
.result => |fds| fds,
};
const exec_path = try bun.selfExePath();
var argv = [_]?[*:0]const u8{
@@ -768,35 +752,148 @@ pub const SecurityScanSubprocess = struct {
const spawn_cwd = FileSystem.instance.top_level_dir;
if (comptime bun.Environment.isWindows) {
try this.spawnWindows(&argv, spawn_cwd);
} else {
try this.spawnPosix(&argv, spawn_cwd);
}
}
fn spawnPosix(this: *SecurityScanSubprocess, argv: *[5]?[*:0]const u8, spawn_cwd: []const u8) !void {
// Create two pipes for IPC:
// - fd 3: child writes response → parent reads (raw pipe)
// - fd 4: parent writes JSON → child reads (socketpair, closed after write = EOF)
const ipc_output_fds = switch (bun.sys.pipe()) {
.err => return error.IPCPipeFailed,
.result => |fds| fds,
};
const extra_fds = [_]bun.spawn.SpawnOptions.Stdio{
.{ .pipe = ipc_output_fds[1] }, // fd 3: child writes here
.ipc, // fd 4: parent writes JSON here
};
const spawn_options = bun.spawn.SpawnOptions{
.stdout = .inherit,
.stderr = .inherit,
.stdin = .inherit,
.cwd = spawn_cwd,
.extra_fds = &.{.{ .pipe = pipe_fds[1] }},
.windows = if (Environment.isWindows) .{
.loop = jsc.EventLoopHandle.init(&this.manager.event_loop),
.extra_fds = &extra_fds,
};
var spawned = try (try bun.spawn.spawnProcess(&spawn_options, @ptrCast(argv), @ptrCast(std.os.environ.ptr))).unwrap();
// Close the write end of the output pipe in the parent
ipc_output_fds[1].close();
// Get the fd for reading child's output and the socketpair for writing JSON
const ipc_read_fd = ipc_output_fds[0];
const json_write_fd = spawned.extra_pipes.items[1];
this.remaining_fds = 1;
_ = bun.sys.setNonblocking(ipc_read_fd);
this.ipc_reader.flags.nonblocking = true;
this.ipc_reader.flags.socket = false;
try this.ipc_reader.start(ipc_read_fd, true).unwrap();
// Set up process and write JSON data to child
var process = spawned.toProcess(&this.manager.event_loop, false);
this.process = process;
process.setExitHandler(this);
const json_data_copy = try this.manager.allocator.dupe(u8, this.json_data);
const json_source = jsc.Subprocess.Source{
.blob = jsc.WebCore.Blob.Any.fromOwnedSlice(this.manager.allocator, json_data_copy),
};
this.stdin_writer = StaticPipeWriter.create(&this.manager.event_loop, this, json_write_fd, json_source);
errdefer {
if (this.stdin_writer) |writer| {
writer.source.detach();
writer.deref();
this.stdin_writer = null;
}
}
switch (this.stdin_writer.?.start()) {
.err => |err| {
Output.errGeneric("Failed to start JSON pipe writer: {f}", .{err});
return error.JSONPipeWriterFailed;
},
.result => {},
}
switch (process.watchOrReap()) {
.err => {
return error.ProcessWatchFailed;
},
.result => {},
}
}
fn spawnWindows(this: *SecurityScanSubprocess, argv: *[5]?[*:0]const u8, spawn_cwd: []const u8) !void {
const uv = bun.windows.libuv;
// On Windows, we use two libuv pipes for IPC:
// - fd 3: child writes response → parent reads (buffer pipe)
// - fd 4: parent writes JSON → child reads (ipc pipe)
const output_pipe = try bun.default_allocator.create(uv.Pipe);
errdefer bun.default_allocator.destroy(output_pipe);
const input_pipe = try bun.default_allocator.create(uv.Pipe);
errdefer bun.default_allocator.destroy(input_pipe);
const extra_fds = [_]bun.spawn.SpawnOptions.Stdio{
.{ .buffer = output_pipe }, // fd 3: child writes here
.{ .ipc = input_pipe }, // fd 4: parent writes JSON here
};
const spawn_options = bun.spawn.SpawnOptions{
.stdout = .inherit,
.stderr = .inherit,
.stdin = .inherit,
.cwd = spawn_cwd,
.extra_fds = &extra_fds,
.windows = .{
.loop = .{ .mini = &this.manager.event_loop.mini },
},
};
var spawned = try (try bun.spawn.spawnProcess(&spawn_options, @ptrCast(&argv), @ptrCast(std.os.environ.ptr))).unwrap();
var spawned = try (try bun.spawn.spawnProcess(&spawn_options, @ptrCast(argv), @ptrCast(std.os.environ.ptr))).unwrap();
pipe_fds[1].close();
const result_output_pipe = spawned.extra_pipes.items[0].buffer;
const result_input_pipe = spawned.extra_pipes.items[1].buffer;
if (comptime bun.Environment.isPosix) {
_ = bun.sys.setNonblocking(pipe_fds[0]);
}
this.remaining_fds = 1;
this.ipc_reader.flags.nonblocking = true;
if (comptime bun.Environment.isPosix) {
this.ipc_reader.flags.socket = false;
}
try this.ipc_reader.start(pipe_fds[0], true).unwrap();
try this.ipc_reader.startWithPipe(result_output_pipe).unwrap();
var process = spawned.toProcess(&this.manager.event_loop, false);
this.process = process;
process.setExitHandler(this);
const json_data_copy = try this.manager.allocator.dupe(u8, this.json_data);
const json_source = jsc.Subprocess.Source{
.blob = jsc.WebCore.Blob.Any.fromOwnedSlice(this.manager.allocator, json_data_copy),
};
const stdio_result = jsc.Subprocess.StdioResult{ .buffer = result_input_pipe };
this.stdin_writer = StaticPipeWriter.create(&this.manager.event_loop, this, stdio_result, json_source);
errdefer {
if (this.stdin_writer) |writer| {
writer.source.detach();
writer.deref();
this.stdin_writer = null;
}
}
switch (this.stdin_writer.?.start()) {
.err => |err| {
Output.errGeneric("Failed to start JSON pipe writer: {f}", .{err});
return error.JSONPipeWriterFailed;
},
.result => {},
}
switch (process.watchOrReap()) {
.err => {
return error.ProcessWatchFailed;
@@ -809,6 +906,14 @@ pub const SecurityScanSubprocess = struct {
return this.has_process_exited and this.remaining_fds == 0;
}
pub fn onCloseIO(this: *SecurityScanSubprocess, _: jsc.Subprocess.StdioKind) void {
if (this.stdin_writer) |writer| {
writer.source.detach();
writer.deref();
this.stdin_writer = null;
}
}
pub fn eventLoop(this: *const SecurityScanSubprocess) *jsc.AnyEventLoop {
return &this.manager.event_loop;
}
@@ -833,7 +938,7 @@ pub const SecurityScanSubprocess = struct {
}
pub fn onStderrChunk(this: *SecurityScanSubprocess, chunk: []const u8) void {
bun.handleOom(this.stderr_data.appendSlice(chunk));
bun.handleOom(this.stderr_data.appendSlice(this.manager.allocator, chunk));
}
pub fn getReadBuffer(this: *SecurityScanSubprocess) []u8 {
@@ -847,7 +952,7 @@ pub const SecurityScanSubprocess = struct {
pub fn onReadChunk(this: *SecurityScanSubprocess, chunk: []const u8, hasMore: bun.io.ReadState) bool {
_ = hasMore;
bun.handleOom(this.ipc_data.appendSlice(chunk));
bun.handleOom(this.ipc_data.appendSlice(this.manager.allocator, chunk));
return true;
}
@@ -865,8 +970,8 @@ pub const SecurityScanSubprocess = struct {
_ = command_ctx; // Reserved for future use
_ = original_cwd; // Reserved for future use
defer {
this.ipc_data.deinit();
this.stderr_data.deinit();
this.ipc_data.deinit(this.manager.allocator);
this.stderr_data.deinit(this.manager.allocator);
}
if (this.exit_status == null) {
@@ -1076,8 +1181,8 @@ pub const SecurityScanSubprocess = struct {
};
fn parseSecurityAdvisoriesFromExpr(manager: *PackageManager, advisories_expr: bun.js_parser.Expr, package_paths: *std.AutoArrayHashMap(PackageID, PackagePath)) ![]SecurityAdvisory {
var advisories_list = std.array_list.Managed(SecurityAdvisory).init(manager.allocator);
defer advisories_list.deinit();
var advisories_list: std.ArrayList(SecurityAdvisory) = .{};
defer advisories_list.deinit(manager.allocator);
if (advisories_expr.data != .e_array) {
Output.errGeneric("Security scanner 'advisories' field must be an array, got: {s}", .{@tagName(advisories_expr.data)});
@@ -1170,10 +1275,10 @@ fn parseSecurityAdvisoriesFromExpr(manager: *PackageManager, advisories_expr: bu
.pkg_path = pkg_path,
};
try advisories_list.append(advisory);
try advisories_list.append(manager.allocator, advisory);
}
return try advisories_list.toOwnedSlice();
return try advisories_list.toOwnedSlice(manager.allocator);
}
const HoistedInstall = @import("../hoisted_install.zig");

View File

@@ -30,13 +30,15 @@ function test(
bunfigScanner?: string | false;
packages?: string[];
scannerFile?: string;
packageJson?: object;
customRegistry?: (urls: string[]) => any;
},
) {
it(
name,
async () => {
const urls: string[] = [];
setHandler(dummyRegistry(urls));
setHandler(options.customRegistry ? options.customRegistry(urls) : dummyRegistry(urls));
const scannerPath = options.scannerFile || "./scanner.ts";
if (typeof options.scanner === "string") {
@@ -55,11 +57,14 @@ function test(
await write("./bunfig.toml", `${bunfig}\n[install.security]\nscanner = "${scannerPath}"`);
}
await write("package.json", {
name: "my-app",
version: "1.0.0",
dependencies: {},
});
await write(
"package.json",
options.packageJson ?? {
name: "my-app",
version: "1.0.0",
dependencies: {},
},
);
const expectedExitCode = options.expectedExitCode ?? (options.fails ? 1 : 0);
const packages = options.packages ?? ["bar"];
@@ -681,3 +686,78 @@ describe("Package Resolution", () => {
},
});
});
describe("Large payload via ipc pipes", () => {
let tgzTempDir: string;
beforeAll(async () => {
const { tmpdir } = await import("node:os");
const { mkdtemp } = await import("node:fs/promises");
const { join } = await import("node:path");
tgzTempDir = await mkdtemp(join(tmpdir(), "bun-test-tgz-"));
// Copy bar-0.0.2.tgz and create test-pkg-* copies in temp dir
const testDir = import.meta.dir;
const barTarball = `${testDir}/bar-0.0.2.tgz`;
const barContent = Bun.file(barTarball);
// Create 10,000 packages to generate ~1.25MB of JSON
// (each entry is ~125 bytes, so 10k * 125 = 1.25MB)
for (let i = 0; i < 10000; i++) {
const targetPath = `${tgzTempDir}/test-pkg-${i}-0.0.2.tgz`;
await Bun.write(targetPath, barContent);
}
});
afterAll(async () => {
const { rm } = await import("node:fs/promises");
try {
await rm(tgzTempDir, { recursive: true, force: true });
} catch (e) {}
});
test("handles JSON data larger than max arg length (>1MB)", {
testTimeout: 60_000,
scanner: async ({ packages }) => {
const jsonSize = JSON.stringify(packages).length;
console.log(`Received JSON payload of ${jsonSize} bytes from ${packages.length} packages via stdin`);
if (jsonSize < 1024 * 1024) {
throw new Error(`Expected JSON payload to exceed 1MB, got ${jsonSize} bytes`);
}
if (packages.length === 0) {
throw new Error("Expected to receive packages via stdin");
}
return [];
},
packageJson: (() => {
const dependencies: Record<string, string> = {};
for (let i = 0; i < 10000; i++) {
dependencies[`test-pkg-${i}`] = "0.0.2";
}
return {
name: "my-app",
version: "1.0.0",
dependencies,
};
})(),
packages: [],
customRegistry: urls => dummyRegistry(urls, { "0.0.2": {} }, 0, tgzTempDir),
expectedExitCode: 0,
expect: ({ out }) => {
expect(out).toContain("Received JSON payload");
expect(out).toContain("via stdin");
expect(out).toContain("packages");
const match = out.match(/Received JSON payload of (\d+) bytes/);
if (match) {
const bytes = parseInt(match[1], 10);
expect(bytes).toBeGreaterThan(1024 * 1024); // >1MB
}
},
});
});

View File

@@ -36,7 +36,12 @@ export function read(path: string) {
return Bun.file(join(package_dir, path));
}
export function dummyRegistry(urls: string[], info: any = { "0.0.2": {} }, numberOfTimesTo500PerURL = 0) {
export function dummyRegistry(
urls: string[],
info: any = { "0.0.2": {} },
numberOfTimesTo500PerURL = 0,
tgzDir?: string,
) {
let retryCountsByURL = new Map<string, number>();
const _handler: Handler = async request => {
urls.push(request.url);
@@ -57,7 +62,8 @@ export function dummyRegistry(urls: string[], info: any = { "0.0.2": {} }, numbe
expect(request.method).toBe("GET");
if (url.endsWith(".tgz")) {
return new Response(file(join(import.meta.dir, basename(url).toLowerCase())), { status });
const tgzPath = join(tgzDir ?? import.meta.dir, basename(url).toLowerCase());
return new Response(file(tgzPath), { status });
}
expect(request.headers.get("accept")).toBe(
"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",