const std = @import("std");const command_lex = @import("command_lex.zig");const sandbox = @import("../permissions/sandbox.zig"); const max_command_bytes = 8 * 1024;const max_ls_operands = 64;const max_git_pathspecs = 64;const max_line_count = 10_000;const max_git_log_count = 1_000;pub const max_direct_pipeline_stages = 8; const PrintfFormatLanguage = enum { portable_literal_newline_percent_string,}; const PrintfPolicy = struct { executable: []const u8, format_language: PrintfFormatLanguage = .portable_literal_newline_percent_string,}; const LsSymlinkSemantics = enum { platform_default,}; const LsPolicy = struct { executable: []const u8, forced_argv: []const []const u8, symlink_semantics: LsSymlinkSemantics = .platform_default,}; pub const ApprovalReason = enum { filesystem_write, network_access, process_or_system, background_process, dynamic_shell, unsupported_shell, unsupported_backend, unsupported_platform, unsupported_input_redirect, unsupported_argument, command_owned_input, unknown_command, planning_failure,}; pub const EnvironmentProfile = enum { basic_read_only, git_read_only,}; pub const DirectStage = struct { executable: []const u8, argv: []const []const u8, environment_profile: EnvironmentProfile,}; pub const DirectReadOnlyPlan = struct { command: []const u8, cwd: []const u8, stages: []const DirectStage, pub fn deinit(self: *DirectReadOnlyPlan, alloc: std.mem.Allocator) void { for (self.stages) |stage| { for (stage.argv) |arg| alloc.free(arg); alloc.free(stage.argv); } alloc.free(self.stages); alloc.free(self.cwd); alloc.free(self.command); self.* = undefined; }}; pub const Admission = union(enum) { direct_read_only: DirectReadOnlyPlan, approval_required: ApprovalReason, pub fn deinit(self: *Admission, alloc: std.mem.Allocator) void { switch (self.*) { .direct_read_only => |*direct| direct.deinit(alloc), .approval_required => {}, } self.* = undefined; }}; pub fn plan( alloc: std.mem.Allocator, command: []const u8, resolved_cwd: []const u8, background: bool, backend: sandbox.BackendKind, target_os: std.Target.Os.Tag,) std.mem.Allocator.Error!Admission { if (background) return .{ .approval_required = .background_process }; if (backend != .none and backend != .macos) { return .{ .approval_required = .unsupported_backend }; } if (target_os != .macos and target_os != .linux) { return .{ .approval_required = .unsupported_platform }; } const trimmed = std.mem.trim(u8, command, " \t\n"); if (trimmed.len == 0) return .{ .approval_required = .unknown_command }; if (trimmed.len > max_command_bytes) return .{ .approval_required = .unsupported_argument }; if (validateShellShape(trimmed)) |reason| { return .{ .approval_required = reason }; } var scratch_state = std.heap.ArenaAllocator.init(alloc); defer scratch_state.deinit(); const scratch = scratch_state.allocator(); const segments = command_lex.pipe_segments(scratch, trimmed) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return .{ .approval_required = .unsupported_shell }, }; if (segments.len == 0) return .{ .approval_required = .unknown_command }; if (segments.len > max_direct_pipeline_stages) { return .{ .approval_required = .process_or_system }; } var planned: std.ArrayList(TemporaryStage) = .empty; for (segments) |segment| { if (segment.text.len == 0) return .{ .approval_required = .unsupported_shell }; const argv = command_lex.tokenize_argv(scratch, segment.text) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return .{ .approval_required = .unsupported_shell }, }; const parsed = try parseStage(scratch, argv.tokens, target_os); switch (parsed) { .approval_required => |reason| return .{ .approval_required = reason }, .direct => |stage| try planned.append(scratch, stage), } } return ownPlan(alloc, command, resolved_cwd, planned.items);} const TemporaryStage = struct { executable: []const u8, argv: []const []const u8, environment_profile: EnvironmentProfile = .basic_read_only,}; const StageAdmission = union(enum) { direct: TemporaryStage, approval_required: ApprovalReason,}; fn validateShellShape(command: []const u8) ?ApprovalReason { if (!std.unicode.utf8ValidateSlice(command) or std.mem.findScalar(u8, command, 0) != null) { return .unsupported_shell; } if (std.mem.findScalar(u8, command, '\r') != null) return .unsupported_shell; var in_single = false; var in_double = false; var i: usize = 0; while (i < command.len) : (i += 1) { const ch = command[i]; if (in_single) { if (ch == '\'') in_single = false; continue; } if (in_double) { if (ch == '"') { in_double = false; continue; } if (ch == '$' or ch == '`') return .dynamic_shell; if (ch == '\\' or ch == '\n') return .unsupported_shell; continue; } switch (ch) { '\'' => in_single = true, '"' => in_double = true, '$', '`', '*', '?', '[', '~' => return .dynamic_shell, '\\', '#', ';', '\n', '(', ')', '{', '}' => return .unsupported_shell, '&' => { if (i + 1 < command.len and command[i + 1] == '>') return .filesystem_write; return .unsupported_shell; }, '|' => { if (i + 1 < command.len and command[i + 1] == '|') return .unsupported_shell; }, '>' => return .filesystem_write, '<' => { if (i + 1 < command.len) { switch (command[i + 1]) { '(' => return .dynamic_shell, '>' => return .filesystem_write, '<', '&' => return .unsupported_shell, else => {}, } } }, else => {}, } } return if (in_single or in_double) .unsupported_shell else null;} fn parseStage( alloc: std.mem.Allocator, tokens: []const command_lex.ArgvToken, target_os: std.Target.Os.Tag,) std.mem.Allocator.Error!StageAdmission { var words: std.ArrayList([]const u8) = .empty; var index: usize = 0; while (index < tokens.len) { const token = tokens[index]; if (!token.quoted and std.mem.eql(u8, token.value, "<")) { return .{ .approval_required = .unsupported_input_redirect }; } if (isOperatorToken(token)) return .{ .approval_required = .unsupported_shell }; try words.append(alloc, token.value); index += 1; } if (words.items.len == 0) return .{ .approval_required = .unknown_command }; if (std.mem.findScalar(u8, words.items[0], '=') != null) { return .{ .approval_required = .dynamic_shell }; } const family = try planFamily(alloc, words.items, target_os); return switch (family) { .approval_required => |reason| .{ .approval_required = reason }, .direct => |stage| .{ .direct = .{ .executable = stage.executable, .argv = stage.argv, .environment_profile = stage.environment_profile, } }, };} fn isOperatorToken(token: command_lex.ArgvToken) bool { if (token.quoted) return false; return std.mem.eql(u8, token.value, "|") or std.mem.eql(u8, token.value, "||") or std.mem.eql(u8, token.value, "&") or std.mem.eql(u8, token.value, "&&") or std.mem.eql(u8, token.value, ";") or command_lex.redirection_kind(token.value) != null;} fn planFamily( alloc: std.mem.Allocator, words: []const []const u8, target_os: std.Target.Os.Tag,) std.mem.Allocator.Error!StageAdmission { const command = words[0]; if (isFilesystemMutation(command)) return .{ .approval_required = .filesystem_write }; if (isNetworkCommand(command)) return .{ .approval_required = .network_access }; if (isProcessOrSystemCommand(command)) return .{ .approval_required = .process_or_system }; if (std.mem.eql(u8, command, "printf")) return planPrintf(alloc, words, target_os); if (std.mem.eql(u8, command, "pwd")) return planPwd(alloc, words); if (std.mem.eql(u8, command, "ls")) return planLs(alloc, words, target_os); if (std.mem.eql(u8, command, "wc")) return planWc(alloc, words); if (std.mem.eql(u8, command, "cat")) return planCat(alloc, words); if (std.mem.eql(u8, command, "head")) return planHeadOrTail(alloc, words, "/usr/bin/head"); if (std.mem.eql(u8, command, "tail")) return planHeadOrTail(alloc, words, "/usr/bin/tail"); if (std.mem.eql(u8, command, "grep")) return planGrep(alloc, words); if (std.mem.eql(u8, command, "git")) return planGit(alloc, words); return .{ .approval_required = .unknown_command };} fn planPrintf( alloc: std.mem.Allocator, words: []const []const u8, target_os: std.Target.Os.Tag,) std.mem.Allocator.Error!StageAdmission { const policy = printfPolicy(target_os); if (words.len < 2) return .{ .approval_required = .unsupported_argument }; const format = words[1]; const conversions = printfStringConversionCount(format) orelse { return .{ .approval_required = .unsupported_argument }; }; if (words.len - 2 != conversions) return .{ .approval_required = .unsupported_argument }; var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, policy.executable); try argv.appendSlice(alloc, words[1..]); return .{ .direct = .{ .executable = policy.executable, .argv = try argv.toOwnedSlice(alloc), } };} fn printfPolicy(target_os: std.Target.Os.Tag) PrintfPolicy { return switch (target_os) { .macos, .linux => .{ .executable = "/usr/bin/printf" }, else => unreachable, };} fn printfStringConversionCount(format: []const u8) ?usize { if (format.len == 0 or format[0] == '-') return null; var count: usize = 0; var index: usize = 0; while (index < format.len) { const ch = format[index]; if (ch < 0x20 or ch > 0x7e) return null; if (ch == '\\') { if (index + 1 >= format.len or format[index + 1] != 'n') return null; index += 2; continue; } if (ch == '%') { if (index + 1 >= format.len) return null; switch (format[index + 1]) { '%' => {}, 's' => count += 1, else => return null, } index += 2; continue; } index += 1; } return count;} fn planPwd( alloc: std.mem.Allocator, words: []const []const u8,) std.mem.Allocator.Error!StageAdmission { if (words.len > 2 or (words.len == 2 and !std.mem.eql(u8, words[1], "-P"))) { return .{ .approval_required = .unsupported_argument }; } const argv = try alloc.dupe([]const u8, &.{ "/bin/pwd", "-P" }); return .{ .direct = .{ .executable = "/bin/pwd", .argv = argv, } };} fn planLs( alloc: std.mem.Allocator, words: []const []const u8, target_os: std.Target.Os.Tag,) std.mem.Allocator.Error!StageAdmission { const policy = lsPolicy(target_os); var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, policy.executable); try argv.appendSlice(alloc, policy.forced_argv); var operands: std.ArrayList([]const u8) = .empty; var parsing_options = true; var explicit_separator = false; for (words[1..]) |word| { if (parsing_options and std.mem.eql(u8, word, "--")) { parsing_options = false; explicit_separator = true; continue; } if (parsing_options and word.len > 1 and word[0] == '-') { for (word[1..]) |flag| { const canonical = switch (flag) { '1' => "-1", 'a' => "-a", 'A' => "-A", 'd' => "-d", 'F' => "-F", 'l', 'n' => "-n", 'p' => "-p", else => return .{ .approval_required = .unsupported_argument }, }; try argv.append(alloc, canonical); } continue; } parsing_options = false; if (!explicit_separator and word.len > 1 and word[0] == '-') { return .{ .approval_required = .unsupported_argument }; } try operands.append(alloc, word); } if (operands.items.len > max_ls_operands) { return .{ .approval_required = .unsupported_argument }; } try argv.append(alloc, "--"); try argv.appendSlice(alloc, operands.items); return .{ .direct = .{ .executable = policy.executable, .argv = try argv.toOwnedSlice(alloc), } };} fn lsPolicy(target_os: std.Target.Os.Tag) LsPolicy { return switch (target_os) { .macos, .linux => .{ .executable = "/bin/ls", .forced_argv = &.{"-q"}, }, else => unreachable, };} fn planWc( alloc: std.mem.Allocator, words: []const []const u8,) std.mem.Allocator.Error!StageAdmission { var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, "/usr/bin/wc"); for (words[1..]) |word| { if (word.len < 2 or word[0] != '-') { return .{ .approval_required = .unsupported_argument }; } for (word[1..]) |flag| { const canonical = switch (flag) { 'L' => "-L", 'c' => "-c", 'l' => "-l", 'm' => "-m", 'w' => "-w", else => return .{ .approval_required = .unsupported_argument }, }; try argv.append(alloc, canonical); } } return .{ .direct = .{ .executable = "/usr/bin/wc", .argv = try argv.toOwnedSlice(alloc), } };} fn planCat( alloc: std.mem.Allocator, words: []const []const u8,) std.mem.Allocator.Error!StageAdmission { if (words.len != 1) return .{ .approval_required = .unsupported_argument }; const argv = try alloc.dupe([]const u8, &.{"/bin/cat"}); return .{ .direct = .{ .executable = "/bin/cat", .argv = argv, } };} fn planHeadOrTail( alloc: std.mem.Allocator, words: []const []const u8, executable: []const u8,) std.mem.Allocator.Error!StageAdmission { var index: usize = 1; var count: []const u8 = "10"; if (index < words.len and std.mem.eql(u8, words[index], "-n")) { if (index + 1 >= words.len or !boundedDecimal(words[index + 1], max_line_count)) { return .{ .approval_required = .unsupported_argument }; } count = words[index + 1]; index += 2; } if (index != words.len) return .{ .approval_required = .unsupported_argument }; var argv: std.ArrayList([]const u8) = .empty; try argv.appendSlice(alloc, &.{ executable, "-n", count }); return .{ .direct = .{ .executable = executable, .argv = try argv.toOwnedSlice(alloc), } };} fn planGrep( alloc: std.mem.Allocator, words: []const []const u8,) std.mem.Allocator.Error!StageAdmission { var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, "/usr/bin/grep"); var index: usize = 1; while (index < words.len) : (index += 1) { const word = words[index]; if (std.mem.eql(u8, word, "--")) { index += 1; break; } if (word.len <= 1 or word[0] != '-') break; for (word[1..]) |flag| { const canonical = switch (flag) { 'E' => "-E", 'F' => "-F", 'i' => "-i", 'n' => "-n", 'v' => "-v", else => return .{ .approval_required = .unsupported_argument }, }; try argv.append(alloc, canonical); } } if (index >= words.len) return .{ .approval_required = .unsupported_argument }; if (words.len - index != 1) {Lines 1–500 of 1544. Load more
const std = @import("std"); const command_lex = @import("command_lex.zig"); const sandbox = @import("../permissions/sandbox.zig"); const max_command_bytes = 8 * 1024; const max_ls_operands = 64; const max_git_pathspecs = 64; const max_line_count = 10_000; const max_git_log_count = 1_000; pub const max_direct_pipeline_stages = 8; const PrintfFormatLanguage = enum { portable_literal_newline_percent_string, }; const PrintfPolicy = struct { executable: []const u8, format_language: PrintfFormatLanguage = .portable_literal_newline_percent_string, }; const LsSymlinkSemantics = enum { platform_default, }; const LsPolicy = struct { executable: []const u8, forced_argv: []const []const u8, symlink_semantics: LsSymlinkSemantics = .platform_default, }; pub const ApprovalReason = enum { filesystem_write, network_access, process_or_system, background_process, dynamic_shell, unsupported_shell, unsupported_backend, unsupported_platform, unsupported_input_redirect, unsupported_argument, command_owned_input, unknown_command, planning_failure, }; pub const EnvironmentProfile = enum { basic_read_only, git_read_only, }; pub const DirectStage = struct { executable: []const u8, argv: []const []const u8, environment_profile: EnvironmentProfile, }; pub const DirectReadOnlyPlan = struct { command: []const u8, cwd: []const u8, stages: []const DirectStage, pub fn deinit(self: *DirectReadOnlyPlan, alloc: std.mem.Allocator) void { for (self.stages) |stage| { for (stage.argv) |arg| alloc.free(arg); alloc.free(stage.argv); } alloc.free(self.stages); alloc.free(self.cwd); alloc.free(self.command); self.* = undefined; } }; pub const Admission = union(enum) { direct_read_only: DirectReadOnlyPlan, approval_required: ApprovalReason, pub fn deinit(self: *Admission, alloc: std.mem.Allocator) void { switch (self.*) { .direct_read_only => |*direct| direct.deinit(alloc), .approval_required => {}, } self.* = undefined; } }; pub fn plan( alloc: std.mem.Allocator, command: []const u8, resolved_cwd: []const u8, background: bool, backend: sandbox.BackendKind, target_os: std.Target.Os.Tag, ) std.mem.Allocator.Error!Admission { if (background) return .{ .approval_required = .background_process }; if (backend != .none and backend != .macos) { return .{ .approval_required = .unsupported_backend }; } if (target_os != .macos and target_os != .linux) { return .{ .approval_required = .unsupported_platform }; } const trimmed = std.mem.trim(u8, command, " \t\n"); if (trimmed.len == 0) return .{ .approval_required = .unknown_command }; if (trimmed.len > max_command_bytes) return .{ .approval_required = .unsupported_argument }; if (validateShellShape(trimmed)) |reason| { return .{ .approval_required = reason }; } var scratch_state = std.heap.ArenaAllocator.init(alloc); defer scratch_state.deinit(); const scratch = scratch_state.allocator(); const segments = command_lex.pipe_segments(scratch, trimmed) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return .{ .approval_required = .unsupported_shell }, }; if (segments.len == 0) return .{ .approval_required = .unknown_command }; if (segments.len > max_direct_pipeline_stages) { return .{ .approval_required = .process_or_system }; } var planned: std.ArrayList(TemporaryStage) = .empty; for (segments) |segment| { if (segment.text.len == 0) return .{ .approval_required = .unsupported_shell }; const argv = command_lex.tokenize_argv(scratch, segment.text) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return .{ .approval_required = .unsupported_shell }, }; const parsed = try parseStage(scratch, argv.tokens, target_os); switch (parsed) { .approval_required => |reason| return .{ .approval_required = reason }, .direct => |stage| try planned.append(scratch, stage), } } return ownPlan(alloc, command, resolved_cwd, planned.items); } const TemporaryStage = struct { executable: []const u8, argv: []const []const u8, environment_profile: EnvironmentProfile = .basic_read_only, }; const StageAdmission = union(enum) { direct: TemporaryStage, approval_required: ApprovalReason, }; fn validateShellShape(command: []const u8) ?ApprovalReason { if (!std.unicode.utf8ValidateSlice(command) or std.mem.findScalar(u8, command, 0) != null) { return .unsupported_shell; } if (std.mem.findScalar(u8, command, '\r') != null) return .unsupported_shell; var in_single = false; var in_double = false; var i: usize = 0; while (i < command.len) : (i += 1) { const ch = command[i]; if (in_single) { if (ch == '\'') in_single = false; continue; } if (in_double) { if (ch == '"') { in_double = false; continue; } if (ch == '$' or ch == '`') return .dynamic_shell; if (ch == '\\' or ch == '\n') return .unsupported_shell; continue; } switch (ch) { '\'' => in_single = true, '"' => in_double = true, '$', '`', '*', '?', '[', '~' => return .dynamic_shell, '\\', '#', ';', '\n', '(', ')', '{', '}' => return .unsupported_shell, '&' => { if (i + 1 < command.len and command[i + 1] == '>') return .filesystem_write; return .unsupported_shell; }, '|' => { if (i + 1 < command.len and command[i + 1] == '|') return .unsupported_shell; }, '>' => return .filesystem_write, '<' => { if (i + 1 < command.len) { switch (command[i + 1]) { '(' => return .dynamic_shell, '>' => return .filesystem_write, '<', '&' => return .unsupported_shell, else => {}, } } }, else => {}, } } return if (in_single or in_double) .unsupported_shell else null; } fn parseStage( alloc: std.mem.Allocator, tokens: []const command_lex.ArgvToken, target_os: std.Target.Os.Tag, ) std.mem.Allocator.Error!StageAdmission { var words: std.ArrayList([]const u8) = .empty; var index: usize = 0; while (index < tokens.len) { const token = tokens[index]; if (!token.quoted and std.mem.eql(u8, token.value, "<")) { return .{ .approval_required = .unsupported_input_redirect }; } if (isOperatorToken(token)) return .{ .approval_required = .unsupported_shell }; try words.append(alloc, token.value); index += 1; } if (words.items.len == 0) return .{ .approval_required = .unknown_command }; if (std.mem.findScalar(u8, words.items[0], '=') != null) { return .{ .approval_required = .dynamic_shell }; } const family = try planFamily(alloc, words.items, target_os); return switch (family) { .approval_required => |reason| .{ .approval_required = reason }, .direct => |stage| .{ .direct = .{ .executable = stage.executable, .argv = stage.argv, .environment_profile = stage.environment_profile, } }, }; } fn isOperatorToken(token: command_lex.ArgvToken) bool { if (token.quoted) return false; return std.mem.eql(u8, token.value, "|") or std.mem.eql(u8, token.value, "||") or std.mem.eql(u8, token.value, "&") or std.mem.eql(u8, token.value, "&&") or std.mem.eql(u8, token.value, ";") or command_lex.redirection_kind(token.value) != null; } fn planFamily( alloc: std.mem.Allocator, words: []const []const u8, target_os: std.Target.Os.Tag, ) std.mem.Allocator.Error!StageAdmission { const command = words[0]; if (isFilesystemMutation(command)) return .{ .approval_required = .filesystem_write }; if (isNetworkCommand(command)) return .{ .approval_required = .network_access }; if (isProcessOrSystemCommand(command)) return .{ .approval_required = .process_or_system }; if (std.mem.eql(u8, command, "printf")) return planPrintf(alloc, words, target_os); if (std.mem.eql(u8, command, "pwd")) return planPwd(alloc, words); if (std.mem.eql(u8, command, "ls")) return planLs(alloc, words, target_os); if (std.mem.eql(u8, command, "wc")) return planWc(alloc, words); if (std.mem.eql(u8, command, "cat")) return planCat(alloc, words); if (std.mem.eql(u8, command, "head")) return planHeadOrTail(alloc, words, "/usr/bin/head"); if (std.mem.eql(u8, command, "tail")) return planHeadOrTail(alloc, words, "/usr/bin/tail"); if (std.mem.eql(u8, command, "grep")) return planGrep(alloc, words); if (std.mem.eql(u8, command, "git")) return planGit(alloc, words); return .{ .approval_required = .unknown_command }; } fn planPrintf( alloc: std.mem.Allocator, words: []const []const u8, target_os: std.Target.Os.Tag, ) std.mem.Allocator.Error!StageAdmission { const policy = printfPolicy(target_os); if (words.len < 2) return .{ .approval_required = .unsupported_argument }; const format = words[1]; const conversions = printfStringConversionCount(format) orelse { return .{ .approval_required = .unsupported_argument }; }; if (words.len - 2 != conversions) return .{ .approval_required = .unsupported_argument }; var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, policy.executable); try argv.appendSlice(alloc, words[1..]); return .{ .direct = .{ .executable = policy.executable, .argv = try argv.toOwnedSlice(alloc), } }; } fn printfPolicy(target_os: std.Target.Os.Tag) PrintfPolicy { return switch (target_os) { .macos, .linux => .{ .executable = "/usr/bin/printf" }, else => unreachable, }; } fn printfStringConversionCount(format: []const u8) ?usize { if (format.len == 0 or format[0] == '-') return null; var count: usize = 0; var index: usize = 0; while (index < format.len) { const ch = format[index]; if (ch < 0x20 or ch > 0x7e) return null; if (ch == '\\') { if (index + 1 >= format.len or format[index + 1] != 'n') return null; index += 2; continue; } if (ch == '%') { if (index + 1 >= format.len) return null; switch (format[index + 1]) { '%' => {}, 's' => count += 1, else => return null, } index += 2; continue; } index += 1; } return count; } fn planPwd( alloc: std.mem.Allocator, words: []const []const u8, ) std.mem.Allocator.Error!StageAdmission { if (words.len > 2 or (words.len == 2 and !std.mem.eql(u8, words[1], "-P"))) { return .{ .approval_required = .unsupported_argument }; } const argv = try alloc.dupe([]const u8, &.{ "/bin/pwd", "-P" }); return .{ .direct = .{ .executable = "/bin/pwd", .argv = argv, } }; } fn planLs( alloc: std.mem.Allocator, words: []const []const u8, target_os: std.Target.Os.Tag, ) std.mem.Allocator.Error!StageAdmission { const policy = lsPolicy(target_os); var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, policy.executable); try argv.appendSlice(alloc, policy.forced_argv); var operands: std.ArrayList([]const u8) = .empty; var parsing_options = true; var explicit_separator = false; for (words[1..]) |word| { if (parsing_options and std.mem.eql(u8, word, "--")) { parsing_options = false; explicit_separator = true; continue; } if (parsing_options and word.len > 1 and word[0] == '-') { for (word[1..]) |flag| { const canonical = switch (flag) { '1' => "-1", 'a' => "-a", 'A' => "-A", 'd' => "-d", 'F' => "-F", 'l', 'n' => "-n", 'p' => "-p", else => return .{ .approval_required = .unsupported_argument }, }; try argv.append(alloc, canonical); } continue; } parsing_options = false; if (!explicit_separator and word.len > 1 and word[0] == '-') { return .{ .approval_required = .unsupported_argument }; } try operands.append(alloc, word); } if (operands.items.len > max_ls_operands) { return .{ .approval_required = .unsupported_argument }; } try argv.append(alloc, "--"); try argv.appendSlice(alloc, operands.items); return .{ .direct = .{ .executable = policy.executable, .argv = try argv.toOwnedSlice(alloc), } }; } fn lsPolicy(target_os: std.Target.Os.Tag) LsPolicy { return switch (target_os) { .macos, .linux => .{ .executable = "/bin/ls", .forced_argv = &.{"-q"}, }, else => unreachable, }; } fn planWc( alloc: std.mem.Allocator, words: []const []const u8, ) std.mem.Allocator.Error!StageAdmission { var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, "/usr/bin/wc"); for (words[1..]) |word| { if (word.len < 2 or word[0] != '-') { return .{ .approval_required = .unsupported_argument }; } for (word[1..]) |flag| { const canonical = switch (flag) { 'L' => "-L", 'c' => "-c", 'l' => "-l", 'm' => "-m", 'w' => "-w", else => return .{ .approval_required = .unsupported_argument }, }; try argv.append(alloc, canonical); } } return .{ .direct = .{ .executable = "/usr/bin/wc", .argv = try argv.toOwnedSlice(alloc), } }; } fn planCat( alloc: std.mem.Allocator, words: []const []const u8, ) std.mem.Allocator.Error!StageAdmission { if (words.len != 1) return .{ .approval_required = .unsupported_argument }; const argv = try alloc.dupe([]const u8, &.{"/bin/cat"}); return .{ .direct = .{ .executable = "/bin/cat", .argv = argv, } }; } fn planHeadOrTail( alloc: std.mem.Allocator, words: []const []const u8, executable: []const u8, ) std.mem.Allocator.Error!StageAdmission { var index: usize = 1; var count: []const u8 = "10"; if (index < words.len and std.mem.eql(u8, words[index], "-n")) { if (index + 1 >= words.len or !boundedDecimal(words[index + 1], max_line_count)) { return .{ .approval_required = .unsupported_argument }; } count = words[index + 1]; index += 2; } if (index != words.len) return .{ .approval_required = .unsupported_argument }; var argv: std.ArrayList([]const u8) = .empty; try argv.appendSlice(alloc, &.{ executable, "-n", count }); return .{ .direct = .{ .executable = executable, .argv = try argv.toOwnedSlice(alloc), } }; } fn planGrep( alloc: std.mem.Allocator, words: []const []const u8, ) std.mem.Allocator.Error!StageAdmission { var argv: std.ArrayList([]const u8) = .empty; try argv.append(alloc, "/usr/bin/grep"); var index: usize = 1; while (index < words.len) : (index += 1) { const word = words[index]; if (std.mem.eql(u8, word, "--")) { index += 1; break; } if (word.len <= 1 or word[0] != '-') break; for (word[1..]) |flag| { const canonical = switch (flag) { 'E' => "-E", 'F' => "-F", 'i' => "-i", 'n' => "-n", 'v' => "-v", else => return .{ .approval_required = .unsupported_argument }, }; try argv.append(alloc, canonical); } } if (index >= words.len) return .{ .approval_required = .unsupported_argument }; if (words.len - index != 1) {