mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 04:49:06 +00:00
210 lines
7.3 KiB
Zig
210 lines
7.3 KiB
Zig
// Example implementation of `bun audit fix` functionality
|
|
// This would be integrated into src/cli/audit_command.zig
|
|
|
|
const std = @import("std");
|
|
const bun = @import("bun");
|
|
const PackageManager = @import("../install/install.zig").PackageManager;
|
|
const Semver = bun.Semver;
|
|
const Output = bun.Output;
|
|
const strings = bun.strings;
|
|
|
|
// Extended AuditCommand with fix support
|
|
pub const AuditCommand = struct {
|
|
pub fn exec(ctx: Command.Context) !noreturn {
|
|
const cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, .audit);
|
|
const manager, _ = PackageManager.init(ctx, cli, .audit) catch |err| {
|
|
// ... error handling ...
|
|
return err;
|
|
};
|
|
|
|
// Check for --fix flag
|
|
const should_fix = cli.audit_flags.fix;
|
|
const code = try audit(ctx, manager, manager.options.json_output, should_fix);
|
|
Global.exit(code);
|
|
}
|
|
|
|
pub fn audit(ctx: Command.Context, pm: *PackageManager, json_output: bool, fix: bool) bun.OOM!u32 {
|
|
// ... existing audit code to load lockfile and collect packages ...
|
|
|
|
const response_text = try sendAuditRequest(ctx.allocator, pm, packages_result.audit_body);
|
|
defer ctx.allocator.free(response_text);
|
|
|
|
// Parse vulnerabilities
|
|
var audit_result = try parseEnhancedAuditReport(
|
|
ctx.allocator,
|
|
response_text,
|
|
pm,
|
|
&dependency_tree,
|
|
);
|
|
defer audit_result.deinit();
|
|
|
|
// If fix flag is set and vulnerabilities found
|
|
if (fix and audit_result.all_vulnerabilities.items.len > 0) {
|
|
return try auditFix(ctx, pm, &audit_result);
|
|
}
|
|
|
|
// Otherwise, just report vulnerabilities
|
|
return printAuditReport(&audit_result);
|
|
}
|
|
|
|
fn auditFix(
|
|
ctx: Command.Context,
|
|
pm: *PackageManager,
|
|
audit_result: *const AuditResult,
|
|
) bun.OOM!u32 {
|
|
var update_requests = std.ArrayList(UpdateRequest).init(ctx.allocator);
|
|
defer update_requests.deinit();
|
|
|
|
var fixed_count: u32 = 0;
|
|
var failed_fixes = std.ArrayList(FailedFix).init(ctx.allocator);
|
|
defer failed_fixes.deinit();
|
|
|
|
// Process each vulnerable package
|
|
var iter = audit_result.vulnerable_packages.iterator();
|
|
while (iter.next()) |entry| {
|
|
const package_info = entry.value_ptr;
|
|
|
|
// Find minimum safe version for this package
|
|
const fix_result = try findFixForPackage(
|
|
ctx.allocator,
|
|
pm,
|
|
package_info,
|
|
);
|
|
|
|
switch (fix_result) {
|
|
.success => |safe_version| {
|
|
try update_requests.append(.{
|
|
.name = package_info.name,
|
|
.name_hash = strings.StringHash(package_info.name),
|
|
.version = .{
|
|
.literal = safe_version.literal,
|
|
.value = .{
|
|
.npm = .{
|
|
.version = safe_version.version,
|
|
},
|
|
},
|
|
.tag = .npm,
|
|
},
|
|
});
|
|
fixed_count += 1;
|
|
},
|
|
.no_safe_version => {
|
|
try failed_fixes.append(.{
|
|
.package_name = package_info.name,
|
|
.reason = "No safe version available",
|
|
});
|
|
},
|
|
.error => |err| {
|
|
try failed_fixes.append(.{
|
|
.package_name = package_info.name,
|
|
.reason = err,
|
|
});
|
|
},
|
|
}
|
|
}
|
|
|
|
// Apply updates if any
|
|
if (update_requests.items.len > 0) {
|
|
Output.prettyln("\n<cyan>Fixing {d} vulnerable packages...<r>", .{update_requests.items.len});
|
|
|
|
// Use existing update infrastructure
|
|
try pm.updatePackageJSONAndInstallWithManagerWithUpdatesAndUpdateRequests(
|
|
ctx,
|
|
update_requests.items,
|
|
.update,
|
|
pm.original_cwd,
|
|
);
|
|
|
|
Output.prettyln("\n<green>✓ Fixed {d} vulnerabilities<r>", .{fixed_count});
|
|
}
|
|
|
|
// Report failed fixes
|
|
if (failed_fixes.items.len > 0) {
|
|
Output.prettyln("\n<yellow>Could not automatically fix {d} vulnerabilities:<r>", .{failed_fixes.items.len});
|
|
for (failed_fixes.items) |failed| {
|
|
Output.prettyln(" <red>{s}<r>: {s}", .{ failed.package_name, failed.reason });
|
|
}
|
|
}
|
|
|
|
return if (failed_fixes.items.len > 0) 1 else 0;
|
|
}
|
|
|
|
const FixResult = union(enum) {
|
|
success: struct {
|
|
literal: []const u8,
|
|
version: Semver.Version,
|
|
},
|
|
no_safe_version,
|
|
error: []const u8,
|
|
};
|
|
|
|
const FailedFix = struct {
|
|
package_name: []const u8,
|
|
reason: []const u8,
|
|
};
|
|
|
|
fn findFixForPackage(
|
|
allocator: std.mem.Allocator,
|
|
pm: *PackageManager,
|
|
package_info: *const PackageInfo,
|
|
) bun.OOM!FixResult {
|
|
// Combine all vulnerable version ranges
|
|
var combined_vulnerable_ranges = std.ArrayList(Semver.Query.Group).init(allocator);
|
|
defer {
|
|
for (combined_vulnerable_ranges.items) |*range| {
|
|
range.deinit();
|
|
}
|
|
combined_vulnerable_ranges.deinit();
|
|
}
|
|
|
|
for (package_info.vulnerabilities.items) |vuln| {
|
|
if (vuln.vulnerable_versions.len == 0) continue;
|
|
|
|
const range = try Semver.Query.parse(
|
|
allocator,
|
|
vuln.vulnerable_versions,
|
|
Semver.SlicedString.init(vuln.vulnerable_versions, vuln.vulnerable_versions),
|
|
);
|
|
try combined_vulnerable_ranges.append(range);
|
|
}
|
|
|
|
// Fetch available versions from registry
|
|
const manifest = try fetchPackageManifest(pm, package_info.name);
|
|
|
|
// Find minimum version that doesn't satisfy ANY vulnerable range
|
|
const versions = manifest.versions;
|
|
for (versions) |version| {
|
|
var is_safe = true;
|
|
|
|
for (combined_vulnerable_ranges.items) |vulnerable_range| {
|
|
if (vulnerable_range.satisfies(version, manifest.string_buf, manifest.string_buf)) {
|
|
is_safe = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_safe) {
|
|
const version_str = try std.fmt.allocPrint(allocator, "{}", .{version});
|
|
return FixResult{
|
|
.success = .{
|
|
.literal = version_str,
|
|
.version = version,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
return FixResult.no_safe_version;
|
|
}
|
|
|
|
// Mock function - would need to be implemented to fetch from registry
|
|
fn fetchPackageManifest(pm: *PackageManager, package_name: []const u8) !PackageManifest {
|
|
// This would:
|
|
// 1. Check if manifest is already cached
|
|
// 2. If not, fetch from registry
|
|
// 3. Parse and return manifest with available versions
|
|
_ = pm;
|
|
_ = package_name;
|
|
@panic("fetchPackageManifest not implemented");
|
|
}
|
|
}; |