mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
bun pm audit (#19855)
This commit is contained in:
@@ -236,6 +236,7 @@ src/ci_info.zig
|
||||
src/cli.zig
|
||||
src/cli/add_command.zig
|
||||
src/cli/add_completions.zig
|
||||
src/cli/audit_command.zig
|
||||
src/cli/build_command.zig
|
||||
src/cli/bunx_command.zig
|
||||
src/cli/colon_list_type.zig
|
||||
|
||||
@@ -260,6 +260,7 @@ _bun_pm_completion() {
|
||||
'hash\:"generate & print the hash of the current lockfile" '
|
||||
'hash-string\:"print the string used to hash the lockfile" '
|
||||
'hash-print\:"print the hash stored in the current lockfile" '
|
||||
'audit\:"run a security audit of dependencies in Bun\'s lockfile"'
|
||||
'cache\:"print the path to the cache folder" '
|
||||
)
|
||||
|
||||
|
||||
@@ -95,6 +95,14 @@ To print the hash stored in the current lockfile:
|
||||
$ bun pm hash-print
|
||||
```
|
||||
|
||||
## audit
|
||||
|
||||
To run a security audit for packages in bun.lock or bun.lockb
|
||||
|
||||
```bash
|
||||
$ bun pm audit
|
||||
```
|
||||
|
||||
## cache
|
||||
|
||||
To print the path to Bun's global module cache:
|
||||
|
||||
@@ -138,6 +138,7 @@ pub const PatchCommitCommand = @import("./cli/patch_commit_command.zig").PatchCo
|
||||
pub const OutdatedCommand = @import("./cli/outdated_command.zig").OutdatedCommand;
|
||||
pub const PublishCommand = @import("./cli/publish_command.zig").PublishCommand;
|
||||
pub const PackCommand = @import("./cli/pack_command.zig").PackCommand;
|
||||
pub const AuditCommand = @import("./cli/audit_command.zig").AuditCommand;
|
||||
pub const InitCommand = @import("./cli/init_command.zig").InitCommand;
|
||||
|
||||
pub const Arguments = struct {
|
||||
|
||||
706
src/cli/audit_command.zig
Normal file
706
src/cli/audit_command.zig
Normal file
@@ -0,0 +1,706 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("bun");
|
||||
const Command = @import("../cli.zig").Command;
|
||||
const PackageManager = @import("../install/install.zig").PackageManager;
|
||||
const Output = bun.Output;
|
||||
const Global = bun.Global;
|
||||
const strings = bun.strings;
|
||||
const http = bun.http;
|
||||
const HeaderBuilder = http.HeaderBuilder;
|
||||
const MutableString = bun.MutableString;
|
||||
const URL = @import("../url.zig").URL;
|
||||
const logger = bun.logger;
|
||||
const semver = @import("../semver.zig");
|
||||
const libdeflate = @import("../deps/libdeflate.zig");
|
||||
|
||||
const VulnerabilityInfo = struct {
|
||||
severity: []const u8,
|
||||
title: []const u8,
|
||||
url: []const u8,
|
||||
vulnerable_versions: []const u8,
|
||||
id: []const u8,
|
||||
package_name: []const u8,
|
||||
};
|
||||
|
||||
const PackageInfo = struct {
|
||||
package_id: u32,
|
||||
name: []const u8,
|
||||
version: []const u8,
|
||||
vulnerabilities: std.ArrayList(VulnerabilityInfo),
|
||||
dependents: std.ArrayList(DependencyPath),
|
||||
|
||||
const DependencyPath = struct {
|
||||
path: std.ArrayList([]const u8),
|
||||
is_direct: bool,
|
||||
};
|
||||
};
|
||||
|
||||
const AuditResult = struct {
|
||||
vulnerable_packages: bun.StringHashMap(PackageInfo),
|
||||
all_vulnerabilities: std.ArrayList(VulnerabilityInfo),
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) AuditResult {
|
||||
return AuditResult{
|
||||
.vulnerable_packages = bun.StringHashMap(PackageInfo).init(allocator),
|
||||
.all_vulnerabilities = std.ArrayList(VulnerabilityInfo).init(allocator),
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AuditResult) void {
|
||||
var iter = self.vulnerable_packages.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
entry.value_ptr.vulnerabilities.deinit();
|
||||
for (entry.value_ptr.dependents.items) |*dependent| {
|
||||
dependent.path.deinit();
|
||||
}
|
||||
entry.value_ptr.dependents.deinit();
|
||||
}
|
||||
self.vulnerable_packages.deinit();
|
||||
self.all_vulnerabilities.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
pub const AuditCommand = struct {
|
||||
/// Returns the exit code of the command. 0 if no vulnerabilities were found, 1 if vulnerabilities were found.
|
||||
/// The exception is when you pass --json, it will simply return 0 as that was considered a successful "request
|
||||
/// for the audit information"
|
||||
pub fn exec(ctx: Command.Context, pm: *PackageManager, args: [][:0]u8) bun.OOM!u32 {
|
||||
var json_output = false;
|
||||
for (args) |arg| {
|
||||
if (std.mem.eql(u8, arg, "--json")) {
|
||||
json_output = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Output.prettyError(comptime Output.prettyFmt("<r><b>bun pm audit <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", true), .{});
|
||||
Output.flush();
|
||||
|
||||
const load_lockfile = pm.lockfile.loadFromCwd(pm, ctx.allocator, ctx.log, true);
|
||||
@import("./package_manager_command.zig").PackageManagerCommand.handleLoadLockfileErrors(load_lockfile, pm);
|
||||
|
||||
var dependency_tree = try buildDependencyTree(ctx.allocator, pm);
|
||||
defer dependency_tree.deinit();
|
||||
|
||||
const packages_result = try collectPackagesForAudit(ctx.allocator, pm);
|
||||
defer ctx.allocator.free(packages_result.audit_body);
|
||||
defer {
|
||||
for (packages_result.skipped_packages.items) |package_name| {
|
||||
ctx.allocator.free(package_name);
|
||||
}
|
||||
packages_result.skipped_packages.deinit();
|
||||
}
|
||||
|
||||
const response_text = try sendAuditRequest(ctx.allocator, pm, packages_result.audit_body);
|
||||
defer ctx.allocator.free(response_text);
|
||||
|
||||
if (json_output) {
|
||||
Output.writer().writeAll(response_text) catch {};
|
||||
Output.writer().writeByte('\n') catch {};
|
||||
return 0;
|
||||
} else if (response_text.len > 0) {
|
||||
const exit_code = try printEnhancedAuditReport(ctx.allocator, response_text, pm, &dependency_tree);
|
||||
|
||||
printSkippedPackages(packages_result.skipped_packages);
|
||||
|
||||
return exit_code;
|
||||
} else {
|
||||
Output.prettyln("<green>No vulnerabilities found<r>", .{});
|
||||
|
||||
printSkippedPackages(packages_result.skipped_packages);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn printSkippedPackages(skipped_packages: std.ArrayList([]const u8)) void {
|
||||
if (skipped_packages.items.len > 0) {
|
||||
Output.pretty("<d>Skipped<r> ", .{});
|
||||
for (skipped_packages.items, 0..) |package_name, i| {
|
||||
if (i > 0) Output.pretty(", ", .{});
|
||||
Output.pretty("{s}", .{package_name});
|
||||
}
|
||||
|
||||
if (skipped_packages.items.len > 1) {
|
||||
Output.prettyln(" <d>because they do not come from the default registry<r>", .{});
|
||||
} else {
|
||||
Output.prettyln(" <d>because it does not come from the default registry<r>", .{});
|
||||
}
|
||||
|
||||
Output.prettyln("", .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn buildDependencyTree(allocator: std.mem.Allocator, pm: *PackageManager) bun.OOM!bun.StringHashMap(std.ArrayList([]const u8)) {
|
||||
var dependency_tree = bun.StringHashMap(std.ArrayList([]const u8)).init(allocator);
|
||||
|
||||
const packages = pm.lockfile.packages.slice();
|
||||
const pkg_names = packages.items(.name);
|
||||
const pkg_dependencies = packages.items(.dependencies);
|
||||
const pkg_resolutions = packages.items(.resolutions);
|
||||
const buf = pm.lockfile.buffers.string_bytes.items;
|
||||
const dependencies = pm.lockfile.buffers.dependencies.items;
|
||||
const resolutions = pm.lockfile.buffers.resolutions.items;
|
||||
|
||||
for (pkg_names, pkg_dependencies, pkg_resolutions, 0..) |pkg_name, deps, res_list, pkg_idx| {
|
||||
const package_name = pkg_name.slice(buf);
|
||||
|
||||
if (packages.items(.resolution)[pkg_idx].tag != .npm) continue;
|
||||
|
||||
const dep_slice = deps.get(dependencies);
|
||||
const res_slice = res_list.get(resolutions);
|
||||
|
||||
for (dep_slice, res_slice) |_, resolved_pkg_id| {
|
||||
if (resolved_pkg_id >= pkg_names.len) continue;
|
||||
|
||||
const resolved_name = pkg_names[resolved_pkg_id].slice(buf);
|
||||
|
||||
const result = try dependency_tree.getOrPut(resolved_name);
|
||||
if (!result.found_existing) {
|
||||
result.key_ptr.* = try allocator.dupe(u8, resolved_name);
|
||||
result.value_ptr.* = std.ArrayList([]const u8).init(allocator);
|
||||
}
|
||||
try result.value_ptr.append(try allocator.dupe(u8, package_name));
|
||||
}
|
||||
}
|
||||
|
||||
return dependency_tree;
|
||||
}
|
||||
|
||||
fn collectPackagesForAudit(allocator: std.mem.Allocator, pm: *PackageManager) bun.OOM!struct { audit_body: []u8, skipped_packages: std.ArrayList([]const u8) } {
|
||||
const packages = pm.lockfile.packages.slice();
|
||||
const pkg_names = packages.items(.name);
|
||||
const pkg_resolutions = packages.items(.resolution);
|
||||
const buf = pm.lockfile.buffers.string_bytes.items;
|
||||
const root_id = pm.root_package_id.get(pm.lockfile, pm.workspace_name_hash);
|
||||
|
||||
var packages_list = std.ArrayList(struct {
|
||||
name: []const u8,
|
||||
versions: std.ArrayList([]const u8),
|
||||
}).init(allocator);
|
||||
defer {
|
||||
for (packages_list.items) |item| {
|
||||
allocator.free(item.name);
|
||||
for (item.versions.items) |version| {
|
||||
allocator.free(version);
|
||||
}
|
||||
item.versions.deinit();
|
||||
}
|
||||
packages_list.deinit();
|
||||
}
|
||||
|
||||
var skipped_packages = std.ArrayList([]const u8).init(allocator);
|
||||
|
||||
for (pkg_names, pkg_resolutions, 0..) |name, res, idx| {
|
||||
if (idx == root_id) continue;
|
||||
if (res.tag != .npm) continue;
|
||||
|
||||
const name_slice = name.slice(buf);
|
||||
|
||||
const package_scope = pm.scopeForPackageName(name_slice);
|
||||
if (package_scope.url_hash != pm.options.scope.url_hash) {
|
||||
try skipped_packages.append(try allocator.dupe(u8, name_slice));
|
||||
continue;
|
||||
}
|
||||
|
||||
const ver_str = try std.fmt.allocPrint(allocator, "{}", .{res.value.npm.version.fmt(buf)});
|
||||
|
||||
var found_package: ?*@TypeOf(packages_list.items[0]) = null;
|
||||
for (packages_list.items) |*item| {
|
||||
if (std.mem.eql(u8, item.name, name_slice)) {
|
||||
found_package = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_package == null) {
|
||||
try packages_list.append(.{
|
||||
.name = try allocator.dupe(u8, name_slice),
|
||||
.versions = std.ArrayList([]const u8).init(allocator),
|
||||
});
|
||||
found_package = &packages_list.items[packages_list.items.len - 1];
|
||||
}
|
||||
|
||||
var version_exists = false;
|
||||
for (found_package.?.versions.items) |existing_ver| {
|
||||
if (std.mem.eql(u8, existing_ver, ver_str)) {
|
||||
version_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version_exists) {
|
||||
try found_package.?.versions.append(ver_str);
|
||||
} else {
|
||||
allocator.free(ver_str);
|
||||
}
|
||||
}
|
||||
|
||||
var body = try MutableString.init(allocator, 1024);
|
||||
body.appendChar('{') catch {};
|
||||
|
||||
for (packages_list.items, 0..) |package, pkg_idx| {
|
||||
if (pkg_idx > 0) body.appendChar(',') catch {};
|
||||
body.appendChar('"') catch {};
|
||||
body.appendSlice(package.name) catch {};
|
||||
body.appendChar('"') catch {};
|
||||
body.appendChar(':') catch {};
|
||||
body.appendChar('[') catch {};
|
||||
for (package.versions.items, 0..) |version, ver_idx| {
|
||||
if (ver_idx > 0) body.appendChar(',') catch {};
|
||||
body.appendChar('"') catch {};
|
||||
body.appendSlice(version) catch {};
|
||||
body.appendChar('"') catch {};
|
||||
}
|
||||
body.appendChar(']') catch {};
|
||||
}
|
||||
body.appendChar('}') catch {};
|
||||
|
||||
return .{
|
||||
.audit_body = try allocator.dupe(u8, body.slice()),
|
||||
.skipped_packages = skipped_packages,
|
||||
};
|
||||
}
|
||||
|
||||
fn sendAuditRequest(allocator: std.mem.Allocator, pm: *PackageManager, body: []const u8) bun.OOM![]u8 {
|
||||
libdeflate.load();
|
||||
var compressor = libdeflate.Compressor.alloc(6) orelse return error.OutOfMemory;
|
||||
defer compressor.deinit();
|
||||
|
||||
const max_compressed_size = compressor.maxBytesNeeded(body, .gzip);
|
||||
const compressed_body = try allocator.alloc(u8, max_compressed_size);
|
||||
defer allocator.free(compressed_body);
|
||||
|
||||
const compression_result = compressor.gzip(body, compressed_body);
|
||||
const final_compressed_body = compressed_body[0..compression_result.written];
|
||||
|
||||
var headers: HeaderBuilder = .{};
|
||||
headers.count("accept", "application/json");
|
||||
headers.count("content-type", "application/json");
|
||||
headers.count("content-encoding", "gzip");
|
||||
if (pm.options.scope.token.len > 0) {
|
||||
headers.count("authorization", "");
|
||||
headers.content.cap += "Bearer ".len + pm.options.scope.token.len;
|
||||
} else if (pm.options.scope.auth.len > 0) {
|
||||
headers.count("authorization", "");
|
||||
headers.content.cap += "Basic ".len + pm.options.scope.auth.len;
|
||||
}
|
||||
try headers.allocate(allocator);
|
||||
headers.append("accept", "application/json");
|
||||
headers.append("content-type", "application/json");
|
||||
headers.append("content-encoding", "gzip");
|
||||
if (pm.options.scope.token.len > 0) {
|
||||
headers.appendFmt("authorization", "Bearer {s}", .{pm.options.scope.token});
|
||||
} else if (pm.options.scope.auth.len > 0) {
|
||||
headers.appendFmt("authorization", "Basic {s}", .{pm.options.scope.auth});
|
||||
}
|
||||
|
||||
const url_str = try std.fmt.allocPrint(allocator, "{s}/-/npm/v1/security/advisories/bulk", .{strings.withoutTrailingSlash(pm.options.scope.url.href)});
|
||||
defer allocator.free(url_str);
|
||||
const url = URL.parse(url_str);
|
||||
|
||||
var response_buf = try MutableString.init(allocator, 1024);
|
||||
var req = http.AsyncHTTP.initSync(
|
||||
allocator,
|
||||
.POST,
|
||||
url,
|
||||
headers.entries,
|
||||
headers.content.ptr.?[0..headers.content.len],
|
||||
&response_buf,
|
||||
final_compressed_body,
|
||||
null,
|
||||
null,
|
||||
.follow,
|
||||
);
|
||||
const res = req.sendSync() catch |err| {
|
||||
Output.err(err, "audit request failed", .{});
|
||||
Global.crash();
|
||||
};
|
||||
|
||||
if (res.status_code >= 400) {
|
||||
Output.prettyErrorln("<red>error<r>: audit request failed (status {d})", .{res.status_code});
|
||||
Global.crash();
|
||||
}
|
||||
|
||||
return try allocator.dupe(u8, response_buf.slice());
|
||||
}
|
||||
|
||||
fn parseVulnerability(allocator: std.mem.Allocator, package_name: []const u8, vuln: bun.JSAst.Expr) bun.OOM!VulnerabilityInfo {
|
||||
var vulnerability = VulnerabilityInfo{
|
||||
.severity = "moderate",
|
||||
.title = "Vulnerability found",
|
||||
.url = "",
|
||||
.vulnerable_versions = "",
|
||||
.id = "",
|
||||
.package_name = try allocator.dupe(u8, package_name),
|
||||
};
|
||||
|
||||
if (vuln.data == .e_object) {
|
||||
const props = vuln.data.e_object.properties.slice();
|
||||
for (props) |prop| {
|
||||
if (prop.key) |key| {
|
||||
if (key.data == .e_string) {
|
||||
const field_name = key.data.e_string.data;
|
||||
if (prop.value) |value| {
|
||||
if (value.data == .e_string) {
|
||||
const field_value = value.data.e_string.data;
|
||||
if (std.mem.eql(u8, field_name, "severity")) {
|
||||
vulnerability.severity = field_value;
|
||||
} else if (std.mem.eql(u8, field_name, "title")) {
|
||||
vulnerability.title = field_value;
|
||||
} else if (std.mem.eql(u8, field_name, "url")) {
|
||||
vulnerability.url = field_value;
|
||||
} else if (std.mem.eql(u8, field_name, "vulnerable_versions")) {
|
||||
vulnerability.vulnerable_versions = field_value;
|
||||
} else if (std.mem.eql(u8, field_name, "id")) {
|
||||
vulnerability.id = field_value;
|
||||
}
|
||||
} else if (value.data == .e_number) {
|
||||
if (std.mem.eql(u8, field_name, "id")) {
|
||||
vulnerability.id = try std.fmt.allocPrint(allocator, "{d}", .{@as(u64, @intFromFloat(value.data.e_number.value))});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vulnerability;
|
||||
}
|
||||
|
||||
fn findDependencyPaths(
|
||||
allocator: std.mem.Allocator,
|
||||
target_package: []const u8,
|
||||
dependency_tree: *const bun.StringHashMap(std.ArrayList([]const u8)),
|
||||
pm: *PackageManager,
|
||||
) bun.OOM!std.ArrayList(PackageInfo.DependencyPath) {
|
||||
var paths = std.ArrayList(PackageInfo.DependencyPath).init(allocator);
|
||||
|
||||
const packages = pm.lockfile.packages.slice();
|
||||
const root_id = pm.root_package_id.get(pm.lockfile, pm.workspace_name_hash);
|
||||
const root_deps = packages.items(.dependencies)[root_id];
|
||||
const dependencies = pm.lockfile.buffers.dependencies.items;
|
||||
const buf = pm.lockfile.buffers.string_bytes.items;
|
||||
const pkg_names = packages.items(.name);
|
||||
const pkg_resolutions = packages.items(.resolution);
|
||||
const pkg_deps = packages.items(.dependencies);
|
||||
|
||||
const dep_slice = root_deps.get(dependencies);
|
||||
for (dep_slice) |dependency| {
|
||||
const dep_name = dependency.name.slice(buf);
|
||||
if (std.mem.eql(u8, dep_name, target_package)) {
|
||||
var direct_path = PackageInfo.DependencyPath{
|
||||
.path = std.ArrayList([]const u8).init(allocator),
|
||||
.is_direct = true,
|
||||
};
|
||||
try direct_path.path.append(try allocator.dupe(u8, target_package));
|
||||
try paths.append(direct_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (pkg_resolutions, pkg_deps, pkg_names) |resolution, workspace_deps, pkg_name| {
|
||||
if (resolution.tag != .workspace) continue;
|
||||
|
||||
const workspace_name = pkg_name.slice(buf);
|
||||
const workspace_dep_slice = workspace_deps.get(dependencies);
|
||||
|
||||
for (workspace_dep_slice) |dependency| {
|
||||
const dep_name = dependency.name.slice(buf);
|
||||
if (std.mem.eql(u8, dep_name, target_package)) {
|
||||
var workspace_path = PackageInfo.DependencyPath{
|
||||
.path = std.ArrayList([]const u8).init(allocator),
|
||||
.is_direct = false,
|
||||
};
|
||||
|
||||
const workspace_prefix = try std.fmt.allocPrint(allocator, "workspace:{s}", .{workspace_name});
|
||||
try workspace_path.path.append(workspace_prefix);
|
||||
try workspace_path.path.append(try allocator.dupe(u8, target_package));
|
||||
try paths.append(workspace_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var queue: std.fifo.LinearFifo([]const u8, .Dynamic) = std.fifo.LinearFifo([]const u8, .Dynamic).init(allocator);
|
||||
defer queue.deinit();
|
||||
var visited = bun.StringHashMap(void).init(allocator);
|
||||
defer visited.deinit();
|
||||
var parent_map = bun.StringHashMap([]const u8).init(allocator);
|
||||
defer parent_map.deinit();
|
||||
|
||||
if (dependency_tree.get(target_package)) |dependents| {
|
||||
for (dependents.items) |dependent| {
|
||||
try queue.writeItem(dependent);
|
||||
try parent_map.put(dependent, target_package);
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.readItem()) |*current| {
|
||||
if (visited.contains(current.*)) continue;
|
||||
try visited.put(current.*, {});
|
||||
|
||||
var is_root_dep = false;
|
||||
for (dep_slice) |*dependency| {
|
||||
const dep_name = dependency.name.slice(buf);
|
||||
if (bun.strings.eql(dep_name, current.*)) {
|
||||
is_root_dep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var workspace_name_for_dep: ?[]const u8 = null;
|
||||
for (pkg_resolutions, pkg_deps, pkg_names) |resolution, workspace_deps, pkg_name| {
|
||||
if (resolution.tag != .workspace) continue;
|
||||
|
||||
const workspace_dep_slice = workspace_deps.get(dependencies);
|
||||
for (workspace_dep_slice) |*dependency| {
|
||||
const dep_name = dependency.name.slice(buf);
|
||||
if (bun.strings.eql(dep_name, current.*)) {
|
||||
workspace_name_for_dep = pkg_name.slice(buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (workspace_name_for_dep != null) break;
|
||||
}
|
||||
|
||||
if (is_root_dep or workspace_name_for_dep != null) {
|
||||
var path = PackageInfo.DependencyPath{
|
||||
.path = std.ArrayList([]const u8).init(allocator),
|
||||
.is_direct = false,
|
||||
};
|
||||
|
||||
var trace = current;
|
||||
while (true) {
|
||||
try path.path.insert(0, try allocator.dupe(u8, trace.*));
|
||||
if (parent_map.get(trace.*)) |*parent| {
|
||||
trace = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (workspace_name_for_dep) |workspace_name| {
|
||||
const workspace_prefix = try std.fmt.allocPrint(allocator, "workspace:{s}", .{workspace_name});
|
||||
try path.path.insert(0, workspace_prefix);
|
||||
}
|
||||
|
||||
try paths.append(path);
|
||||
} else {
|
||||
if (dependency_tree.get(current.*)) |dependents| {
|
||||
for (dependents.items) |dependent| {
|
||||
if (!visited.contains(dependent)) {
|
||||
try queue.writeItem(dependent);
|
||||
try parent_map.put(dependent, current.*);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
fn printEnhancedAuditReport(
|
||||
allocator: std.mem.Allocator,
|
||||
response_text: []const u8,
|
||||
pm: *PackageManager,
|
||||
dependency_tree: *const bun.StringHashMap(std.ArrayList([]const u8)),
|
||||
) bun.OOM!u32 {
|
||||
const source = logger.Source.initPathString("audit-response.json", response_text);
|
||||
var log = logger.Log.init(allocator);
|
||||
defer log.deinit();
|
||||
|
||||
const expr = @import("../json_parser.zig").parse(&source, &log, allocator, true) catch {
|
||||
Output.writer().writeAll(response_text) catch {};
|
||||
Output.writer().writeByte('\n') catch {};
|
||||
return 1;
|
||||
};
|
||||
|
||||
if (expr.data == .e_object and expr.data.e_object.properties.len == 0) {
|
||||
Output.prettyln("<green>No vulnerabilities found<r>", .{});
|
||||
return 0;
|
||||
}
|
||||
|
||||
var audit_result = AuditResult.init(allocator);
|
||||
defer audit_result.deinit();
|
||||
|
||||
var vuln_counts = struct {
|
||||
low: u32 = 0,
|
||||
moderate: u32 = 0,
|
||||
high: u32 = 0,
|
||||
critical: u32 = 0,
|
||||
}{};
|
||||
|
||||
if (expr.data == .e_object) {
|
||||
const properties = expr.data.e_object.properties.slice();
|
||||
|
||||
for (properties) |prop| {
|
||||
if (prop.key) |key| {
|
||||
if (key.data == .e_string) {
|
||||
const package_name = key.data.e_string.data;
|
||||
|
||||
if (prop.value) |value| {
|
||||
if (value.data == .e_array) {
|
||||
const vulns = value.data.e_array.items.slice();
|
||||
for (vulns) |vuln| {
|
||||
if (vuln.data == .e_object) {
|
||||
const vulnerability = try parseVulnerability(allocator, package_name, vuln);
|
||||
|
||||
if (std.mem.eql(u8, vulnerability.severity, "low")) {
|
||||
vuln_counts.low += 1;
|
||||
} else if (std.mem.eql(u8, vulnerability.severity, "moderate")) {
|
||||
vuln_counts.moderate += 1;
|
||||
} else if (std.mem.eql(u8, vulnerability.severity, "high")) {
|
||||
vuln_counts.high += 1;
|
||||
} else if (std.mem.eql(u8, vulnerability.severity, "critical")) {
|
||||
vuln_counts.critical += 1;
|
||||
} else {
|
||||
vuln_counts.moderate += 1;
|
||||
}
|
||||
|
||||
try audit_result.all_vulnerabilities.append(vulnerability);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (audit_result.all_vulnerabilities.items) |vulnerability| {
|
||||
const paths = try findDependencyPaths(allocator, vulnerability.package_name, dependency_tree, pm);
|
||||
|
||||
const result = try audit_result.vulnerable_packages.getOrPut(vulnerability.package_name);
|
||||
if (!result.found_existing) {
|
||||
result.value_ptr.* = PackageInfo{
|
||||
.package_id = 0,
|
||||
.name = vulnerability.package_name,
|
||||
.version = vulnerability.vulnerable_versions,
|
||||
.vulnerabilities = std.ArrayList(VulnerabilityInfo).init(allocator),
|
||||
.dependents = paths,
|
||||
};
|
||||
}
|
||||
try result.value_ptr.vulnerabilities.append(vulnerability);
|
||||
}
|
||||
|
||||
var package_iter = audit_result.vulnerable_packages.iterator();
|
||||
while (package_iter.next()) |entry| {
|
||||
const package_info = entry.value_ptr;
|
||||
|
||||
if (package_info.vulnerabilities.items.len > 0) {
|
||||
const main_vuln = package_info.vulnerabilities.items[0];
|
||||
|
||||
// const is_direct_dependency: bool = brk: {
|
||||
// for (package_info.dependents.items) |path| {
|
||||
// if (path.is_direct) {
|
||||
// break :brk true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// break :brk false;
|
||||
// };
|
||||
|
||||
if (main_vuln.vulnerable_versions.len > 0) {
|
||||
Output.prettyln("<red>{s}<r> {s}", .{ main_vuln.package_name, main_vuln.vulnerable_versions });
|
||||
} else {
|
||||
Output.prettyln("<red>{s}<r>", .{main_vuln.package_name});
|
||||
}
|
||||
|
||||
for (package_info.dependents.items) |path| {
|
||||
if (path.path.items.len > 1) {
|
||||
if (std.mem.startsWith(u8, path.path.items[0], "workspace:")) {
|
||||
const vulnerable_pkg = path.path.items[path.path.items.len - 1];
|
||||
const workspace_part = path.path.items[0];
|
||||
|
||||
Output.prettyln(" <d>{s} › <red>{s}<r>", .{ workspace_part, vulnerable_pkg });
|
||||
} else {
|
||||
const vulnerable_pkg = path.path.items[0];
|
||||
|
||||
var reversed_items = std.ArrayList([]const u8).init(allocator);
|
||||
for (path.path.items[1..]) |item| try reversed_items.append(item);
|
||||
std.mem.reverse([]const u8, reversed_items.items);
|
||||
defer reversed_items.deinit();
|
||||
|
||||
const vuln_pkg_path = try std.mem.join(allocator, " › ", reversed_items.items);
|
||||
defer allocator.free(vuln_pkg_path);
|
||||
|
||||
Output.prettyln(" <d>{s} › <red>{s}<r>", .{ vuln_pkg_path, vulnerable_pkg });
|
||||
}
|
||||
} else {
|
||||
Output.prettyln(" <d>(direct dependency)<r>", .{});
|
||||
}
|
||||
}
|
||||
|
||||
for (package_info.vulnerabilities.items) |vuln| {
|
||||
if (vuln.title.len > 0) {
|
||||
if (std.mem.eql(u8, vuln.severity, "critical")) {
|
||||
Output.prettyln(" <red>critical<d>:<r> {s} - <d>{s}<r>", .{ vuln.title, vuln.url });
|
||||
} else if (std.mem.eql(u8, vuln.severity, "high")) {
|
||||
Output.prettyln(" <red>high<d>:<r> {s} - <d>{s}<r>", .{ vuln.title, vuln.url });
|
||||
} else if (std.mem.eql(u8, vuln.severity, "moderate")) {
|
||||
Output.prettyln(" <yellow>moderate<d>:<r> {s} - <d>{s}<r>", .{ vuln.title, vuln.url });
|
||||
} else {
|
||||
Output.prettyln(" <cyan>low<d>:<r> {s} - <d>{s}<r>", .{ vuln.title, vuln.url });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (is_direct_dependency) {
|
||||
// Output.prettyln(" To fix: <green>`bun update {s}`<r>", .{package_info.name});
|
||||
// } else {
|
||||
// Output.prettyln(" To fix: <green>`bun update --latest`<r><d> (may be a breaking change)<r>", .{});
|
||||
// }
|
||||
|
||||
Output.prettyln("", .{});
|
||||
}
|
||||
}
|
||||
|
||||
const total = vuln_counts.low + vuln_counts.moderate + vuln_counts.high + vuln_counts.critical;
|
||||
if (total > 0) {
|
||||
Output.pretty("<b>{d} vulnerabilities<r> (", .{total});
|
||||
|
||||
var has_previous = false;
|
||||
if (vuln_counts.critical > 0) {
|
||||
Output.pretty("<red><b>{d} critical<r>", .{vuln_counts.critical});
|
||||
has_previous = true;
|
||||
}
|
||||
if (vuln_counts.high > 0) {
|
||||
if (has_previous) Output.pretty(", ", .{});
|
||||
Output.pretty("<red>{d} high<r>", .{vuln_counts.high});
|
||||
has_previous = true;
|
||||
}
|
||||
if (vuln_counts.moderate > 0) {
|
||||
if (has_previous) Output.pretty(", ", .{});
|
||||
Output.pretty("<yellow>{d} moderate<r>", .{vuln_counts.moderate});
|
||||
has_previous = true;
|
||||
}
|
||||
if (vuln_counts.low > 0) {
|
||||
if (has_previous) Output.pretty(", ", .{});
|
||||
Output.pretty("<cyan>{d} low<r>", .{vuln_counts.low});
|
||||
}
|
||||
Output.prettyln(")", .{});
|
||||
|
||||
Output.prettyln("", .{});
|
||||
Output.prettyln("To update all dependencies to the latest compatible versions:", .{});
|
||||
Output.prettyln(" <green>bun update<r>", .{});
|
||||
Output.prettyln("", .{});
|
||||
Output.prettyln("To update all dependencies to the latest versions (including breaking changes):", .{});
|
||||
Output.prettyln(" <green>bun update --latest<r>", .{});
|
||||
Output.prettyln("", .{});
|
||||
}
|
||||
|
||||
if (total > 0) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
Output.writer().writeAll(response_text) catch {};
|
||||
Output.writer().writeByte('\n') catch {};
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ const TrustCommand = @import("./pm_trusted_command.zig").TrustCommand;
|
||||
const DefaultTrustedCommand = @import("./pm_trusted_command.zig").DefaultTrustedCommand;
|
||||
const Environment = bun.Environment;
|
||||
pub const PackCommand = @import("./pack_command.zig").PackCommand;
|
||||
pub const AuditCommand = @import("./audit_command.zig").AuditCommand;
|
||||
const Npm = Install.Npm;
|
||||
const PmViewCommand = @import("./pm_view_command.zig");
|
||||
const File = bun.sys.File;
|
||||
@@ -131,6 +132,7 @@ pub const PackageManagerCommand = struct {
|
||||
\\ <b><green>bun pm<r> <blue>hash<r> generate & print the hash of the current lockfile
|
||||
\\ <b><green>bun pm<r> <blue>hash-string<r> print the string used to hash the lockfile
|
||||
\\ <b><green>bun pm<r> <blue>hash-print<r> print the hash stored in the current lockfile
|
||||
\\ <b><green>bun pm<r> <blue>audit<r> check installed packages for vulnerabilities
|
||||
\\ <b><green>bun pm<r> <blue>cache<r> print the path to the cache folder
|
||||
\\ <b><green>bun pm<r> <blue>cache rm<r> clear the cache
|
||||
\\ <b><green>bun pm<r> <blue>migrate<r> migrate another package manager's lockfile without installing anything
|
||||
@@ -250,6 +252,9 @@ pub const PackageManagerCommand = struct {
|
||||
|
||||
_ = try pm.lockfile.hasMetaHashChanged(true, pm.lockfile.packages.len);
|
||||
Global.exit(0);
|
||||
} else if (strings.eqlComptime(subcommand, "audit")) {
|
||||
const code = try AuditCommand.exec(ctx, pm, args);
|
||||
Global.exit(code);
|
||||
} else if (strings.eqlComptime(subcommand, "cache")) {
|
||||
var dir: bun.PathBuffer = undefined;
|
||||
var fd = pm.getCacheDirectory();
|
||||
|
||||
@@ -8963,6 +8963,10 @@ pub const PackageManager = struct {
|
||||
break :brk loader;
|
||||
};
|
||||
|
||||
if (subcommand == .pm and cli.positionals.len >= 2 and strings.eqlComptime(cli.positionals[1], "audit")) {
|
||||
env.quiet = true;
|
||||
}
|
||||
|
||||
env.loadProcess();
|
||||
try env.load(entries_option.entries, &[_][]u8{}, .production, false);
|
||||
|
||||
|
||||
445
test/cli/install/__snapshots__/bun-audit.test.ts.snap
Normal file
445
test/cli/install/__snapshots__/bun-audit.test.ts.snap
Normal file
@@ -0,0 +1,445 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`\`bun pm audit\` should exit code 1 when there are vulnerabilities: bun-audit-expect-vulnerabilities-found 1`] = `
|
||||
"minimist <0.2.4
|
||||
express › mkdirp › minimist
|
||||
critical: Prototype Pollution in minimist - https://github.com/advisories/GHSA-xvch-5gv4-984h
|
||||
moderate: Prototype Pollution in minimist - https://github.com/advisories/GHSA-vh95-rmgr-6w4m
|
||||
|
||||
express >=3.4.5 <4.0.0-rc1
|
||||
(direct dependency)
|
||||
low: Express Open Redirect vulnerability - https://github.com/advisories/GHSA-jj78-5fmv-mv28
|
||||
low: express vulnerable to XSS via response.redirect() - https://github.com/advisories/GHSA-qw6h-vgh9-j6wx
|
||||
moderate: Express ressource injection - https://github.com/advisories/GHSA-cm5g-3pgc-8rg4
|
||||
moderate: Express.js Open Redirect in malformed URLs - https://github.com/advisories/GHSA-rv95-896h-c2vc
|
||||
|
||||
qs <6.2.4
|
||||
express › connect › body-parser › qs
|
||||
high: qs vulnerable to Prototype Pollution - https://github.com/advisories/GHSA-hrpp-h998-j3pp
|
||||
high: Prototype Pollution Protection Bypass in qs - https://github.com/advisories/GHSA-gqgv-6jq5-jjj9
|
||||
|
||||
send <0.19.0
|
||||
express › send
|
||||
low: send vulnerable to template injection that can lead to XSS - https://github.com/advisories/GHSA-m6fv-jmcg-4jfg
|
||||
|
||||
negotiator <0.6.1
|
||||
express › connect › serve-index › accepts › negotiator
|
||||
high: Regular Expression Denial of Service in negotiator - https://github.com/advisories/GHSA-7mc5-chhp-fmc3
|
||||
|
||||
base64-url <2.0.0
|
||||
express › connect › csurf › csrf › uid-safe › base64-url
|
||||
high: Out-of-bounds Read in base64-url - https://github.com/advisories/GHSA-j4mr-9xw3-c9jx
|
||||
|
||||
serve-static <1.16.0
|
||||
express › connect › serve-static
|
||||
low: serve-static vulnerable to template injection that can lead to XSS - https://github.com/advisories/GHSA-cm22-4g7w-348p
|
||||
|
||||
cookie <0.7.0
|
||||
express › connect › cookie
|
||||
low: cookie accepts cookie name, path, and domain with out of bounds characters - https://github.com/advisories/GHSA-pxg6-pf52-xh8x
|
||||
|
||||
mime <1.4.1
|
||||
express › send › mime
|
||||
high: mime Regular Expression Denial of Service when MIME lookup performed on untrusted user input - https://github.com/advisories/GHSA-wrvr-8mpx-r7pp
|
||||
|
||||
body-parser <1.20.3
|
||||
express › connect › body-parser
|
||||
high: body-parser vulnerable to denial of service when url encoding is enabled - https://github.com/advisories/GHSA-qwcr-r2fm-qrc7
|
||||
|
||||
fresh <0.5.2
|
||||
express › connect › fresh
|
||||
high: Regular Expression Denial of Service in fresh - https://github.com/advisories/GHSA-9qj9-36jm-prpv
|
||||
|
||||
morgan <1.9.1
|
||||
express › connect › morgan
|
||||
critical: Code Injection in morgan - https://github.com/advisories/GHSA-gwg9-rgvj-4h5j
|
||||
|
||||
basic-auth-connect <1.1.0
|
||||
express › connect › basic-auth-connect
|
||||
high: basic-auth-connect's callback uses time unsafe string comparison - https://github.com/advisories/GHSA-7p89-p6hx-q4fw
|
||||
|
||||
debug <2.6.9
|
||||
express › connect › compression › debug
|
||||
high: debug Inefficient Regular Expression Complexity vulnerability - https://github.com/advisories/GHSA-9vvw-cc9w-f27h
|
||||
low: Regular Expression Denial of Service in debug - https://github.com/advisories/GHSA-gxpj-cx7g-858c
|
||||
|
||||
ms <2.0.0
|
||||
express › connect › serve-favicon › ms
|
||||
moderate: Vercel ms Inefficient Regular Expression Complexity vulnerability - https://github.com/advisories/GHSA-w9mr-4mfr-499f
|
||||
|
||||
21 vulnerabilities (2 critical, 9 high, 4 moderate, 6 low)
|
||||
|
||||
To update all dependencies to the latest compatible versions:
|
||||
bun update
|
||||
|
||||
To update all dependencies to the latest versions (including breaking changes):
|
||||
bun update --latest
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`\`bun pm audit\` should print valid JSON and exit 0 when --json is passed and there are no vulnerabilities: bun-audit-expect-valid-json-stdout-report-no-vulnerabilities 1`] = `{}`;
|
||||
|
||||
exports[`\`bun pm audit\` should print valid JSON and exit 0 when --json is passed and there are vulnerabilities: bun-audit-expect-valid-json-stdout-report-vulnerabilities 1`] = `
|
||||
{
|
||||
"base64-url": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null,
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-125",
|
||||
],
|
||||
"id": 1090859,
|
||||
"severity": "high",
|
||||
"title": "Out-of-bounds Read in base64-url",
|
||||
"url": "https://github.com/advisories/GHSA-j4mr-9xw3-c9jx",
|
||||
"vulnerable_versions": "<2.0.0",
|
||||
},
|
||||
],
|
||||
"basic-auth-connect": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-208",
|
||||
],
|
||||
"id": 1099800,
|
||||
"severity": "high",
|
||||
"title": "basic-auth-connect's callback uses time unsafe string comparison",
|
||||
"url": "https://github.com/advisories/GHSA-7p89-p6hx-q4fw",
|
||||
"vulnerable_versions": "<1.1.0",
|
||||
},
|
||||
],
|
||||
"body-parser": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-405",
|
||||
],
|
||||
"id": 1099520,
|
||||
"severity": "high",
|
||||
"title": "body-parser vulnerable to denial of service when url encoding is enabled",
|
||||
"url": "https://github.com/advisories/GHSA-qwcr-r2fm-qrc7",
|
||||
"vulnerable_versions": "<1.20.3",
|
||||
},
|
||||
],
|
||||
"cookie": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null,
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-74",
|
||||
],
|
||||
"id": 1103907,
|
||||
"severity": "low",
|
||||
"title": "cookie accepts cookie name, path, and domain with out of bounds characters",
|
||||
"url": "https://github.com/advisories/GHSA-pxg6-pf52-xh8x",
|
||||
"vulnerable_versions": "<0.7.0",
|
||||
},
|
||||
],
|
||||
"debug": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-1333",
|
||||
],
|
||||
"id": 1094457,
|
||||
"severity": "high",
|
||||
"title": "debug Inefficient Regular Expression Complexity vulnerability",
|
||||
"url": "https://github.com/advisories/GHSA-9vvw-cc9w-f27h",
|
||||
"vulnerable_versions": "<2.6.9",
|
||||
},
|
||||
{
|
||||
"cvss": {
|
||||
"score": 3.7,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-400",
|
||||
],
|
||||
"id": 1096795,
|
||||
"severity": "low",
|
||||
"title": "Regular Expression Denial of Service in debug",
|
||||
"url": "https://github.com/advisories/GHSA-gxpj-cx7g-858c",
|
||||
"vulnerable_versions": "<2.6.9",
|
||||
},
|
||||
],
|
||||
"express": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 4.7,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-601",
|
||||
],
|
||||
"id": 1099969,
|
||||
"severity": "low",
|
||||
"title": "Express Open Redirect vulnerability",
|
||||
"url": "https://github.com/advisories/GHSA-jj78-5fmv-mv28",
|
||||
"vulnerable_versions": ">=3.4.5 <4.0.0-rc1",
|
||||
},
|
||||
{
|
||||
"cvss": {
|
||||
"score": 5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-79",
|
||||
],
|
||||
"id": 1100530,
|
||||
"severity": "low",
|
||||
"title": "express vulnerable to XSS via response.redirect()",
|
||||
"url": "https://github.com/advisories/GHSA-qw6h-vgh9-j6wx",
|
||||
"vulnerable_versions": "<4.20.0",
|
||||
},
|
||||
{
|
||||
"cvss": {
|
||||
"score": 4,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:N",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-74",
|
||||
],
|
||||
"id": 1101381,
|
||||
"severity": "moderate",
|
||||
"title": "Express ressource injection",
|
||||
"url": "https://github.com/advisories/GHSA-cm5g-3pgc-8rg4",
|
||||
"vulnerable_versions": "<=3.21.4",
|
||||
},
|
||||
{
|
||||
"cvss": {
|
||||
"score": 6.1,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-601",
|
||||
"CWE-1286",
|
||||
],
|
||||
"id": 1096820,
|
||||
"severity": "moderate",
|
||||
"title": "Express.js Open Redirect in malformed URLs",
|
||||
"url": "https://github.com/advisories/GHSA-rv95-896h-c2vc",
|
||||
"vulnerable_versions": "<4.19.2",
|
||||
},
|
||||
],
|
||||
"fresh": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-400",
|
||||
],
|
||||
"id": 1093570,
|
||||
"severity": "high",
|
||||
"title": "Regular Expression Denial of Service in fresh",
|
||||
"url": "https://github.com/advisories/GHSA-9qj9-36jm-prpv",
|
||||
"vulnerable_versions": "<0.5.2",
|
||||
},
|
||||
],
|
||||
"mime": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-400",
|
||||
],
|
||||
"id": 1093780,
|
||||
"severity": "high",
|
||||
"title": "mime Regular Expression Denial of Service when MIME lookup performed on untrusted user input",
|
||||
"url": "https://github.com/advisories/GHSA-wrvr-8mpx-r7pp",
|
||||
"vulnerable_versions": "<1.4.1",
|
||||
},
|
||||
],
|
||||
"minimist": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 9.8,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-1321",
|
||||
],
|
||||
"id": 1097677,
|
||||
"severity": "critical",
|
||||
"title": "Prototype Pollution in minimist",
|
||||
"url": "https://github.com/advisories/GHSA-xvch-5gv4-984h",
|
||||
"vulnerable_versions": "<0.2.4",
|
||||
},
|
||||
{
|
||||
"cvss": {
|
||||
"score": 5.6,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-1321",
|
||||
],
|
||||
"id": 1096466,
|
||||
"severity": "moderate",
|
||||
"title": "Prototype Pollution in minimist",
|
||||
"url": "https://github.com/advisories/GHSA-vh95-rmgr-6w4m",
|
||||
"vulnerable_versions": "<0.2.1",
|
||||
},
|
||||
],
|
||||
"morgan": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 9.8,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-94",
|
||||
],
|
||||
"id": 1093804,
|
||||
"severity": "critical",
|
||||
"title": "Code Injection in morgan",
|
||||
"url": "https://github.com/advisories/GHSA-gwg9-rgvj-4h5j",
|
||||
"vulnerable_versions": "<1.9.1",
|
||||
},
|
||||
],
|
||||
"ms": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 5.3,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-1333",
|
||||
],
|
||||
"id": 1094419,
|
||||
"severity": "moderate",
|
||||
"title": "Vercel ms Inefficient Regular Expression Complexity vulnerability",
|
||||
"url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f",
|
||||
"vulnerable_versions": "<2.0.0",
|
||||
},
|
||||
],
|
||||
"negotiator": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null,
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-400",
|
||||
],
|
||||
"id": 1090969,
|
||||
"severity": "high",
|
||||
"title": "Regular Expression Denial of Service in negotiator",
|
||||
"url": "https://github.com/advisories/GHSA-7mc5-chhp-fmc3",
|
||||
"vulnerable_versions": "<0.6.1",
|
||||
},
|
||||
],
|
||||
"qs": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-1321",
|
||||
],
|
||||
"id": 1104115,
|
||||
"severity": "high",
|
||||
"title": "qs vulnerable to Prototype Pollution",
|
||||
"url": "https://github.com/advisories/GHSA-hrpp-h998-j3pp",
|
||||
"vulnerable_versions": "<6.2.4",
|
||||
},
|
||||
{
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-20",
|
||||
],
|
||||
"id": 1087527,
|
||||
"severity": "high",
|
||||
"title": "Prototype Pollution Protection Bypass in qs",
|
||||
"url": "https://github.com/advisories/GHSA-gqgv-6jq5-jjj9",
|
||||
"vulnerable_versions": "<6.0.4",
|
||||
},
|
||||
],
|
||||
"send": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-79",
|
||||
],
|
||||
"id": 1100526,
|
||||
"severity": "low",
|
||||
"title": "send vulnerable to template injection that can lead to XSS",
|
||||
"url": "https://github.com/advisories/GHSA-m6fv-jmcg-4jfg",
|
||||
"vulnerable_versions": "<0.19.0",
|
||||
},
|
||||
],
|
||||
"serve-static": [
|
||||
{
|
||||
"cvss": {
|
||||
"score": 5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L",
|
||||
},
|
||||
"cwe": [
|
||||
"CWE-79",
|
||||
],
|
||||
"id": 1100528,
|
||||
"severity": "low",
|
||||
"title": "serve-static vulnerable to template injection that can lead to XSS",
|
||||
"url": "https://github.com/advisories/GHSA-cm22-4g7w-348p",
|
||||
"vulnerable_versions": "<1.16.0",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`\`bun pm audit\` should exit 1 and behave exactly the same when there are vulnerabilities when only devDependencies are specified: bun-audit-expect-vulnerabilities-found 1`] = `
|
||||
"ms <2.0.0
|
||||
(direct dependency)
|
||||
moderate: Vercel ms Inefficient Regular Expression Complexity vulnerability - https://github.com/advisories/GHSA-w9mr-4mfr-499f
|
||||
high: Regular Expression Denial of Service in ms - https://github.com/advisories/GHSA-3fx5-fwvr-xrjg
|
||||
|
||||
2 vulnerabilities (1 high, 1 moderate)
|
||||
|
||||
To update all dependencies to the latest compatible versions:
|
||||
bun update
|
||||
|
||||
To update all dependencies to the latest versions (including breaking changes):
|
||||
bun update --latest
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`\`bun pm audit\` when a project has some safe dependencies and some vulnerable dependencies, we should not print the safe dependencies: bun-audit-expect-vulnerabilities-found 1`] = `
|
||||
"ms <2.0.0
|
||||
(direct dependency)
|
||||
moderate: Vercel ms Inefficient Regular Expression Complexity vulnerability - https://github.com/advisories/GHSA-w9mr-4mfr-499f
|
||||
high: Regular Expression Denial of Service in ms - https://github.com/advisories/GHSA-3fx5-fwvr-xrjg
|
||||
|
||||
2 vulnerabilities (1 high, 1 moderate)
|
||||
|
||||
To update all dependencies to the latest compatible versions:
|
||||
bun update
|
||||
|
||||
To update all dependencies to the latest versions (including breaking changes):
|
||||
bun update --latest
|
||||
|
||||
"
|
||||
`;
|
||||
290
test/cli/install/bun-audit.test.ts
Normal file
290
test/cli/install/bun-audit.test.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { readableStreamToText, spawn } from "bun";
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, DirectoryTree, gunzipJsonRequest, lazyPromiseLike, tempDirWithFiles } from "harness";
|
||||
import { join } from "node:path";
|
||||
import { resolveBulkAdvisoryFixture } from "./registry/fixtures/audit/audit-fixtures";
|
||||
|
||||
function fixture(
|
||||
folder:
|
||||
| "express@3"
|
||||
| "vuln-with-only-dev-dependencies"
|
||||
| "safe-is-number@7"
|
||||
| "mix-of-safe-and-vulnerable-dependencies",
|
||||
) {
|
||||
return join(import.meta.dirname, "registry", "fixtures", "audit", folder);
|
||||
}
|
||||
|
||||
let server: Bun.Server;
|
||||
|
||||
beforeAll(() => {
|
||||
server = Bun.serve({
|
||||
fetch: async req => {
|
||||
const body = await gunzipJsonRequest(req);
|
||||
|
||||
const fixture = resolveBulkAdvisoryFixture(body);
|
||||
|
||||
if (!fixture) {
|
||||
console.log("No fixture found for", body);
|
||||
return new Response("No fixture found", { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json(fixture);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
function doAuditTest(
|
||||
label: string,
|
||||
options: {
|
||||
args?: string[];
|
||||
exitCode: number;
|
||||
files: DirectoryTree | string;
|
||||
fn: (std: { stdout: PromiseLike<string>; stderr: PromiseLike<string>; dir: string }) => Promise<void>;
|
||||
},
|
||||
) {
|
||||
test(label, async () => {
|
||||
const dir = tempDirWithFiles("bun-test-pm-audit-" + label.replace(/[^a-zA-Z0-9]/g, "-"), options.files);
|
||||
|
||||
const cmd = [bunExe(), "pm", "audit", ...(options.args ?? [])];
|
||||
|
||||
const url = server.url.toString().slice(0, -1);
|
||||
|
||||
const proc = spawn({
|
||||
cmd,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
cwd: dir,
|
||||
env: {
|
||||
...bunEnv,
|
||||
NPM_CONFIG_REGISTRY: url,
|
||||
},
|
||||
});
|
||||
|
||||
const stdout = lazyPromiseLike(() => readableStreamToText(proc.stdout));
|
||||
const stderr = lazyPromiseLike(() => readableStreamToText(proc.stderr));
|
||||
|
||||
const exitCode = await proc.exited;
|
||||
|
||||
try {
|
||||
expect(exitCode).toBe(options.exitCode);
|
||||
await options.fn({ stdout, stderr, dir });
|
||||
} catch (e) {
|
||||
const err = await stderr;
|
||||
const out = await stdout;
|
||||
|
||||
// useful to see what went wrong otherwise
|
||||
// we are just eating the rror silently
|
||||
|
||||
console.log("ERR:", err);
|
||||
console.log("OUT:", out);
|
||||
|
||||
throw e; //but still rethrow so test fails
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("`bun pm audit`", () => {
|
||||
doAuditTest("should fail with no package.json", {
|
||||
exitCode: 1,
|
||||
files: {
|
||||
"README.md": "This place sure is empty...",
|
||||
},
|
||||
fn: async ({ stderr }) => {
|
||||
expect(await stderr).toContain("No package.json was found for directory");
|
||||
},
|
||||
});
|
||||
|
||||
doAuditTest("should fail with package.json but no lockfile", {
|
||||
exitCode: 1,
|
||||
files: {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"express": "3",
|
||||
},
|
||||
}),
|
||||
},
|
||||
fn: async ({ stderr }) => {
|
||||
expect(await stderr).toContain("error: Lockfile not found");
|
||||
},
|
||||
});
|
||||
|
||||
doAuditTest("should exit 0 when there are no dependencies in package.json", {
|
||||
exitCode: 0,
|
||||
files: {
|
||||
// i deemed this small enough to justify not needing a fixture
|
||||
"package.json": JSON.stringify({
|
||||
name: "empty-package",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
"bun.lock": JSON.stringify({
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "empty-package",
|
||||
},
|
||||
},
|
||||
"packages": {},
|
||||
}),
|
||||
},
|
||||
fn: async ({ stdout }) => {
|
||||
expect(await stdout).toBe("No vulnerabilities found\n");
|
||||
},
|
||||
});
|
||||
|
||||
doAuditTest("should exit 0 when there are no vulnerabilities", {
|
||||
exitCode: 0,
|
||||
files: fixture("safe-is-number@7"),
|
||||
fn: async ({ stdout }) => {
|
||||
expect(await stdout).toBe("No vulnerabilities found\n");
|
||||
},
|
||||
});
|
||||
|
||||
doAuditTest("should exit code 1 when there are vulnerabilities", {
|
||||
exitCode: 1,
|
||||
files: fixture("express@3"),
|
||||
fn: async ({ stdout }) => {
|
||||
expect(await stdout).toMatchSnapshot("bun-audit-expect-vulnerabilities-found");
|
||||
},
|
||||
});
|
||||
|
||||
doAuditTest("should print valid JSON and exit 0 when --json is passed and there are no vulnerabilities", {
|
||||
exitCode: 0,
|
||||
files: fixture("safe-is-number@7"),
|
||||
args: ["--json"],
|
||||
fn: async ({ stdout }) => {
|
||||
const out = await stdout;
|
||||
const json = JSON.parse(out); // this would throw making the test fail if the JSON was invalid
|
||||
expect(json).toMatchSnapshot("bun-audit-expect-valid-json-stdout-report-no-vulnerabilities");
|
||||
},
|
||||
});
|
||||
|
||||
doAuditTest("should print valid JSON and exit 0 when --json is passed and there are vulnerabilities", {
|
||||
exitCode: 0,
|
||||
files: fixture("express@3"),
|
||||
args: ["--json"],
|
||||
fn: async ({ stdout }) => {
|
||||
const out = await stdout;
|
||||
const json = JSON.parse(out); // this would throw making the test fail if the JSON was invalid
|
||||
expect(json).toMatchSnapshot("bun-audit-expect-valid-json-stdout-report-vulnerabilities");
|
||||
},
|
||||
});
|
||||
|
||||
doAuditTest(
|
||||
"should exit 1 and behave exactly the same when there are vulnerabilities when only devDependencies are specified",
|
||||
{
|
||||
exitCode: 1,
|
||||
files: fixture("vuln-with-only-dev-dependencies"),
|
||||
fn: async ({ stdout }) => {
|
||||
expect(await stdout).toMatchSnapshot("bun-audit-expect-vulnerabilities-found");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
doAuditTest(
|
||||
"when a project has some safe dependencies and some vulnerable dependencies, we should not print the safe dependencies",
|
||||
{
|
||||
exitCode: 1,
|
||||
files: fixture("mix-of-safe-and-vulnerable-dependencies"),
|
||||
fn: async ({ stdout }) => {
|
||||
// this fixture is using a safe version of is-number and an unsafe version of ms
|
||||
// so we want to check that `is-number` is not included in the output and that `ms` is
|
||||
|
||||
const out = await stdout;
|
||||
|
||||
expect(out).toContain("ms");
|
||||
expect(out).not.toContain("is-number");
|
||||
|
||||
expect(out).toMatchSnapshot("bun-audit-expect-vulnerabilities-found");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const fakeIntegrity = // this is just random/fake data as the integrity check is not important for this test
|
||||
"sha512-V8E0l1jyyeSSS9R+J9oljx5eq2rqzClInuwaPcyuv0Mm3ViI/3/rcc4rCEO8i4eQ4I0O0FAGYDA2i5xWHHPhzg==";
|
||||
|
||||
doAuditTest(
|
||||
"packages that come from non-default registries should be ignored from the audit, however they should get surfaced at the bottom of the output that they got skipped",
|
||||
{
|
||||
exitCode: 0,
|
||||
files: {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"@foo/bar": "1.0.0",
|
||||
"@foo/baz": "1.0.0",
|
||||
},
|
||||
}),
|
||||
"bun.lock": JSON.stringify({
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "test",
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@foo/bar": ["@foo/bar@1.0.0", "", {}, fakeIntegrity],
|
||||
"@foo/baz": ["@foo/baz@1.0.0", "", {}, fakeIntegrity],
|
||||
},
|
||||
}),
|
||||
//prettier-ignore
|
||||
".npmrc": [
|
||||
`registry=https://registry.npmjs.org`,
|
||||
`@foo:registry=https://my-registry.example.com`,
|
||||
].join("\n"),
|
||||
},
|
||||
fn: async ({ stdout }) => {
|
||||
const out = await stdout;
|
||||
|
||||
expect(out).toContain("Skipped @foo/bar, @foo/baz because they do not come from the default registry");
|
||||
expect(out).toContain("No vulnerabilities found");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
doAuditTest("workspaces print the path to the vulnerable package and include workspace:pkg in the name", {
|
||||
exitCode: 1,
|
||||
files: {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test",
|
||||
version: "1.0.0",
|
||||
workspaces: ["a"],
|
||||
}),
|
||||
|
||||
"a/package.json": JSON.stringify({
|
||||
"name": "a",
|
||||
"dependencies": {
|
||||
"ms": "0.7.0",
|
||||
},
|
||||
}),
|
||||
|
||||
"bun.lock": JSON.stringify({
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "bun-audit-playground",
|
||||
},
|
||||
"a": {
|
||||
"name": "a",
|
||||
"dependencies": {
|
||||
"ms": "0.7.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"a": ["a@workspace:a"],
|
||||
"ms": ["ms@0.7.0", "", {}, fakeIntegrity],
|
||||
},
|
||||
}),
|
||||
},
|
||||
fn: async ({ stdout }) => {
|
||||
expect(await stdout).toInclude("workspace:a › ms");
|
||||
},
|
||||
});
|
||||
});
|
||||
397
test/cli/install/registry/fixtures/audit/audit-fixtures.json
generated
Normal file
397
test/cli/install/registry/fixtures/audit/audit-fixtures.json
generated
Normal file
@@ -0,0 +1,397 @@
|
||||
{
|
||||
"{}": {},
|
||||
"{\"ms\":[\"0.7.0\"]}": {
|
||||
"ms": [
|
||||
{
|
||||
"id": 1094419,
|
||||
"url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f",
|
||||
"title": "Vercel ms Inefficient Regular Expression Complexity vulnerability",
|
||||
"severity": "moderate",
|
||||
"vulnerable_versions": "<2.0.0",
|
||||
"cwe": [
|
||||
"CWE-1333"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 5.3,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1098340,
|
||||
"url": "https://github.com/advisories/GHSA-3fx5-fwvr-xrjg",
|
||||
"title": "Regular Expression Denial of Service in ms",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<0.7.1",
|
||||
"cwe": [
|
||||
"CWE-400",
|
||||
"CWE-1333"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"{\"is-number\":[\"7.0.0\"],\"ms\":[\"0.7.0\"]}": {
|
||||
"ms": [
|
||||
{
|
||||
"id": 1094419,
|
||||
"url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f",
|
||||
"title": "Vercel ms Inefficient Regular Expression Complexity vulnerability",
|
||||
"severity": "moderate",
|
||||
"vulnerable_versions": "<2.0.0",
|
||||
"cwe": [
|
||||
"CWE-1333"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 5.3,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1098340,
|
||||
"url": "https://github.com/advisories/GHSA-3fx5-fwvr-xrjg",
|
||||
"title": "Regular Expression Denial of Service in ms",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<0.7.1",
|
||||
"cwe": [
|
||||
"CWE-400",
|
||||
"CWE-1333"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"{\"is-number\":[\"7.0.0\"]}": {},
|
||||
"{\"accepts\":[\"1.2.13\",\"1.3.8\"],\"base64-url\":[\"1.2.1\"],\"basic-auth\":[\"1.0.4\"],\"basic-auth-connect\":[\"1.0.0\"],\"batch\":[\"0.5.3\"],\"body-parser\":[\"1.13.3\"],\"bytes\":[\"2.1.0\",\"2.4.0\"],\"commander\":[\"2.6.0\"],\"compressible\":[\"2.0.18\"],\"compression\":[\"1.5.2\"],\"connect\":[\"2.30.2\"],\"connect-timeout\":[\"1.6.2\"],\"content-disposition\":[\"0.5.0\"],\"content-type\":[\"1.0.5\"],\"cookie\":[\"0.1.3\"],\"cookie-parser\":[\"1.3.5\"],\"cookie-signature\":[\"1.0.6\"],\"core-util-is\":[\"1.0.3\"],\"crc\":[\"3.3.0\"],\"csrf\":[\"3.0.6\"],\"csurf\":[\"1.8.3\"],\"debug\":[\"2.2.0\",\"2.6.9\"],\"depd\":[\"1.0.1\",\"2.0.0\",\"1.1.2\"],\"destroy\":[\"1.0.3\",\"1.0.4\"],\"ee-first\":[\"1.1.1\"],\"errorhandler\":[\"1.4.3\"],\"escape-html\":[\"1.0.2\",\"1.0.3\"],\"etag\":[\"1.7.0\"],\"express\":[\"3.21.2\"],\"express-session\":[\"1.11.3\"],\"finalhandler\":[\"0.4.0\"],\"forwarded\":[\"0.1.2\"],\"fresh\":[\"0.3.0\"],\"http-errors\":[\"1.3.1\"],\"iconv-lite\":[\"0.4.11\",\"0.4.13\"],\"inherits\":[\"2.0.4\"],\"ipaddr.js\":[\"1.0.5\"],\"isarray\":[\"0.0.1\"],\"media-typer\":[\"0.3.0\"],\"merge-descriptors\":[\"1.0.0\"],\"method-override\":[\"2.3.10\"],\"methods\":[\"1.1.2\"],\"mime\":[\"1.3.4\"],\"mime-db\":[\"1.52.0\"],\"mime-types\":[\"2.1.35\"],\"minimist\":[\"0.0.8\"],\"mkdirp\":[\"0.5.1\"],\"morgan\":[\"1.6.1\"],\"ms\":[\"0.7.1\",\"0.7.2\",\"2.0.0\"],\"multiparty\":[\"3.3.2\"],\"negotiator\":[\"0.5.3\",\"0.6.3\"],\"on-finished\":[\"2.3.0\"],\"on-headers\":[\"1.0.2\"],\"parseurl\":[\"1.3.3\"],\"pause\":[\"0.1.0\"],\"proxy-addr\":[\"1.0.10\"],\"qs\":[\"4.0.0\"],\"random-bytes\":[\"1.0.0\"],\"range-parser\":[\"1.0.3\"],\"raw-body\":[\"2.1.7\"],\"readable-stream\":[\"1.1.14\"],\"response-time\":[\"2.3.3\"],\"rndm\":[\"1.2.0\"],\"send\":[\"0.13.0\",\"0.13.2\"],\"serve-favicon\":[\"2.3.2\"],\"serve-index\":[\"1.7.3\"],\"serve-static\":[\"1.10.3\"],\"statuses\":[\"1.2.1\"],\"stream-counter\":[\"0.2.0\"],\"string_decoder\":[\"0.10.31\"],\"tsscmp\":[\"1.0.5\"],\"type-is\":[\"1.6.18\"],\"uid-safe\":[\"2.0.0\",\"2.1.4\"],\"unpipe\":[\"1.0.0\"],\"utils-merge\":[\"1.0.0\"],\"vary\":[\"1.0.1\",\"1.1.2\"],\"vhost\":[\"3.0.2\"]}": {
|
||||
"base64-url": [
|
||||
{
|
||||
"id": 1090859,
|
||||
"url": "https://github.com/advisories/GHSA-j4mr-9xw3-c9jx",
|
||||
"title": "Out-of-bounds Read in base64-url",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<2.0.0",
|
||||
"cwe": [
|
||||
"CWE-125"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"basic-auth-connect": [
|
||||
{
|
||||
"id": 1099800,
|
||||
"url": "https://github.com/advisories/GHSA-7p89-p6hx-q4fw",
|
||||
"title": "basic-auth-connect's callback uses time unsafe string comparison",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<1.1.0",
|
||||
"cwe": [
|
||||
"CWE-208"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
|
||||
}
|
||||
}
|
||||
],
|
||||
"body-parser": [
|
||||
{
|
||||
"id": 1099520,
|
||||
"url": "https://github.com/advisories/GHSA-qwcr-r2fm-qrc7",
|
||||
"title": "body-parser vulnerable to denial of service when url encoding is enabled",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<1.20.3",
|
||||
"cwe": [
|
||||
"CWE-405"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
}
|
||||
],
|
||||
"cookie": [
|
||||
{
|
||||
"id": 1103907,
|
||||
"url": "https://github.com/advisories/GHSA-pxg6-pf52-xh8x",
|
||||
"title": "cookie accepts cookie name, path, and domain with out of bounds characters",
|
||||
"severity": "low",
|
||||
"vulnerable_versions": "<0.7.0",
|
||||
"cwe": [
|
||||
"CWE-74"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"debug": [
|
||||
{
|
||||
"id": 1094457,
|
||||
"url": "https://github.com/advisories/GHSA-9vvw-cc9w-f27h",
|
||||
"title": "debug Inefficient Regular Expression Complexity vulnerability",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<2.6.9",
|
||||
"cwe": [
|
||||
"CWE-1333"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1096795,
|
||||
"url": "https://github.com/advisories/GHSA-gxpj-cx7g-858c",
|
||||
"title": "Regular Expression Denial of Service in debug",
|
||||
"severity": "low",
|
||||
"vulnerable_versions": "<2.6.9",
|
||||
"cwe": [
|
||||
"CWE-400"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 3.7,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
|
||||
}
|
||||
}
|
||||
],
|
||||
"express": [
|
||||
{
|
||||
"id": 1099969,
|
||||
"url": "https://github.com/advisories/GHSA-jj78-5fmv-mv28",
|
||||
"title": "Express Open Redirect vulnerability",
|
||||
"severity": "low",
|
||||
"vulnerable_versions": ">=3.4.5 <4.0.0-rc1",
|
||||
"cwe": [
|
||||
"CWE-601"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 4.7,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1100530,
|
||||
"url": "https://github.com/advisories/GHSA-qw6h-vgh9-j6wx",
|
||||
"title": "express vulnerable to XSS via response.redirect()",
|
||||
"severity": "low",
|
||||
"vulnerable_versions": "<4.20.0",
|
||||
"cwe": [
|
||||
"CWE-79"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1101381,
|
||||
"url": "https://github.com/advisories/GHSA-cm5g-3pgc-8rg4",
|
||||
"title": "Express ressource injection",
|
||||
"severity": "moderate",
|
||||
"vulnerable_versions": "<=3.21.4",
|
||||
"cwe": [
|
||||
"CWE-74"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 4,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:N"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1096820,
|
||||
"url": "https://github.com/advisories/GHSA-rv95-896h-c2vc",
|
||||
"title": "Express.js Open Redirect in malformed URLs",
|
||||
"severity": "moderate",
|
||||
"vulnerable_versions": "<4.19.2",
|
||||
"cwe": [
|
||||
"CWE-601",
|
||||
"CWE-1286"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 6.1,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fresh": [
|
||||
{
|
||||
"id": 1093570,
|
||||
"url": "https://github.com/advisories/GHSA-9qj9-36jm-prpv",
|
||||
"title": "Regular Expression Denial of Service in fresh",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<0.5.2",
|
||||
"cwe": [
|
||||
"CWE-400"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
}
|
||||
],
|
||||
"mime": [
|
||||
{
|
||||
"id": 1093780,
|
||||
"url": "https://github.com/advisories/GHSA-wrvr-8mpx-r7pp",
|
||||
"title": "mime Regular Expression Denial of Service when MIME lookup performed on untrusted user input",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<1.4.1",
|
||||
"cwe": [
|
||||
"CWE-400"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
}
|
||||
],
|
||||
"minimist": [
|
||||
{
|
||||
"id": 1097677,
|
||||
"url": "https://github.com/advisories/GHSA-xvch-5gv4-984h",
|
||||
"title": "Prototype Pollution in minimist",
|
||||
"severity": "critical",
|
||||
"vulnerable_versions": "<0.2.4",
|
||||
"cwe": [
|
||||
"CWE-1321"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 9.8,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1096466,
|
||||
"url": "https://github.com/advisories/GHSA-vh95-rmgr-6w4m",
|
||||
"title": "Prototype Pollution in minimist",
|
||||
"severity": "moderate",
|
||||
"vulnerable_versions": "<0.2.1",
|
||||
"cwe": [
|
||||
"CWE-1321"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 5.6,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L"
|
||||
}
|
||||
}
|
||||
],
|
||||
"morgan": [
|
||||
{
|
||||
"id": 1093804,
|
||||
"url": "https://github.com/advisories/GHSA-gwg9-rgvj-4h5j",
|
||||
"title": "Code Injection in morgan",
|
||||
"severity": "critical",
|
||||
"vulnerable_versions": "<1.9.1",
|
||||
"cwe": [
|
||||
"CWE-94"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 9.8,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ms": [
|
||||
{
|
||||
"id": 1094419,
|
||||
"url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f",
|
||||
"title": "Vercel ms Inefficient Regular Expression Complexity vulnerability",
|
||||
"severity": "moderate",
|
||||
"vulnerable_versions": "<2.0.0",
|
||||
"cwe": [
|
||||
"CWE-1333"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 5.3,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
|
||||
}
|
||||
}
|
||||
],
|
||||
"negotiator": [
|
||||
{
|
||||
"id": 1090969,
|
||||
"url": "https://github.com/advisories/GHSA-7mc5-chhp-fmc3",
|
||||
"title": "Regular Expression Denial of Service in negotiator",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<0.6.1",
|
||||
"cwe": [
|
||||
"CWE-400"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"qs": [
|
||||
{
|
||||
"id": 1104115,
|
||||
"url": "https://github.com/advisories/GHSA-hrpp-h998-j3pp",
|
||||
"title": "qs vulnerable to Prototype Pollution",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<6.2.4",
|
||||
"cwe": [
|
||||
"CWE-1321"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1087527,
|
||||
"url": "https://github.com/advisories/GHSA-gqgv-6jq5-jjj9",
|
||||
"title": "Prototype Pollution Protection Bypass in qs",
|
||||
"severity": "high",
|
||||
"vulnerable_versions": "<6.0.4",
|
||||
"cwe": [
|
||||
"CWE-20"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 7.5,
|
||||
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
|
||||
}
|
||||
}
|
||||
],
|
||||
"send": [
|
||||
{
|
||||
"id": 1100526,
|
||||
"url": "https://github.com/advisories/GHSA-m6fv-jmcg-4jfg",
|
||||
"title": "send vulnerable to template injection that can lead to XSS",
|
||||
"severity": "low",
|
||||
"vulnerable_versions": "<0.19.0",
|
||||
"cwe": [
|
||||
"CWE-79"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L"
|
||||
}
|
||||
}
|
||||
],
|
||||
"serve-static": [
|
||||
{
|
||||
"id": 1100528,
|
||||
"url": "https://github.com/advisories/GHSA-cm22-4g7w-348p",
|
||||
"title": "serve-static vulnerable to template injection that can lead to XSS",
|
||||
"severity": "low",
|
||||
"vulnerable_versions": "<1.16.0",
|
||||
"cwe": [
|
||||
"CWE-79"
|
||||
],
|
||||
"cvss": {
|
||||
"score": 5,
|
||||
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
46
test/cli/install/registry/fixtures/audit/audit-fixtures.ts
generated
Normal file
46
test/cli/install/registry/fixtures/audit/audit-fixtures.ts
generated
Normal file
@@ -0,0 +1,46 @@
|
||||
import auditFixturesJson from "./audit-fixtures.json" with { type: "json" };
|
||||
|
||||
type AuditReport = (typeof auditFixturesJson)[keyof typeof auditFixturesJson];
|
||||
|
||||
const fixtures = Object.entries(auditFixturesJson).map(
|
||||
([key, value]) => [JSON.parse(key) as Record<string, string[]>, value as AuditReport] as const,
|
||||
);
|
||||
|
||||
export function resolveBulkAdvisoryFixture(request: Record<string, string[]>) {
|
||||
for (const [body, response] of fixtures) {
|
||||
if (isSameJSON(body, request)) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
type JsonValue = string | number | boolean | null | JsonValue[] | { [key in string]: JsonValue };
|
||||
|
||||
function isSameJSON<T extends JsonValue>(a: T, b: T) {
|
||||
return sortedObjectHash(a) === sortedObjectHash(b);
|
||||
}
|
||||
|
||||
function sortedObjectHash(obj: JsonValue): string {
|
||||
if (typeof obj === "string") {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
const elements = obj.map(sortedObjectHash);
|
||||
return `[${elements.join(",")}]`;
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
const sortedKeys = Object.keys(obj).sort();
|
||||
const pairs = sortedKeys.map(key => `${JSON.stringify(key)}:${sortedObjectHash(obj[key])}`);
|
||||
return `{${pairs.join(",")}}`;
|
||||
}
|
||||
|
||||
if (typeof obj === "number" || typeof obj === "boolean" || obj === null) {
|
||||
return String(obj);
|
||||
}
|
||||
|
||||
return obj satisfies never;
|
||||
}
|
||||
198
test/cli/install/registry/fixtures/audit/express@3/bun.lock
Normal file
198
test/cli/install/registry/fixtures/audit/express@3/bun.lock
Normal file
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "express-3",
|
||||
"dependencies": {
|
||||
"express": "3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"accepts": ["accepts@1.2.13", "", { "dependencies": { "mime-types": "~2.1.6", "negotiator": "0.5.3" } }, "sha512-R190A3EzrS4huFOVZajhXCYZt5p5yrkaQOB4nsWzfth0cYaDcSN5J86l58FJ1dt7igp37fB/QhnuFkGAJmr+eg=="],
|
||||
|
||||
"base64-url": ["base64-url@1.2.1", "", {}, "sha512-V8E0l1jyyeSSS9R+J9oljx5eq2rqzClInuwaPcyuv0Mm3ViI/3/rcc4rCEO8i4eQ4I0O0FAGYDA2i5xWHHPhzg=="],
|
||||
|
||||
"basic-auth": ["basic-auth@1.0.4", "", {}, "sha512-uvq3I/zC5TmG0WZJDzsXzIytU9GiiSq23Gl27Dq9sV81JTfPfQhtdADECP1DJZeJoZPuYU0Y81hWC5y/dOR+Yw=="],
|
||||
|
||||
"basic-auth-connect": ["basic-auth-connect@1.0.0", "", {}, "sha512-kiV+/DTgVro4aZifY/hwRwALBISViL5NP4aReaR2EVJEObpbUBHIkdJh/YpcoEiYt7nBodZ6U2ajZeZvSxUCCg=="],
|
||||
|
||||
"batch": ["batch@0.5.3", "", {}, "sha512-aQgHPLH2DHpFTpBl5/GiVdNzHEqsLCSs1RiPvqkKP1+7RkNJlv71kL8/KXmvvaLqoZ7ylmvqkZhLjjAoRz8Xgw=="],
|
||||
|
||||
"body-parser": ["body-parser@1.13.3", "", { "dependencies": { "bytes": "2.1.0", "content-type": "~1.0.1", "debug": "~2.2.0", "depd": "~1.0.1", "http-errors": "~1.3.1", "iconv-lite": "0.4.11", "on-finished": "~2.3.0", "qs": "4.0.0", "raw-body": "~2.1.2", "type-is": "~1.6.6" } }, "sha512-ypX8/9uws2W+CjPp3QMmz1qklzlhRBknQve22Y+WFecHql+qDFfG+VVNX7sooA4Q3+2fdq4ZZj6Xr07gA90RZg=="],
|
||||
|
||||
"bytes": ["bytes@2.1.0", "", {}, "sha512-k9VSlRfRi5JYyQWMylSOgjld96ta1qaQUIvmn+na0BzViclH04PBumewv4z5aeXNkn6Z/gAN5FtPeBLvV20F9w=="],
|
||||
|
||||
"commander": ["commander@2.6.0", "", {}, "sha512-PhbTMT+ilDXZKqH8xbvuUY2ZEQNef0Q7DKxgoEKb4ccytsdvVVJmYqR0sGbi96nxU6oGrwEIQnclpK2NBZuQlg=="],
|
||||
|
||||
"compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="],
|
||||
|
||||
"compression": ["compression@1.5.2", "", { "dependencies": { "accepts": "~1.2.12", "bytes": "2.1.0", "compressible": "~2.0.5", "debug": "~2.2.0", "on-headers": "~1.0.0", "vary": "~1.0.1" } }, "sha512-+2fE8M8+Oe0kAlbMPz6UinaaH/HaGf+c5HlWRyYtPga/PHKxStJJKTU4xca8StY0JQ78L2kJaslpgSzCKgHaxQ=="],
|
||||
|
||||
"connect": ["connect@2.30.2", "", { "dependencies": { "basic-auth-connect": "1.0.0", "body-parser": "~1.13.3", "bytes": "2.1.0", "compression": "~1.5.2", "connect-timeout": "~1.6.2", "content-type": "~1.0.1", "cookie": "0.1.3", "cookie-parser": "~1.3.5", "cookie-signature": "1.0.6", "csurf": "~1.8.3", "debug": "~2.2.0", "depd": "~1.0.1", "errorhandler": "~1.4.2", "express-session": "~1.11.3", "finalhandler": "0.4.0", "fresh": "0.3.0", "http-errors": "~1.3.1", "method-override": "~2.3.5", "morgan": "~1.6.1", "multiparty": "3.3.2", "on-headers": "~1.0.0", "parseurl": "~1.3.0", "pause": "0.1.0", "qs": "4.0.0", "response-time": "~2.3.1", "serve-favicon": "~2.3.0", "serve-index": "~1.7.2", "serve-static": "~1.10.0", "type-is": "~1.6.6", "utils-merge": "1.0.0", "vhost": "~3.0.1" } }, "sha512-eY4YHls5bz/g6h9Q8B/aVkS6D7+TRiRlI3ksuruv3yc2rLbTG7HB/7T/CoZsuVH5e2i3S9J+2eARV5o7GIYq8Q=="],
|
||||
|
||||
"connect-timeout": ["connect-timeout@1.6.2", "", { "dependencies": { "debug": "~2.2.0", "http-errors": "~1.3.1", "ms": "0.7.1", "on-headers": "~1.0.0" } }, "sha512-qIFt3Ja6gRuJtVoWhPa5FtOO8ERs0MfW/QkmQ0vjrAL78otrkxe8w/qjTAgU/T1W/jH5qeZXJHilmOPKNTiEQw=="],
|
||||
|
||||
"content-disposition": ["content-disposition@0.5.0", "", {}, "sha512-PWzG8GssMHTPSLBoOeK5MvPPJeWU5ZVX8omvJC16BUH/nUX6J/jM/hgm/mrPWzTXVV3B3OoBhFdHXyGLU4TgUw=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"cookie": ["cookie@0.1.3", "", {}, "sha512-mWkFhcL+HVG1KjeCjEBVJJ7s4sAGMLiBDFSDs4bzzvgLZt7rW8BhP6XV/8b1+pNvx/skd3yYxPuaF3Z6LlQzyw=="],
|
||||
|
||||
"cookie-parser": ["cookie-parser@1.3.5", "", { "dependencies": { "cookie": "0.1.3", "cookie-signature": "1.0.6" } }, "sha512-YN/8nzPcK5o6Op4MIzAd4H4qUal5+3UaMhVIeaafFYL0pKvBQA/9Yhzo7ZwvBpjdGshsiTAb1+FC37M6RdPDFg=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
|
||||
|
||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||
|
||||
"crc": ["crc@3.3.0", "", {}, "sha512-QCx3z7FOZbJrapsnewTkh1Hxh6PHV61SRHbx6Q65Uih3y0kfIj+dDGI3uQ4Q1DLKOILyvpZxvJpoKPrxathpCg=="],
|
||||
|
||||
"csrf": ["csrf@3.0.6", "", { "dependencies": { "rndm": "1.2.0", "tsscmp": "1.0.5", "uid-safe": "2.1.4" } }, "sha512-3q1ocniLMgk9nHHEt/I/JsN9IfiGjgp6MHgYNT7+CPmQvi5DF6qzenXnZSH6f9Qaa+4DhmUDJa8SgFZ+OFf9Qg=="],
|
||||
|
||||
"csurf": ["csurf@1.8.3", "", { "dependencies": { "cookie": "0.1.3", "cookie-signature": "1.0.6", "csrf": "~3.0.0", "http-errors": "~1.3.1" } }, "sha512-p2NJ9fGOn5HCaV9jAOBCSjIGMRMrpm9/yDswD0bFi7zQv1ifDufIKI5nem9RmhMsH6jVD6Sx6vs57hnivvkJJw=="],
|
||||
|
||||
"debug": ["debug@2.2.0", "", { "dependencies": { "ms": "0.7.1" } }, "sha512-X0rGvJcskG1c3TgSCPqHJ0XJgwlcvOC7elJ5Y0hYuKBZoVqWpAMfLOeIh2UI/DCQ5ruodIjvsugZtjUYUw2pUw=="],
|
||||
|
||||
"depd": ["depd@1.0.1", "", {}, "sha512-OEWAMbCkK9IWQ8pfTvHBhCSqHgR+sk5pbiYqq0FqfARG4Cy+cRsCbITx6wh5pcsmfBPiJAcbd98tfdz5fnBbag=="],
|
||||
|
||||
"destroy": ["destroy@1.0.3", "", {}, "sha512-KB/AVLKRwZPOEo6/lxkDJ+Bv3jFRRrhmnRMPvpWwmIfUggpzGkQBqolyo8FRf833b/F5rzmy1uVN3fHBkjTxgw=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"errorhandler": ["errorhandler@1.4.3", "", { "dependencies": { "accepts": "~1.3.0", "escape-html": "~1.0.3" } }, "sha512-pp1hk9sZBq4Bj/e/Cl84fJ3cYiQDFZk3prp7jrurUbPGOlY7zA2OubjhhEAWuUb8VNTFIkGwoby7Uq6YpicfvQ=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.2", "", {}, "sha512-J5ahyCRC4liskWVAfkmosNWfG0eHQxI0W+Ko7k3cZaYVMfgt05dwZ68vw6S/TZM1BPvuTv3kq6CRCb7WWtBUVA=="],
|
||||
|
||||
"etag": ["etag@1.7.0", "", {}, "sha512-Mbv5pNpLNPrm1b4rzZlZlfTRpdDr31oiD43N362sIyvSWVNu5Du33EcJGzvEV4YdYLuENB1HzND907cQkFmXNw=="],
|
||||
|
||||
"express": ["express@3.21.2", "", { "dependencies": { "basic-auth": "~1.0.3", "commander": "2.6.0", "connect": "2.30.2", "content-disposition": "0.5.0", "content-type": "~1.0.1", "cookie": "0.1.3", "cookie-signature": "1.0.6", "debug": "~2.2.0", "depd": "~1.0.1", "escape-html": "1.0.2", "etag": "~1.7.0", "fresh": "0.3.0", "merge-descriptors": "1.0.0", "methods": "~1.1.1", "mkdirp": "0.5.1", "parseurl": "~1.3.0", "proxy-addr": "~1.0.8", "range-parser": "~1.0.2", "send": "0.13.0", "utils-merge": "1.0.0", "vary": "~1.0.1" }, "bin": { "express": "./bin/express" } }, "sha512-r3mq2RNCDxAdmZrzEAdjlk5/W7x8+vjU1aAcoAoZFq62KtkWQX+MbaSN4g59CwdUFf9MFf1VSqkZJ+LeR9jmww=="],
|
||||
|
||||
"express-session": ["express-session@1.11.3", "", { "dependencies": { "cookie": "0.1.3", "cookie-signature": "1.0.6", "crc": "3.3.0", "debug": "~2.2.0", "depd": "~1.0.1", "on-headers": "~1.0.0", "parseurl": "~1.3.0", "uid-safe": "~2.0.0", "utils-merge": "1.0.0" } }, "sha512-QdSbGRRg+JMvlYpancRDFXDmIMqjEdpowriwQc4Kz3mvPwTnOPD/h5FSS21+4z4Isosta+ULmEwL6F3/lylWWg=="],
|
||||
|
||||
"finalhandler": ["finalhandler@0.4.0", "", { "dependencies": { "debug": "~2.2.0", "escape-html": "1.0.2", "on-finished": "~2.3.0", "unpipe": "~1.0.0" } }, "sha512-jJU2WE88OqUvwAIf/1K2G2fTdKKZ8LvSwYQyFFekDcmBnBmht38enbcmErnA7iNZktcEo/o2JAHYbe1QDOAgaA=="],
|
||||
|
||||
"forwarded": ["forwarded@0.1.2", "", {}, "sha512-Ua9xNhH0b8pwE3yRbFfXJvfdWF0UHNCdeyb2sbi9Ul/M+r3PTdrz7Cv4SCfZRMjmzEM9PhraqfZFbGTIg3OMyA=="],
|
||||
|
||||
"fresh": ["fresh@0.3.0", "", {}, "sha512-akx5WBKAwMSg36qoHTuMMVncHWctlaDGslJASDYAhoLrzDUDCjZlOngNa/iC6lPm9aA0qk8pN5KnpmbJHSIIQQ=="],
|
||||
|
||||
"http-errors": ["http-errors@1.3.1", "", { "dependencies": { "inherits": "~2.0.1", "statuses": "1" } }, "sha512-gMygNskMurDCWfoCdyh1gOeDfSbkAHXqz94QoPj5IHIUjC/BG8/xv7FSEUr7waR5RcAya4j58bft9Wu/wHNeXA=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.4.11", "", {}, "sha512-8UmnaYeP5puk18SkBrYULVTiq7REcimhx+ykJVJBiaz89DQmVQAfS29ZhHah86la90/t0xy4vRk86/2cCwNodA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.0.5", "", {}, "sha512-wBj+q+3uP78gMowwWgFLAYm/q4x5goyZmDsmuvyz+nd1u0D/ghgXXtc1OkgmTzSiWT101kiqGacwFk9eGQw6xQ=="],
|
||||
|
||||
"isarray": ["isarray@0.0.1", "", {}, "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="],
|
||||
|
||||
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@1.0.0", "", {}, "sha512-YJiZmTZTkrqvgefMsWdioTKsZdHnfAhHHkEdPg+4PCqMJEGHQo5iJQjEbMv3XyBZ6y3Z2Rj1mqq1WNKq9e0yNw=="],
|
||||
|
||||
"method-override": ["method-override@2.3.10", "", { "dependencies": { "debug": "2.6.9", "methods": "~1.1.2", "parseurl": "~1.3.2", "vary": "~1.1.2" } }, "sha512-Ks2/7e+3JuwQcpLybc6wTHyqg13HDjOhLcE+YaAEub9DbSxF+ieMvxUlybmWW9luRMh9Cd0rO9aNtzUT51xfNQ=="],
|
||||
|
||||
"methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
|
||||
|
||||
"mime": ["mime@1.3.4", "", { "bin": { "mime": "cli.js" } }, "sha512-sAaYXszED5ALBt665F0wMQCUXpGuZsGdopoqcHPdL39ZYdi7uHoZlhrfZfhv8WzivhBzr/oXwaj+yiK5wY8MXQ=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"minimist": ["minimist@0.0.8", "", {}, "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q=="],
|
||||
|
||||
"mkdirp": ["mkdirp@0.5.1", "", { "dependencies": { "minimist": "0.0.8" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA=="],
|
||||
|
||||
"morgan": ["morgan@1.6.1", "", { "dependencies": { "basic-auth": "~1.0.3", "debug": "~2.2.0", "depd": "~1.0.1", "on-finished": "~2.3.0", "on-headers": "~1.0.0" } }, "sha512-WWxlTx5xCqbtSeX/gPVHUZBhAhSMfYQLgPrWHEN0FYnF+zf1Ju/Zct6rpeKmvzibrYF4QvFVws7IN61BxnKu+Q=="],
|
||||
|
||||
"ms": ["ms@0.7.1", "", {}, "sha512-lRLiIR9fSNpnP6TC4v8+4OU7oStC01esuNowdQ34L+Gk8e5Puoc88IqJ+XAY/B3Mn2ZKis8l8HX90oU8ivzUHg=="],
|
||||
|
||||
"multiparty": ["multiparty@3.3.2", "", { "dependencies": { "readable-stream": "~1.1.9", "stream-counter": "~0.2.0" } }, "sha512-FX6dDOKzDpkrb5/+Imq+V6dmCZNnC02tMDiZfrgHSYgfQj6CVPGzOVqfbHKt/Vy4ZZsmMPXkulyLf92lCyvV7A=="],
|
||||
|
||||
"negotiator": ["negotiator@0.5.3", "", {}, "sha512-oXmnazqehLNFohqgLxRyUdOQU9/UX0NpCpsnbjWUjM62ZM8oSOXYZpHc68XR130ftPNano0oQXGdREAplZRhaQ=="],
|
||||
|
||||
"on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="],
|
||||
|
||||
"on-headers": ["on-headers@1.0.2", "", {}, "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="],
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"pause": ["pause@0.1.0", "", {}, "sha512-aeHLgQCtI3tcuYVnrvAeVb4Tkm1za4r3YDv3hMeUxcRxet3dbEhJOdtoMrsT/Q5tY3Oy2A1A9FD5el5tWp2FSg=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@1.0.10", "", { "dependencies": { "forwarded": "~0.1.0", "ipaddr.js": "1.0.5" } }, "sha512-iq6kR9KN32aFvXjDyC8nIrm203AHeIBPjL6dpaHgSdbpTO8KoPlD0xG92xwwtkCL9+yt1LE5VwpEk43TyP38Dg=="],
|
||||
|
||||
"qs": ["qs@4.0.0", "", {}, "sha512-8MPmJ83uBOPsQj5tQCv4g04/nTiY+d17yl9o3Bw73vC6XlEm2POIRRlOgWJ8i74bkGLII670cDJJZkgiZ2sIkg=="],
|
||||
|
||||
"random-bytes": ["random-bytes@1.0.0", "", {}, "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="],
|
||||
|
||||
"range-parser": ["range-parser@1.0.3", "", {}, "sha512-nDsRrtIxVUO5opg/A8T2S3ebULVIfuh8ECbh4w3N4mWxIiT3QILDJDUQayPqm2e8Q8NUa0RSUkGCfe33AfjR3Q=="],
|
||||
|
||||
"raw-body": ["raw-body@2.1.7", "", { "dependencies": { "bytes": "2.4.0", "iconv-lite": "0.4.13", "unpipe": "1.0.0" } }, "sha512-x4d27vsIG04gZ1imkuDXB9Rd/EkAx5kYzeMijIYw1PAor0Ld3nTlkQQwDjKu42GdRUFCX1AfGnTSQB4O57eWVg=="],
|
||||
|
||||
"readable-stream": ["readable-stream@1.1.14", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ=="],
|
||||
|
||||
"response-time": ["response-time@2.3.3", "", { "dependencies": { "depd": "~2.0.0", "on-headers": "~1.0.1" } }, "sha512-SsjjOPHl/FfrTQNgmc5oen8Hr1Jxpn6LlHNXxCIFdYMHuK1kMeYMobb9XN3mvxaGQm3dbegqYFMX4+GDORfbWg=="],
|
||||
|
||||
"rndm": ["rndm@1.2.0", "", {}, "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw=="],
|
||||
|
||||
"send": ["send@0.13.0", "", { "dependencies": { "debug": "~2.2.0", "depd": "~1.0.1", "destroy": "1.0.3", "escape-html": "1.0.2", "etag": "~1.7.0", "fresh": "0.3.0", "http-errors": "~1.3.1", "mime": "1.3.4", "ms": "0.7.1", "on-finished": "~2.3.0", "range-parser": "~1.0.2", "statuses": "~1.2.1" } }, "sha512-zck2y84i0SbUUiwq2l5gGPNVpCplL48og5xIhFjNjQa09003YCTy6Vb3rKfVuG8W8PWNUtUOntjQEBdwkJ9oBw=="],
|
||||
|
||||
"serve-favicon": ["serve-favicon@2.3.2", "", { "dependencies": { "etag": "~1.7.0", "fresh": "0.3.0", "ms": "0.7.2", "parseurl": "~1.3.1" } }, "sha512-oHEaA3ohvKxEWhjP97cQ6QuTTbMBF3AxDyMSvBtvnl1jXaB2Ik6kXE7nUtPM3YVU5VHCDe6n7JZrFCWzQuvXEQ=="],
|
||||
|
||||
"serve-index": ["serve-index@1.7.3", "", { "dependencies": { "accepts": "~1.2.13", "batch": "0.5.3", "debug": "~2.2.0", "escape-html": "~1.0.3", "http-errors": "~1.3.1", "mime-types": "~2.1.9", "parseurl": "~1.3.1" } }, "sha512-g18EQWY83uFBldFpCyK/a49yxQgIMEMLA6U9f66FiI848mLkMO8EY/xRAZAoCwNFwSUAiArCF3mdjaNXpd3ghw=="],
|
||||
|
||||
"serve-static": ["serve-static@1.10.3", "", { "dependencies": { "escape-html": "~1.0.3", "parseurl": "~1.3.1", "send": "0.13.2" } }, "sha512-ScsFovjz3Db+vGgpofR/U8p8UULEcGV9akqyo8TQ1mMnjcxemE7Y5Muo+dvy3tJLY/doY2v1H61eCBMYGmwfrA=="],
|
||||
|
||||
"statuses": ["statuses@1.2.1", "", {}, "sha512-pVEuxHdSGrt8QmQ3LOZXLhSA6MP/iPqKzZeO6Squ7PNGkA/9MBsSfV0/L+bIxkoDmjF4tZcLpcVq/fkqoHvuKg=="],
|
||||
|
||||
"stream-counter": ["stream-counter@0.2.0", "", { "dependencies": { "readable-stream": "~1.1.8" } }, "sha512-GjA2zKc2iXUUKRcOxXQmhEx0Ev3XHJ6c8yWGqhQjWwhGrqNwSsvq9YlRLgoGtZ5Kx2Ln94IedaqJ5GUG6aBbxA=="],
|
||||
|
||||
"string_decoder": ["string_decoder@0.10.31", "", {}, "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="],
|
||||
|
||||
"tsscmp": ["tsscmp@1.0.5", "", {}, "sha512-aP/vy9xYiYGvtpW4xBkxdoeqbT+nNeo/37cdQk3iSiGz0xKb20XwOgBSqYo1DzEqt1ycPubEfPU3oHgzsRRL3g=="],
|
||||
|
||||
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
|
||||
|
||||
"uid-safe": ["uid-safe@2.0.0", "", { "dependencies": { "base64-url": "1.2.1" } }, "sha512-PH/12q0a/sEGVS28fZ5evILW2Ayn13PwkYmCleDsIPm39vUIqN58hjyqtUd496kyMY6WkXtaDMDpS8nSCmNKTg=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"utils-merge": ["utils-merge@1.0.0", "", {}, "sha512-HwU9SLQEtyo+0uoKXd1nkLqigUWLB+QuNQR4OcmB73eWqksM5ovuqcycks2x043W8XVb75rG1HQ0h93TMXkzQQ=="],
|
||||
|
||||
"vary": ["vary@1.0.1", "", {}, "sha512-yNsH+tC0r8quK2tg/yqkXqqaYzeKTkSqQ+8T6xCoWgOi/bU/omMYz+6k+I91JJJDeltJzI7oridTOq6OYkY0Tw=="],
|
||||
|
||||
"vhost": ["vhost@3.0.2", "", {}, "sha512-S3pJdWrpFWrKMboRU4dLYgMrTgoPALsmYwOvyebK2M6X95b9kQrjZy5rwl3uzzpfpENe/XrNYu/2U+e7/bmT5g=="],
|
||||
|
||||
"csrf/uid-safe": ["uid-safe@2.1.4", "", { "dependencies": { "random-bytes": "~1.0.0" } }, "sha512-MHTGzIDNPv1XhDK0MyKvEroobUhtpMa649/9SIFbTRO2dshLctD3zxOwQw+gQ+Mlp5osfMdUU1sjcO6Fw4rvCA=="],
|
||||
|
||||
"errorhandler/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||
|
||||
"errorhandler/escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"method-override/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"method-override/vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"raw-body/bytes": ["bytes@2.4.0", "", {}, "sha512-SvUX8+c/Ga454a4fprIdIUzUN9xfd1YTvYh7ub5ZPJ+ZJ/+K2Bp6IpWGmnw8r3caLTsmhvJAKZz3qjIo9+XuCQ=="],
|
||||
|
||||
"raw-body/iconv-lite": ["iconv-lite@0.4.13", "", {}, "sha512-QwVuTNQv7tXC5mMWFX5N5wGjmybjNBBD8P3BReTkPmipoxTUFgWM2gXNvldHQr6T14DH0Dh6qBVg98iJt7u4mQ=="],
|
||||
|
||||
"response-time/depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"serve-favicon/ms": ["ms@0.7.2", "", {}, "sha512-5NnE67nQSQDJHVahPJna1PQ/zCXMnQop3yUCxjKPNzCxuyPSKWTQ/5Gu5CZmjetwGLWRA+PzeF5thlbOdbQldA=="],
|
||||
|
||||
"serve-index/escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"serve-static/escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"serve-static/send": ["send@0.13.2", "", { "dependencies": { "debug": "~2.2.0", "depd": "~1.1.0", "destroy": "~1.0.4", "escape-html": "~1.0.3", "etag": "~1.7.0", "fresh": "0.3.0", "http-errors": "~1.3.1", "mime": "1.3.4", "ms": "0.7.1", "on-finished": "~2.3.0", "range-parser": "~1.0.3", "statuses": "~1.2.1" } }, "sha512-cQ0rmXHrdO2Iof08igV2bG/yXWD106ANwBg6DkGQNT2Vsznbgq6T0oAIQboy1GoFsIuy51jCim26aA9tj3Z3Zg=="],
|
||||
|
||||
"errorhandler/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"method-override/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"serve-static/send/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="],
|
||||
|
||||
"serve-static/send/destroy": ["destroy@1.0.4", "", {}, "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "express-3",
|
||||
"dependencies": {
|
||||
"express": "3"
|
||||
}
|
||||
}
|
||||
67
test/cli/install/registry/fixtures/audit/generate-audit-fixtures.ts
generated
Normal file
67
test/cli/install/registry/fixtures/audit/generate-audit-fixtures.ts
generated
Normal file
@@ -0,0 +1,67 @@
|
||||
import { $, readableStreamToText, spawn } from "bun";
|
||||
import { bunEnv, bunExe, gunzipJsonRequest, tempDirWithFiles } from "harness";
|
||||
import * as path from "node:path";
|
||||
|
||||
const output = path.join(import.meta.dirname, "audit-fixtures.json");
|
||||
|
||||
const packages = await Array.fromAsync(
|
||||
new Bun.Glob("./*/package.json").scan({
|
||||
cwd: import.meta.dirname,
|
||||
}),
|
||||
);
|
||||
|
||||
const absolutes = packages.map(p => path.resolve(import.meta.dirname, p));
|
||||
|
||||
const result: Record<string, unknown> = {
|
||||
"{}": {},
|
||||
};
|
||||
|
||||
for (const packageJsonPath of absolutes) {
|
||||
const directory = path.dirname(packageJsonPath);
|
||||
const tmp = tempDirWithFiles("bun-audit-fixture-generator", directory);
|
||||
|
||||
const { promise: requestBodyPromise, resolve, reject } = Promise.withResolvers<string>();
|
||||
|
||||
using server = Bun.serve({
|
||||
port: 12345,
|
||||
fetch: async req => {
|
||||
try {
|
||||
const body = await gunzipJsonRequest(req);
|
||||
resolve(JSON.stringify(body));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
||||
return Response.json({});
|
||||
},
|
||||
});
|
||||
|
||||
await $`bun i`.cwd(tmp);
|
||||
|
||||
await spawn({
|
||||
cmd: [bunExe(), "pm", "audit"],
|
||||
cwd: tmp,
|
||||
env: {
|
||||
...bunEnv,
|
||||
NPM_CONFIG_REGISTRY: server.url.toString(),
|
||||
},
|
||||
}).exited;
|
||||
|
||||
const body = await requestBodyPromise;
|
||||
|
||||
const { stdout, exited } = spawn({
|
||||
cmd: [bunExe(), "pm", "audit", "--json"],
|
||||
cwd: tmp,
|
||||
stdout: "pipe",
|
||||
stderr: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
await exited;
|
||||
|
||||
const text = await readableStreamToText(stdout);
|
||||
|
||||
result[body] = JSON.parse(text);
|
||||
}
|
||||
|
||||
await Bun.file(output).write(JSON.stringify(result, null, "\t"));
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "mix-of-safe-and-vulnerable-dependencies",
|
||||
"dependencies": {
|
||||
"is-number": "7.0.0",
|
||||
"ms": "0.7.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"ms": ["ms@0.7.0", "", {}, "sha512-YmuMMkfOZzzAftlHwiQxFepJx/5rDaYi9o9QanyBCk485BRAyM/vB9XoYlZvglxE/pmAWOiQgrdoE10watiK9w=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "mix-of-safe-and-vulnerable-dependencies",
|
||||
"dependencies": {
|
||||
"is-number": "7.0.0",
|
||||
"ms": "0.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "safe-is-number-7",
|
||||
"dependencies": {
|
||||
"is-number": "7.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "safe-is-number-7",
|
||||
"dependencies": {
|
||||
"is-number": "7.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "vuln-with-only-dev-dependencies",
|
||||
"devDependencies": {
|
||||
"ms": "0.7.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"ms": ["ms@0.7.0", "", {}, "sha512-YmuMMkfOZzzAftlHwiQxFepJx/5rDaYi9o9QanyBCk485BRAyM/vB9XoYlZvglxE/pmAWOiQgrdoE10watiK9w=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "vuln-with-only-dev-dependencies",
|
||||
"devDependencies": {
|
||||
"ms": "0.7.0"
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
import { gc as bunGC, sleepSync, spawnSync, unsafe, which, write } from "bun";
|
||||
import { heapStats } from "bun:jsc";
|
||||
import { fork, ChildProcess } from "child_process";
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { readFile, readlink, writeFile, readdir, rm } from "fs/promises";
|
||||
import { ChildProcess, fork } from "child_process";
|
||||
import { readdir, readFile, readlink, rm, writeFile } from "fs/promises";
|
||||
import fs, { closeSync, openSync, rmSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import { dirname, isAbsolute, join } from "path";
|
||||
@@ -46,8 +46,8 @@ export const isFlaky = isCI;
|
||||
export const isBroken = isCI;
|
||||
export const isASAN = basename(process.execPath).includes("bun-asan");
|
||||
|
||||
export const bunEnv: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
export const bunEnv: NodeJS.Dict<string> = {
|
||||
...(process.env as NodeJS.Dict<string>),
|
||||
GITHUB_ACTIONS: "false",
|
||||
BUN_DEBUG_QUIET_LOGS: "1",
|
||||
NO_COLOR: "1",
|
||||
@@ -205,7 +205,7 @@ export async function makeTree(base: string, tree: DirectoryTree) {
|
||||
}
|
||||
}
|
||||
|
||||
export function makeTreeSync(base: string, tree: DirectoryTree) {
|
||||
export function makeTreeSyncFromDirectoryTree(base: string, tree: DirectoryTree) {
|
||||
const isDirectoryTree = (value: string | DirectoryTree | Buffer): value is DirectoryTree =>
|
||||
typeof value === "object" && value && typeof value?.byteLength === "undefined";
|
||||
|
||||
@@ -227,11 +227,20 @@ export function makeTreeSync(base: string, tree: DirectoryTree) {
|
||||
}
|
||||
}
|
||||
|
||||
export function makeTreeSync(base: string, filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string) {
|
||||
if (typeof filesOrAbsolutePathToCopyFolderFrom === "string") {
|
||||
fs.cpSync(filesOrAbsolutePathToCopyFolderFrom, base, { recursive: true });
|
||||
return;
|
||||
}
|
||||
|
||||
return makeTreeSyncFromDirectoryTree(base, filesOrAbsolutePathToCopyFolderFrom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively create files within a new temporary directory.
|
||||
*
|
||||
* @param basename prefix of the new temporary directory
|
||||
* @param files directory tree. Each key is a folder or file, and each value is the contents of the file. Use objects for directories.
|
||||
* @param filesOrAbsolutePathToCopyFolderFrom Directory tree or absolute path to a folder to copy. If passing an object each key is a folder or file, and each value is the contents of the file. Use objects for directories.
|
||||
* @returns an absolute path to the new temporary directory
|
||||
*
|
||||
* @example
|
||||
@@ -244,9 +253,12 @@ export function makeTreeSync(base: string, tree: DirectoryTree) {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function tempDirWithFiles(basename: string, files: DirectoryTree): string {
|
||||
export function tempDirWithFiles(
|
||||
basename: string,
|
||||
filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string,
|
||||
): string {
|
||||
const base = fs.mkdtempSync(join(fs.realpathSync(os.tmpdir()), basename + "_"));
|
||||
makeTreeSync(base, files);
|
||||
makeTreeSync(base, filesOrAbsolutePathToCopyFolderFrom);
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -1652,3 +1664,46 @@ export async function readdirSorted(path: string): Promise<string[]> {
|
||||
results.sort();
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for making automatically lazily-executed promises.
|
||||
*
|
||||
* The difference is that the promise has not already started to be evaluated when it is created,
|
||||
* only when you await it does it execute the function.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* function createMyFixture() {
|
||||
* return {
|
||||
* start: lazyPromiseLike(() => fetch("https://example.com")),
|
||||
* stop: lazyPromiseLike(() => fetch("https://example.com")),
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const { start, stop } = createMyFixture();
|
||||
*
|
||||
* await start; // Calls only the start function
|
||||
* ```
|
||||
*
|
||||
* @param fn A function to make lazily evaluated.
|
||||
* @returns A promise-like object that will evaluate the function when `then` is called.
|
||||
*/
|
||||
export function lazyPromiseLike<T>(fn: () => Promise<T>): PromiseLike<T> {
|
||||
let p: Promise<T>;
|
||||
|
||||
return {
|
||||
then(onfulfilled, onrejected) {
|
||||
if (!p) {
|
||||
p = fn();
|
||||
}
|
||||
return p.then(onfulfilled, onrejected);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function gunzipJsonRequest(req: Request) {
|
||||
const buf = await req.arrayBuffer();
|
||||
const inflated = Bun.gunzipSync(buf);
|
||||
const body = JSON.parse(Buffer.from(inflated).toString("utf-8"));
|
||||
return body;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user