Files
bun.sh/example-audit-fix-implementation.zig
2025-06-10 04:56:00 +00:00

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");
}
};