Codex 系统手册

沙箱选择和 Unix 平台启动器

stage-14.2.416 个文件

这一阶段是在真正运行外部命令前,先决定“要不要关进沙箱、怎么关”。入口和管家负责统一不同系统的做法,整理权限、目录、网络和环境变量。Linux 上有两套栏杆:bubblewrap 像临时隔离房间,Landlock/seccomp 像门禁和断网开关;相关文件会检查工具是否可用、找项目自带版本,并把普通命令改写成沙箱启动命令。Unix 的提权部分则像值班员:命令想跳出限制时,客户端先问服务端,服务端按规则决定放行、升级权限或拒绝。

本阶段的文件16

沙盒 API 与选择

这些文件定义公共沙盒接口,以及选择沙盒策略并将启动请求改写为平台专用执行形式的核心逻辑。

sandboxing/src/lib.rs源码 ↗
orchestrationcross-cutting

沙箱可以理解成给程序画一个安全活动范围:它能读写什么、能不能访问某些目录,都被限制住,避免误伤系统或泄露数据。这个文件本身不做具体隔离工作,而是像一个前台接待,把 Linux 的 bubblewrap、Landlock,macOS 的 seatbelt,以及通用的沙箱管理器统一导出给其他代码使用。这样外部代码不用关心“现在是什么系统、该用哪套沙箱”。它还处理一个很重要的边界问题:沙箱设置失败时,要把内部错误变成统一的 CodexErr,也就是项目对外认识的错误格式。特别地,在非 Linux 系统上,bubblewrap 相关警告永远返回空,因为那里根本不使用这套 Linux 工具。

函数细节2
system_bwrap_warning27–31 ↗
fn system_bwrap_warning(
    _permission_profile: &codex_protocol::models::PermissionProfile,
) -> Option<String>

作用:这个函数在非 Linux 系统上提供一个“占位版本”。因为 bubblewrap 是 Linux 上的沙箱工具,在其他系统上没有相关警告,所以它直接表示“没有警告”。

数据流:进去的是一个权限配置,但在非 Linux 系统上它不会被使用;函数不检查、不转换任何内容;出来的是 None,意思是没有需要展示给用户的 bubblewrap 警告,也不会改动任何状态。

调用关系:其他代码可以统一调用 system_bwrap_warning,而不用先判断当前系统是不是 Linux。在 Linux 上真正的实现来自 bwrap 模块;在非 Linux 上就走这里这个空实现,保证调用方的流程不被平台差异打断。

CodexErr::from34–52 ↗
fn from(err: SandboxTransformError) -> Self

作用:这个函数把沙箱转换过程中出现的内部错误,翻译成项目统一的 CodexErr 错误。这样上层代码和用户看到的是稳定、可理解的错误,而不是零散的底层细节。

数据流:进去的是一个 SandboxTransformError,也就是准备沙箱命令或策略时出的错;函数根据错误种类分类:路径不合法会变成 InvalidRequest,缺少 Linux 沙箱执行程序会变成对应的沙箱工具缺失错误,平台不支持的功能会变成 UnsupportedOperation;出来的是一个 CodexErr,原错误不会被修复,只是被换成更适合对外传递的形式。

调用关系:当沙箱管理器或策略转换代码失败时,上层需要把失败原因交给协议层或请求处理层。这个函数就是中间的翻译员:它接收 SandboxTransformError,并调用 CodexErr 的 InvalidRequest 或 UnsupportedOperation 等构造方式,生成整个项目都能识别的错误。

调用图:外部调用 2 个(InvalidRequest, UnsupportedOperation)。

sandboxing/src/manager.rs源码 ↗
orchestrationrequest handling

可以把这个文件想成机场安检口。外面传进来一个要执行的命令、它想在哪个目录运行、允许读写哪些文件、能不能联网等信息;这里先把这些信息检查一遍,把 URI 路径转换成本机真正能用的绝对路径,再根据当前操作系统选择合适的沙箱。沙箱就是一个受限制的小房间,程序在里面运行,不能随便碰外面的文件和网络。它还会处理一些特殊情况,比如网络代理需要读取托管的 MITM CA 证书包时,会额外把这个证书路径加入“可读名单”;Linux 上如果要用 bubblewrap(一种 Linux 沙箱工具)但运行在不支持的 WSL1 环境里,就会提前报错。最后它产出一个 SandboxExecRequest,也就是执行层真正能拿去启动进程的“最终发车单”。

函数细节14
SandboxType::as_metric_tag33–40 ↗
fn as_metric_tag(self) -> &'static str

作用:把沙箱类型变成一个短字符串,方便打点、统计和日志记录。比如 Linux 的 seccomp 沙箱会变成 "seccomp"。

数据流:进去的是一个 SandboxType 枚举值,也就是“当前选的是哪种沙箱” → 它按固定表格查对应名字 → 出来的是一个静态字符串,不改动任何状态。

调用关系:它通常服务于监控和记录环节,让外部系统不用理解 Rust 枚举,也能用统一标签统计不同沙箱的使用情况。

get_platform_sandbox50–64 ↗
fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType>

作用:根据当前操作系统,回答“这台机器默认能用哪种平台沙箱”。Windows 还要看配置里有没有启用 Windows 沙箱。

数据流:进去的是 windows_sandbox_enabled 这个开关 → 它检查编译目标系统:macOS 返回 Seatbelt,Linux 返回 Seccomp,Windows 在开关打开时返回受限令牌沙箱,否则返回没有沙箱 → 出来的是一个可选的 SandboxType。

调用关系:SandboxManager::select_initial 会调用它,把用户偏好和权限需求落实成具体平台上的沙箱选择。它依赖 cfg! 这类编译期系统判断。

调用图:被 1 处调用(select_initial);外部调用 1 个(cfg!)。

with_managed_mitm_ca_readable_root66–85 ↗
fn with_managed_mitm_ca_readable_root(
    permission_profile: PermissionProfile,
    managed_mitm_ca_trust_bundle_path: Option<&AbsolutePathBuf>,
    sandbox_policy_cwd: &Path,
) -> PermissionProfile

作用:如果网络代理提供了托管的 MITM CA 证书包,就把这个证书文件加入沙箱的“允许读取”范围。这样被沙箱关住的程序才能读取证书,正常走受控网络代理。

数据流:进去的是原始权限配置、可选的证书包路径、沙箱策略用的当前目录 → 如果没有证书路径,就原样返回权限配置;如果有,就先拆出文件系统和网络权限,把证书路径加进可读根目录,再按原来的 enforcement(强制执行级别)组装回新的 PermissionProfile → 出来的是可能被扩展过的权限配置。

调用关系:SandboxManager::transform 在计算最终权限时会调用它。它夹在“合并用户权限”和“生成执行请求”之间,保证网络代理需要的证书不会被沙箱自己挡住。

调用图:调用 3 个内部函数(enforcement, from_runtime_permissions_with_enforcement, to_runtime_permissions);被 1 处调用(transform);外部调用 1 个(from_ref)。

SandboxTransformError::fmt152–172 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把沙箱转换过程中出现的错误,变成人能看懂的错误信息。比如路径无效、Linux 沙箱程序缺失、当前平台不支持某种沙箱。

数据流:进去的是一个 SandboxTransformError 错误值和格式化输出器 → 它根据错误种类写出不同的说明文字 → 出来的是格式化结果,供日志、命令行或上层错误展示使用。

调用关系:当 SandboxManager::transform 或相关检查返回错误后,外层代码展示错误时会间接用到它。它不解决错误,只负责把错误说清楚。

调用图:外部调用 1 个(write!)。

SandboxTransformError::source176–186 ↗
fn source(&self) -> Option<&(dyn std::error::Error + 'static)>

作用:告诉错误处理系统:这个沙箱错误背后有没有更底层的原始错误。比如路径转换失败时,底层的 io::Error 会被保留下来。

数据流:进去的是一个 SandboxTransformError → 如果错误是命令工作目录或策略工作目录无效,就返回里面包着的底层 IO 错误;其他错误没有底层来源,就返回空 → 不改动任何状态。

调用关系:这是 Rust 标准错误接口的一部分。上层如果想追踪“错误链”,会通过它看到最早的失败原因。

SandboxManager::new193–195 ↗
fn new() -> Self

作用:创建一个新的 SandboxManager。这个管理器本身不保存复杂状态,所以创建过程很轻。

数据流:没有输入 → 直接构造一个空的 SandboxManager → 出来的是可用于选择和转换沙箱请求的管理器实例。

调用关系:很多执行准备流程和测试都会先调用它,再调用 select_initial 或 transform。它相当于拿到一位“沙箱管家”,之后具体干活还要看传入的请求。

调用图:被 15 处调用(build_exec_request, select_process_exec_tool_sandbox_type, new, file_system_sandbox_context_uses_active_attempt, no_sandbox_attempt_has_no_file_system_context, explicit_escalation_prepares_exec_without_managed_network, prepare_sandboxed_exec, sandbox_exec_request, danger_full_access_defaults_to_no_sandbox_without_network_requirements, danger_full_access_uses_platform_sandbox_with_network_requirements (+5 more))。

SandboxManager::select_initial197–224 ↗
fn select_initial(
        &self,
        file_system_policy: &FileSystemSandboxPolicy,
        network_policy: NetworkSandboxPolicy,
        pref: SandboxablePreference,
        windows_sandbox_level

作用:在真正启动命令前,先决定初始沙箱类型:不用沙箱、必须用平台沙箱,还是按权限需求自动判断。

数据流:进去的是文件权限策略、网络权限策略、用户偏好、Windows 沙箱级别、是否有托管网络要求 → 如果用户禁止沙箱就返回 None;如果用户要求沙箱就尽量选当前平台沙箱;如果是自动模式,就先问权限策略是否需要平台沙箱,再决定 → 出来的是一个 SandboxType。

调用关系:运行主流程 run 会调用它来做第一轮选择。它把活儿分给 should_require_platform_sandbox 判断“有没有必要”,再用 get_platform_sandbox 找“这台机器能用哪种”。

调用图:调用 2 个内部函数(get_platform_sandbox, should_require_platform_sandbox);被 1 处调用(run)。

SandboxManager::transform226–338 ↗
fn transform(
        &self,
        request: SandboxTransformRequest<'_>,
    ) -> Result<SandboxExecRequest, SandboxTransformError>

作用:把一个高层的沙箱启动请求,整理成执行层可以直接使用的 SandboxExecRequest。它是这个文件里最核心的“装配线”。

数据流:进去的是 SandboxTransformRequest,里面有命令、参数、环境变量、工作目录 URI、权限配置、网络代理、沙箱类型和平台选项 → 它先把命令工作目录和策略工作目录转换成本机绝对路径;再合并额外权限,必要时加入代理证书可读路径;然后把程序名和参数拼成 argv;最后按沙箱类型包装命令:无沙箱就直接运行,macOS 用 seatbelt 包一层,Linux 用 codex-linux-sandbox 包一层,Windows 保留原命令并携带 Windows 沙箱设置 → 出来的是 SandboxExecRequest;如果路径无效、缺少 Linux 沙箱程序或 WSL1 不支持,就返回错误。

调用关系:env_for 会调用它准备真正执行命令前的最终请求。它会调用 with_managed_mitm_ca_readable_root 处理代理证书,调用 os_argv_to_strings 和 os_string_to_command_component 整理命令参数,Linux 分支还会调用 allow_network_for_proxy、ensure_linux_bubblewrap_is_supported、create_linux_sandbox_command_args_for_permission_profile 和 linux_sandbox_arg0_override。

调用图:调用 9 个内部函数(is_wsl1, allow_network_for_proxy, create_linux_sandbox_command_args_for_permission_profile, ensure_linux_bubblewrap_is_supported, linux_sandbox_arg0_override, os_argv_to_strings, os_string_to_command_component, with_managed_mitm_ca_readable_root, effective_permission_profile);被 1 处调用(env_for);外部调用 1 个(with_capacity)。

compatibility_sandbox_policy_for_permission_profile341–351 ↗
fn compatibility_sandbox_policy_for_permission_profile(
    permissions: &PermissionProfile,
    cwd: &Path,
) -> SandboxPolicy

作用:把新的 PermissionProfile 权限配置转成旧接口还能理解的 SandboxPolicy。这样新旧两套权限表达方式可以暂时兼容。

数据流:进去的是 PermissionProfile 和当前目录 cwd → 它先尝试用权限配置自己的旧格式转换;如果失败,就退一步拆出运行时文件系统和网络权限,再生成一个接近旧行为的 WorkspaceWrite 策略 → 出来的是 SandboxPolicy。

调用关系:它是兼容层函数,主要给还没完全迁移到新权限模型的代码使用。它优先走 to_legacy_sandbox_policy,失败时把工作交给 compatibility_workspace_write_policy。

调用图:调用 1 个内部函数(to_legacy_sandbox_policy)。

compatibility_workspace_write_policy353–382 ↗
fn compatibility_workspace_write_policy(
    file_system_policy: FileSystemSandboxPolicy,
    network_policy: NetworkSandboxPolicy,
    cwd: &Path,
) -> SandboxPolicy

作用:在无法直接转成旧策略时,临时拼出一个“工作区可写”的旧沙箱策略。它尽量根据新文件系统权限还原旧系统关心的几个开关。

数据流:进去的是文件系统权限、网络权限和当前目录 cwd → 它找出允许写入的根目录,排除当前工作目录本身;检查 TMPDIR 环境变量指向的临时目录能不能写;再检查 /tmp 能不能写;同时读取网络是否允许 → 出来的是 SandboxPolicy::WorkspaceWrite,里面写明可写目录、是否允许网络、是否排除临时目录。

调用关系:compatibility_sandbox_policy_for_permission_profile 在旧格式转换失败时会调用它。它会询问 FileSystemSandboxPolicy 的可写目录和路径写权限,也会读取环境变量 TMPDIR。

调用图:调用 4 个内部函数(can_write_path_with_cwd, get_writable_roots_with_cwd, is_enabled, from_absolute_path);外部调用 2 个(new, var_os)。

ensure_linux_bubblewrap_is_supported385–398 ↗
fn ensure_linux_bubblewrap_is_supported(
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    use_legacy_landlock: bool,
    allow_network_for_proxy: bool,
    is_wsl1: bool,
) -> Result<(),

作用:在 Linux 上提前检查:当前情况是否需要 bubblewrap 沙箱工具,以及 WSL1 能不能承受。WSL1 是较老的 Windows Linux 子系统,某些沙箱能力不够。

数据流:进去的是文件系统权限、是否使用旧版 Landlock、是否需要给代理放行网络、当前是否 WSL1 → 它判断只要代理网络需要放行,或者新版 Landlock 下没有全盘写权限,就需要 bubblewrap;如果又是在 WSL1,就返回不支持错误;否则返回成功 → 不改动状态。

调用关系:SandboxManager::transform 的 LinuxSeccomp 分支会调用它。它在生成 Linux 沙箱命令前把不可能成功的场景拦住,避免后面启动时才失败。

调用图:调用 1 个内部函数(has_full_disk_write_access);被 1 处调用(transform)。

os_argv_to_strings400–404 ↗
fn os_argv_to_strings(argv: Vec<OsString>) -> Vec<String>

作用:把操作系统风格的命令参数 OsString 列表,转换成普通 String 列表。这样后续构造沙箱命令时更容易处理。

数据流:进去的是 Vec<OsString>,也就是程序名和参数组成的列表 → 它逐个调用 os_string_to_command_component 做转换 → 出来的是 Vec<String>。

调用关系:SandboxManager::transform 在无沙箱、macOS 沙箱、Linux 沙箱和 Windows 分支都会用到它,用来把命令参数整理成统一格式。

调用图:被 1 处调用(transform)。

os_string_to_command_component406–410 ↗
fn os_string_to_command_component(value: OsString) -> String

作用:把单个 OsString 转成 String。遇到不是合法 UTF-8 的内容时,也不会直接崩溃,而是用有损方式转成可显示文本。

数据流:进去的是一个 OsString → 它先尝试正常转成 String;如果失败,就用 to_string_lossy 把无法表示的字符替换掉 → 出来的是一个 String。

调用关系:SandboxManager::transform 会用它处理 Linux 沙箱程序路径,os_argv_to_strings 会用它处理整组命令参数,linux_sandbox_arg0_override 也会用它生成 arg0 覆盖值。

调用图:被 2 处调用(transform, linux_sandbox_arg0_override);外部调用 1 个(into_string)。

linux_sandbox_arg0_override412–418 ↗
fn linux_sandbox_arg0_override(exe: &Path) -> String

作用:为 Linux 沙箱程序决定启动时显示给它自己的 arg0 名字。arg0 可以理解为程序启动时看到的“自己叫什么”。

数据流:进去的是 Linux 沙箱可执行文件路径 → 它查看文件名是否正好等于约定的 CODEX_LINUX_SANDBOX_ARG0;如果是,就用真实路径字符串;否则用约定名称作为 arg0 → 出来的是一个 String。

调用关系:SandboxManager::transform 的 LinuxSeccomp 分支在包装命令时会调用它。它还会调用 os_string_to_command_component 来安全地把路径转成字符串。

调用图:调用 1 个内部函数(os_string_to_command_component);被 1 处调用(transform);外部调用 2 个(as_os_str, file_name)。

sandboxing/src/bwrap.rs源码 ↗
domain_logicstartup / sandbox prerequisite check

Codex 在 Linux 上需要用 bubblewrap 来做沙箱,沙箱可以理解成给命令临时搭一个“隔离小房间”,限制它能看哪些文件、能不能联网。这个文件做的事就是开工前检查这间小房间能不能搭起来。它先看当前权限设置是否真的需要平台级沙箱;如果需要,就去 PATH 里找系统安装的 bwrap。找不到时提示安装。找到后,还会专门试跑一次 bwrap,看系统是否允许创建用户命名空间(Linux 的隔离能力之一,简单说就是让进程在自己的“小世界”里假装有独立用户和网络环境)。它还特别识别 WSL1,因为 WSL1 天生不支持这里需要的能力。整体上它比较保守:只有确认是用户命名空间相关失败时才警告,超时或探测异常时尽量不误报。

函数细节9
system_bwrap_warning40–47 ↗
fn system_bwrap_warning(permission_profile: &PermissionProfile) -> Option<String>

作用:这是对外最主要的入口:给它一份权限配置,它会判断要不要提醒用户 bubblewrap 相关问题。有人在准备启用 Linux 沙箱前会调用它,用来提前拿到一段可展示给用户的警告文字。

数据流:输入是一份 PermissionProfile,也就是“这次命令允许做什么、不允许做什么”的权限说明。它先问 should_warn_about_system_bwrap:这种权限配置是否需要真正的系统沙箱;如果不需要,就直接返回 None。需要的话,它再调用 find_system_bwrap_in_path 找系统里的 bwrap,最后把找到或没找到的结果交给 system_bwrap_warning_for_path,输出一段警告文字或 None。

调用关系:它像总接待员一样串起整套检查流程:先让 should_warn_about_system_bwrap 判断有没有检查必要,再让 find_system_bwrap_in_path 找工具,最后让 system_bwrap_warning_for_path 做具体诊断。调用者只需要看它有没有返回警告,不必知道里面分了多少步。

调用图:调用 3 个内部函数(find_system_bwrap_in_path, should_warn_about_system_bwrap, system_bwrap_warning_for_path)。

should_warn_about_system_bwrap49–56 ↗
fn should_warn_about_system_bwrap(permission_profile: &PermissionProfile) -> bool

作用:这个函数判断“当前权限要求是不是严到必须依赖系统沙箱”。如果本来就不需要 bubblewrap,那就没必要吓用户说 bwrap 有问题。

数据流:输入是 PermissionProfile。它先把这份权限配置转换成运行时真正使用的文件系统策略和网络策略,然后调用 should_require_platform_sandbox 判断是否需要平台级沙箱。输出是 true 或 false:true 表示后面应该继续检查 bwrap,false 表示不用管。

调用关系:它被 system_bwrap_warning 放在第一步调用,作用像门卫:只有确实需要 Linux 沙箱时,后面的查找 bwrap、试跑 bwrap 才会发生。它把权限解释工作交给 to_runtime_permissions,把最终判断交给 should_require_platform_sandbox。

调用图:调用 2 个内部函数(to_runtime_permissions, should_require_platform_sandbox);被 1 处调用(system_bwrap_warning)。

system_bwrap_warning_for_path58–72 ↗
fn system_bwrap_warning_for_path(system_bwrap_path: Option<&Path>) -> Option<String>

作用:这个函数根据“系统里有没有 bwrap、当前系统支不支持、权限够不够”来决定具体该给哪种警告。它把抽象问题变成用户能看懂的提示。

数据流:输入是一个可选的 bwrap 路径:有路径表示找到了,None 表示没找到。它先检查是不是 WSL1;如果是,直接返回 WSL1 不支持的警告。不是 WSL1 但没找到 bwrap,就返回“请安装 bubblewrap”的警告。找到了 bwrap 后,它调用 system_bwrap_has_user_namespace_access 试探权限;如果发现不能创建用户命名空间,就返回权限警告;一切正常则返回 None。

调用关系:它由 system_bwrap_warning 在找到路径后调用,是整个流程里的“诊断医生”。它会向 is_wsl1 询问系统类型,并把最重的实际试跑检查交给 system_bwrap_has_user_namespace_access。

调用图:调用 2 个内部函数(is_wsl1, system_bwrap_has_user_namespace_access);被 1 处调用(system_bwrap_warning)。

system_bwrap_has_user_namespace_access74–136 ↗
fn system_bwrap_has_user_namespace_access(system_bwrap_path: &Path, timeout: Duration) -> bool

作用:这个函数实际运行一次 bwrap,检查系统是否允许它创建沙箱所需的用户命名空间和网络隔离。它不是正式跑用户命令,只是做一次小测试。

数据流:输入是 bwrap 程序路径和一个超时时间。它启动一个很小的命令:让 bwrap 建立用户和网络隔离,只读绑定根目录,然后运行 /bin/true。它丢弃标准输出,收集一小段标准错误。进程正常成功,就返回 true;如果失败但错误文字不像已知的用户命名空间问题,也返回 true,避免误报;只有错误文字命中已知失败特征时,才返回 false。若启动失败、等待出错或超时,它会尽量收尾进程,并偏向返回 true。

调用关系:它被 system_bwrap_warning_for_path 调用,是最接近真实系统能力的一步。它会在结束后把错误输出交给 is_user_namespace_failure 判断失败原因;过程中使用系统进程、计时和短暂 sleep 来避免卡住主流程。

调用图:调用 1 个内部函数(is_user_namespace_failure);被 1 处调用(system_bwrap_warning_for_path);外部调用 6 个(now, null, piped, new, new, sleep)。

is_wsl1138–141 ↗
fn is_wsl1() -> bool

作用:这个函数判断当前环境是不是 WSL1。WSL1 是 Windows 上第一代 Linux 兼容层,它不能提供这里需要的用户命名空间,所以必须单独提示。

数据流:它不需要外部输入,而是读取 /proc/version 这个 Linux 系统信息文件。读到内容后,它根据里面的文字判断是否像 WSL1;读不到或判断不符合,就返回 false。输出是一个布尔值:true 表示是 WSL1。

调用关系:它会被 system_bwrap_warning_for_path 用来优先识别 WSL1,因为这种情况不该再继续做普通的 bwrap 权限探测。调用图里还显示它也会被 transform 使用,说明别的沙箱策略转换流程也需要知道是否处在 WSL1。

调用图:被 2 处调用(system_bwrap_warning_for_path, transform);外部调用 1 个(read_to_string)。

proc_version_indicates_wsl1143–159 ↗
fn proc_version_indicates_wsl1(proc_version: &str) -> bool

作用:这个函数只负责从 /proc/version 的文字里判断“看起来是不是 WSL1”。把文字解析单独拆出来,方便测试各种系统版本字符串。

数据流:输入是一段系统版本文本。它先转成小写,然后查找 wsl 后面是否跟着数字;如果找到 wsl1,就返回 true。如果没看到明确数字,它再用老规则判断:包含 microsoft 但不包含 microsoft-standard,也认为像 WSL1。最终输出 true 或 false。

调用关系:它是 is_wsl1 背后的文字判读工具。is_wsl1 负责读文件,它负责看内容像不像 WSL1;这样测试时不必真的改系统文件,只要喂不同字符串就能验证判断。

is_user_namespace_failure161–166 ↗
fn is_user_namespace_failure(output: &Output) -> bool

作用:这个函数判断 bwrap 的失败是不是因为“不能创建用户命名空间”。它把一堆底层错误文字翻译成一个简单的是或否。

数据流:输入是一次进程运行的 Output,里面主要用到标准错误 stderr。它把 stderr 按 UTF-8 文本来读,遇到不标准的字节也尽量转成可读文字,然后检查是否包含几条已知的失败提示。只要命中其中一条,就返回 true;否则返回 false。

调用关系:它被 system_bwrap_has_user_namespace_access 在试跑 bwrap 后调用。前者负责拿到错误输出,后者负责判断这些文字是不是用户命名空间权限问题,从而决定要不要向用户发出权限警告。

调用图:被 1 处调用(system_bwrap_has_user_namespace_access);外部调用 1 个(from_utf8_lossy)。

find_system_bwrap_in_path168–172 ↗
fn find_system_bwrap_in_path() -> Option<PathBuf>

作用:这个函数在系统 PATH 里寻找真正可用的 bwrap 程序。PATH 可以理解成操作系统找命令时会依次查看的一串目录。

数据流:它不接收显式输入,而是读取环境变量 PATH,并获取当前工作目录。拿到这些后,它把 PATH 拆成多个目录,再交给 find_system_bwrap_in_search_paths 去查找。找到了就返回 bwrap 的完整路径;PATH 不存在、当前目录取不到或没找到时,返回 None。

调用关系:它被 system_bwrap_warning 调用,位于“确定需要检查沙箱”之后、“判断具体警告”之前。它把环境读取和路径拆分做好,再把实际搜索规则交给 find_system_bwrap_in_search_paths。

调用图:调用 1 个内部函数(find_system_bwrap_in_search_paths);被 1 处调用(system_bwrap_warning);外部调用 3 个(current_dir, split_paths, var_os)。

find_system_bwrap_in_search_paths174–191 ↗
fn find_system_bwrap_in_search_paths(
    search_paths: impl IntoIterator<Item = PathBuf>,
    cwd: &Path,
) -> Option<PathBuf>

作用:这个函数按给定搜索目录查找 bwrap,并刻意避开当前项目目录里的同名程序。这样做是为了尽量找到系统安装的 bubblewrap,而不是误用项目文件夹里可能被放进去的假 bwrap。

数据流:输入是一组搜索路径和当前工作目录。它先把搜索路径合成系统可用的形式,再把当前目录转成规范路径。然后它查找所有名叫 bwrap 的候选程序,对每个候选路径做规范化;如果当前目录不是根目录,并且候选程序位于当前目录里面,就跳过。最后返回第一个不在当前目录里的 bwrap 路径;如果没有合适的,就返回 None。

调用关系:它由 find_system_bwrap_in_path 调用,是实际执行搜索和过滤规则的地方。find_system_bwrap_in_path 负责从环境里取材料,它负责按安全一点的规则挑出真正的系统 bwrap。

调用图:被 1 处调用(find_system_bwrap_in_path);外部调用 4 个(parent, join_paths, canonicalize, which_in_all)。

sandboxing/src/landlock.rs源码 ↗
orchestration命令进入 Linux 沙箱前

这个文件处理 Linux 下运行命令前的“沙箱说明书”。沙箱可以理解成给程序划一个活动范围:哪些目录能碰、网络能不能用、当前工作目录在哪里。这里不会亲自做隔离,而是拼出一串命令行参数,交给 codex-linux-sandbox 去执行真正的限制。文件里还区分了两种网络情况:如果系统要求“受管理的网络”,就只允许通过代理走网络;否则保持原来的行为。最重要的函数会把 PermissionProfile(权限配置,说明能访问什么)转成 JSON 字符串,再和工作目录、兼容旧 Landlock 的开关、代理网络开关一起排成参数列表。这里特别注意用 -- 把沙箱自己的参数和真正要执行的命令隔开,就像在表格中间画一条分界线,避免用户命令里以短横线开头的内容被误当成沙箱选项。

函数细节3
allow_network_for_proxy8–13 ↗
fn allow_network_for_proxy(enforce_managed_network: bool) -> bool

作用:这个函数决定要不要给沙箱开启“只允许代理用网络”的模式。它把外部传进来的“是否强制受管理网络”要求,直接变成沙箱启动时使用的网络开关。

数据流:进去的是一个布尔值,表示当前是否启用了受管理网络要求。函数不读取别的信息,也不做复杂判断;如果要求启用,它就返回 true,表示要让代理可以用网络;如果没有要求,它就返回 false,表示沿用原来的无特殊网络放行行为。

调用关系:它是在准备运行沙箱命令时被用到的判断小零件。run_command_under_sandboxspawn_command_under_linux_sandboxtransform 会先问它网络开关该怎么设,然后再把结果交给后续拼沙箱参数的流程。

调用图:被 3 处调用(run_command_under_sandbox, spawn_command_under_linux_sandbox, transform)。

create_linux_sandbox_command_args_for_permission_profile23–60 ↗
fn create_linux_sandbox_command_args_for_permission_profile(
    command: Vec<String>,
    command_cwd: &Path,
    permission_profile: &PermissionProfile,
    sandbox_policy_cwd: &Path,
    use_legacy

作用:这个函数把一份权限配置和要执行的命令,拼成 codex-linux-sandbox 能看懂的一整组命令行参数。别人会用它来确保沙箱小帮手收到完整、顺序正确的说明。

数据流:进去的是原始命令、命令要在哪个目录运行、权限配置、沙箱策略要以哪个目录为基准、是否使用旧版 Landlock、是否允许代理网络。函数先把权限配置转成 JSON 文本,把两个目录路径转成字符串;如果路径不是合法 UTF-8,或者权限配置没法转成 JSON,它会直接报错停止。然后它按固定顺序放入沙箱策略目录、命令目录、权限配置,再根据开关追加旧版 Landlock 或代理网络参数,最后加上 -- 分隔符并接上真正要运行的命令。出来的是一个字符串数组,也就是可以传给沙箱小帮手的参数列表。

调用关系:这是当前带权限配置的主要拼装入口。run_command_under_sandboxspawn_command_under_linux_sandboxtransform 在准备把命令送进 Linux 沙箱时会调用它。它内部只借助标准的路径转字符串、对象转字符串和创建数组这些基础动作,核心职责是把上游给的安全意图排成下游沙箱程序能理解的顺序。

调用图:被 3 处调用(run_command_under_sandbox, spawn_command_under_linux_sandbox, transform);外部调用 3 个(to_str, to_string, vec!)。

create_linux_sandbox_command_args65–103 ↗
fn create_linux_sandbox_command_args(
    command: Vec<String>,
    command_cwd: &Path,
    sandbox_policy_cwd: &Path,
    use_legacy_landlock: bool,
    allow_network_for_proxy: bool,
) -> Vec<String

作用:这个函数是一个较简单的参数拼装器:它不接收完整的权限配置,只根据工作目录、旧版 Landlock 开关、代理网络开关和原始命令来生成沙箱参数。它主要用于保留旧式调用形状或测试这种较基础的拼装规则。

数据流:进去的是原始命令、命令工作目录、沙箱策略目录、是否使用旧版 Landlock、是否允许代理网络。函数先把两个目录路径转成字符串;如果路径无法安全表示成 UTF-8 文本,就会报错停止。接着它放入目录参数,根据条件追加 --use-legacy-landlock--allow-network-for-proxy,再放入 -- 作为分隔,最后接上真正要执行的命令。出来的是一组字符串形式的命令行参数。

调用关系:它和带 PermissionProfile 的拼装函数像是同一类工具的简化版。调用图里没有显示其他项目代码直接调用它;代码上也标明在非测试场景允许它暂时不用。它内部只调用标准的路径转字符串和创建数组等基础能力,用来验证或保留不带权限 JSON 的参数格式。

调用图:外部调用 2 个(to_str, vec!)。

Linux 沙盒执行

这些文件实现 Linux 端沙盒机制和启动路径,从命令封装、bubblewrap 与 Landlock 强制执行,到最终可执行文件分发。

core/src/landlock.rs源码 ↗
orchestrationrequest handling

这个文件解决的是“让外部命令安全运行”的问题。项目有时需要启动 shell 工具或子进程,但这些程序可能会访问不该访问的文件,或者偷偷连网。这里的做法不是直接运行命令,而是先调用一个叫 codex-linux-sandbox 的辅助程序。这个辅助程序会根据权限配置,把文件系统访问和网络访问限制好。文件里的核心函数会先读取权限档案,整理出沙箱需要的启动参数,再决定网络该不该受限,以及标准输入输出该怎么接。还有一个细节很重要:它会处理程序启动时的 argv0,也就是进程看到的“自己叫什么名字”。有些沙箱入口靠这个名字来分流,所以这里会确保名字正确,避免明明想进沙箱却走错入口。最后,它把整理好的请求交给通用的异步启动函数去真正创建子进程。

函数细节1
spawn_command_under_linux_sandbox22–70 ↗
async fn spawn_command_under_linux_sandbox(
    codex_linux_sandbox_exe: P,
    command: Vec<String>,
    command_cwd: AbsolutePathBuf,
    permission_profile: &PermissionProfile,
    sandbox_policy_c

作用:这个函数负责在 Linux 沙箱中启动一个命令。别人想运行外部工具、但又不想让它自由访问系统资源时,就会用它。

数据流:输入是一串要执行的命令、工作目录、权限配置、沙箱辅助程序路径、是否使用旧版 Landlock、输入输出策略、可选网络代理和环境变量。它先从权限配置里取出网络限制规则,再把命令和权限转换成 codex-linux-sandbox 能看懂的参数;接着检查沙箱程序的文件名,必要时强制设置 argv0,保证启动后会进入正确的 Linux 沙箱入口。最后它把程序路径、参数、目录、网络策略、输入输出策略和环境变量打包成 SpawnChildRequest,交给 spawn_child_async 异步启动,返回一个正在运行的子进程,或者返回启动失败的系统错误。

调用关系:它处在“准备安全运行命令”和“真正启动子进程”之间。它自己不直接实现沙箱规则,而是调用 permission_profile.network_sandbox_policy 取得网络策略,调用 allow_network_for_proxy 和 create_linux_sandbox_command_args_for_permission_profile 生成沙箱启动参数,最后把所有准备好的信息交给 spawn_child_async 去创建进程。也就是说,它像调度员:先把安全要求翻译成启动命令,再把任务交给真正负责启动进程的底层函数。

调用图:调用 5 个内部函数(spawn_child_async, network_sandbox_policy, allow_network_for_proxy, create_linux_sandbox_command_args_for_permission_profile, as_path);外部调用 4 个(as_ref, file_name, to_path_buf, to_string_lossy)。

linux-sandbox/src/landlock.rs源码 ↗
domain_logicsandbox setup,子进程启动前

这个文件解决的是:运行外部命令时,不能让它想连网就连网、想提权就提权。可以把它想成给孩子出门前戴上门禁卡:哪些门能开、哪些门不能开,先在当前线程里设好,之后子进程继承这些限制。现在文件系统隔离主要交给 bubblewrap 做,这里的 Landlock(一种 Linux 文件访问限制机制)只是保留的老方案或备用方案。核心入口会先把用户的权限配置翻译成运行时规则,再判断要不要装网络 seccomp。若需要限制,就先打开 no_new_privs(禁止获得新特权),再安装过滤器。过滤器默认放行普通系统调用,但会把联网、调试别人进程、某些高风险 I/O 能力挡掉。文件里还带了几条测试,专门确认“全网络”“受限网络”“代理转发网络”这些情况不会选错限制模式。

函数细节12
apply_permission_profile_to_current_thread42–88 ↗
fn apply_permission_profile_to_current_thread(
    permission_profile: &PermissionProfile,
    cwd: &Path,
    apply_landlock_fs: bool,
    allow_network_for_proxy: bool,
    proxy_routed_network: boo

作用:这是本文件的总开关:根据一份权限配置,把当前线程变成受限制状态,让之后启动的子进程继承这些限制。它避免把整个 CLI 主进程都锁住,只锁即将运行命令的那条执行线。

数据流:进去的是权限配置、当前工作目录、是否启用旧版 Landlock 文件限制、是否为了代理允许网络、网络是否走代理转发。它先把权限配置拆成文件系统规则和网络规则,再算出网络 seccomp 该用哪种模式。需要限制时,它会先打开 no_new_privs;如果网络要管,就安装网络过滤器;如果还要求旧版文件系统限制,就检查是否支持,并把可写目录整理出来交给 Landlock。出来的是成功或错误;成功后,当前线程已经带上这些限制。

调用关系:run_main 在准备运行沙箱里的命令时会调用它。它自己不直接写所有规则,而是把判断交给 network_seccomp_mode,把禁止提权交给 set_no_new_privs,把网络过滤交给 install_network_seccomp_filter_on_current_thread,把旧版文件限制交给 install_filesystem_landlock_rules_on_current_thread。

调用图:调用 5 个内部函数(install_filesystem_landlock_rules_on_current_thread, install_network_seccomp_filter_on_current_thread, network_seccomp_mode, set_no_new_privs, to_runtime_permissions);被 1 处调用(run_main);外部调用 1 个(UnsupportedOperation)。

should_install_network_seccomp96–103 ↗
fn should_install_network_seccomp(
    network_sandbox_policy: NetworkSandboxPolicy,
    allow_network_for_proxy: bool,
) -> bool

作用:这个函数只回答一个问题:这次到底要不要安装网络 seccomp 过滤器。它把普通“全网络放行”和“由代理管理的网络必须收紧”这两种情况区分开。

数据流:进去的是网络策略和一个布尔值:是否为了代理而允许网络。它读取网络策略是否启用网络;如果网络本来不是全放开,或者虽然放开但实际要走受控代理,就返回 true。否则返回 false,表示不用装网络过滤器。

调用关系:network_seccomp_mode 会先问它是否需要装过滤器。它是一个很小的判断零件,避免后面的模式选择函数里塞太多条件。

调用图:调用 1 个内部函数(is_enabled);被 1 处调用(network_seccomp_mode)。

network_seccomp_mode105–117 ↗
fn network_seccomp_mode(
    network_sandbox_policy: NetworkSandboxPolicy,
    allow_network_for_proxy: bool,
    proxy_routed_network: bool,
) -> Option<NetworkSeccompMode>

作用:这个函数决定网络过滤器的具体模式:完全受限,还是允许走代理桥的受控网络。它相当于给网络门禁选择“关门”还是“只开代理专用门”。

数据流:进去的是网络策略、是否为了代理允许网络、是否启用代理转发。它先调用 should_install_network_seccomp 判断要不要过滤;如果不用,就返回 None。需要过滤时,如果是代理转发网络,就返回 ProxyRouted;否则返回 Restricted。

调用关系:apply_permission_profile_to_current_thread 在真正安装过滤器前会调用它。它不安装任何东西,只负责做决策,然后把结果交给 install_network_seccomp_filter_on_current_thread 使用。

调用图:调用 1 个内部函数(should_install_network_seccomp);被 1 处调用(apply_permission_profile_to_current_thread)。

set_no_new_privs120–126 ↗
fn set_no_new_privs() -> Result<()>

作用:这个函数打开 Linux 的 no_new_privs 开关,也就是禁止当前线程和后续子进程通过 setuid 等方式获得新特权。seccomp 也需要这个安全前提才能可靠启用。

数据流:进去没有业务数据,只是调用 Linux 的 prctl 系统接口设置 PR_SET_NO_NEW_PRIVS。系统调用成功就返回成功;失败时,它读取操作系统最后的错误并转成项目里的错误返回。它会改变当前线程之后的权限行为。

调用关系:apply_permission_profile_to_current_thread 在需要 seccomp 或旧版 Landlock 文件限制时调用它。它直接和操作系统打交道,是安装限制前的安全垫。

调用图:被 1 处调用(apply_permission_profile_to_current_thread);外部调用 2 个(last_os_error, prctl)。

install_filesystem_landlock_rules_on_current_thread137–163 ↗
fn install_filesystem_landlock_rules_on_current_thread(
    writable_roots: Vec<AbsolutePathBuf>,
) -> Result<()>

作用:这个函数安装旧版 Landlock 文件系统规则:默认允许读整个文件系统,但只允许写指定目录和 /dev/null。现在主流程多用 bubblewrap 做文件隔离,所以它更像备用工具。

数据流:进去的是一组绝对路径,表示允许写入的目录。它创建 Landlock 规则集,声明要管读写权限;先加一条“全盘可读”的规则,再加“/dev/null 可写”和“这些目录可写”的规则,然后把规则限制到当前线程。如果内核没有真正执行这些规则,就返回沙箱错误;成功后,当前线程的文件写入范围被收窄。

调用关系:apply_permission_profile_to_current_thread 只有在明确要求使用旧版 Landlock 文件系统管控、并且文件策略不是全盘可写时才会调用它。它依赖 landlock 库生成路径规则,并在最后让操作系统执行限制。

调用图:被 1 处调用(apply_permission_profile_to_current_thread);外部调用 5 个(from_all, from_read, default, path_beneath_rules, Sandbox)。

install_network_seccomp_filter_on_current_thread169–268 ↗
fn install_network_seccomp_filter_on_current_thread(
    mode: NetworkSeccompMode,
) -> std::result::Result<(), SandboxErr>

作用:这个函数真正把网络 seccomp 过滤器装到当前线程上。它用“默认允许,命中黑名单就返回 EPERM 权限错误”的方式,挡住不该发生的网络和高风险系统调用。

数据流:进去的是网络过滤模式:Restricted 或 ProxyRouted。它先建立一张系统调用黑名单,固定禁止 ptrace、跨进程读写内存、io_uring 等能力。Restricted 模式下,它禁止连接、监听、收发等网络调用,只允许本机进程间常用的 AF_UNIX socket;ProxyRouted 模式下,它允许 IPv4/IPv6 socket 去访问隔离命名空间里的代理桥,但仍挡住其他 socket 类型。最后它按当前 CPU 架构生成 BPF 过滤程序并应用到当前线程。成功后,违规系统调用会收到 EPERM。

调用关系:apply_permission_profile_to_current_thread 在 network_seccomp_mode 给出模式后调用它。它内部构造 seccompiler 规则,最后交给 apply_filter 让 Linux 内核开始执行这些规则。

调用图:被 1 处调用(apply_permission_profile_to_current_thread);外部调用 8 个(new, Errno, new, new, cfg!, apply_filter, unimplemented!, vec!)。

tests::managed_network_enforces_seccomp_even_for_full_network_policy279–287 ↗
fn managed_network_enforces_seccomp_even_for_full_network_policy()

作用:这个测试确认:即使策略看起来允许完整网络,只要网络是由受控代理管理的,也仍然要安装 seccomp。这样可以防止程序绕过代理直接联网。

数据流:进去的是一个“网络启用”的策略和 allow_network_for_proxy 为 true 的情况。测试调用判断函数,期望结果是 true。出来没有业务结果;如果结果不对,测试失败。

调用关系:测试运行器会执行它。它验证 should_install_network_seccomp 在托管网络场景下的关键安全选择。

调用图:外部调用 1 个(assert_eq!)。

tests::full_network_policy_without_managed_network_skips_seccomp290–298 ↗
fn full_network_policy_without_managed_network_skips_seccomp()

作用:这个测试确认:如果策略明确允许完整网络,而且没有托管代理介入,就不需要安装网络 seccomp。也就是说,正常的全网络模式不会被多余地拦住。

数据流:进去的是“网络启用”策略和 allow_network_for_proxy 为 false。测试调用 should_install_network_seccomp,期望返回 false。若返回 true,说明系统会误装过滤器,测试就失败。

调用关系:测试运行器会执行它。它保护的是 should_install_network_seccomp 对普通全网络场景的判断。

调用图:外部调用 1 个(assert_eq!)。

tests::restricted_network_policy_always_installs_seccomp301–310 ↗
fn restricted_network_policy_always_installs_seccomp()

作用:这个测试确认:只要网络策略是受限的,不管有没有代理相关许可,都必须安装 seccomp。这样受限网络不会因为某个开关被意外放开。

数据流:进去的是“受限网络”策略,分别搭配 allow_network_for_proxy 为 false 和 true。测试两次调用 should_install_network_seccomp,并要求两次都为 true。结果不符就表示安全判断出错。

调用关系:测试运行器会执行它。它覆盖 should_install_network_seccomp 对受限网络的基本安全保证。

调用图:外部调用 1 个(assert!)。

tests::managed_proxy_routes_use_proxy_routed_seccomp_mode313–322 ↗
fn managed_proxy_routes_use_proxy_routed_seccomp_mode()

作用:这个测试确认:当网络走托管代理转发时,模式应选 ProxyRouted,而不是完全禁止网络。这样程序可以连到代理桥,但不能随便绕路。

数据流:进去的是“网络启用”策略、allow_network_for_proxy 为 true、proxy_routed_network 为 true。测试调用 network_seccomp_mode,期望得到 Some(ProxyRouted)。如果不是这个结果,说明代理转发场景的门禁模式选错了。

调用关系:测试运行器会执行它。它验证 network_seccomp_mode 会把托管代理场景交给正确的过滤模式。

调用图:外部调用 1 个(assert_eq!)。

tests::restricted_network_without_proxy_routing_uses_restricted_mode325–334 ↗
fn restricted_network_without_proxy_routing_uses_restricted_mode()

作用:这个测试确认:受限网络且没有代理转发时,应使用 Restricted 模式。也就是大多数直接联网动作都应该被拦住。

数据流:进去的是“受限网络”策略、allow_network_for_proxy 为 false、proxy_routed_network 为 false。测试调用 network_seccomp_mode,期望得到 Some(Restricted)。结果不对就说明限制模式选择有误。

调用关系:测试运行器会执行它。它保护 network_seccomp_mode 对普通受限网络场景的判断。

调用图:外部调用 1 个(assert_eq!)。

tests::full_network_without_managed_proxy_skips_network_seccomp_mode337–346 ↗
fn full_network_without_managed_proxy_skips_network_seccomp_mode()

作用:这个测试确认:完整网络且没有托管代理时,network_seccomp_mode 应该返回 None,表示不安装网络过滤器。这样全网络权限不会被无意缩水。

数据流:进去的是“网络启用”策略、allow_network_for_proxy 为 false、proxy_routed_network 为 false。测试调用 network_seccomp_mode,期望得到 None。若返回某个模式,就说明系统会错误地加限制。

调用关系:测试运行器会执行它。它验证 network_seccomp_mode 在最宽松网络配置下会选择跳过 seccomp。

调用图:外部调用 1 个(assert_eq!)。

linux-sandbox/src/bwrap.rs源码 ↗
domain_logiccommand launch / sandbox setup

可以把这个文件理解成“进门前布置房间”的清单生成器。bubblewrap 是 Linux 上用来搭一个临时文件系统视图的工具:外面的真实硬盘不变,但进到沙箱里的程序只能看到被安排好的那一套目录。这个文件根据文件系统策略,决定哪些路径只读、哪些路径可写、哪些路径完全看不见,还处理符号链接(像快捷方式一样指向别处的路径)、不存在的敏感路径、通配符屏蔽规则,以及是否隔离网络。它不会直接运行目标程序,而是生成一串 bubblewrap 参数和一些后续清理需要记住的临时目标。这样做能避免命令误改项目元数据、偷看被禁止的文件,或者通过链接绕过限制。

函数细节85
BwrapOptions::default76–82 ↗
fn default() -> Self

作用:给 bubblewrap 沙箱提供一套默认选项。默认会挂载新的 /proc,网络不隔离,通配符扫描不限制深度。

数据流:没有输入 → 填好 mount_proc、network_mode、glob_scan_max_depth 三个默认值 → 返回一个可直接使用的 BwrapOptions。

调用关系:调用方如果不想手动指定细节,就用它拿默认配置;后面 create_bwrap_command_args 会按这些选项决定是否隔离网络、是否挂载 /proc

调用图:被 2 处调用(full_disk_write_with_unreadable_glob_still_wraps_and_masks_match, restricted_policy_chdirs_to_canonical_command_cwd)。

BwrapNetworkMode::should_unshare_network101–103 ↗
fn should_unshare_network(self) -> bool

作用:判断当前网络模式是否需要切到一个新的网络空间。网络空间可以理解成给进程单独准备一套网络环境。

数据流:输入一个网络模式 → 判断它是不是 FullAccess → 输出 true 或 false,表示要不要加 --unshare-net

调用关系:create_bwrap_flags 和 create_bwrap_flags_full_filesystem 会用它决定 bubblewrap 参数里是否加入网络隔离。

调用图:外部调用 1 个(matches!)。

FileIdentity::from_metadata121–126 ↗
fn from_metadata(metadata: &Metadata) -> Self

作用:从文件的系统信息里提取“身份号码”。这里用设备号和 inode 号判断是不是同一个真实文件。

数据流:输入文件元数据 Metadata → 读取 dev 和 ino → 返回 FileIdentity。

调用关系:SyntheticMountTarget 用它记录原本就存在的空文件或空目录,后续清理时靠它避免误删用户本来就有的东西。

调用图:被 3 处调用(existing_empty_directory, existing_empty_file, should_remove_after_bwrap);外部调用 2 个(dev, ino)。

ProtectedCreateTarget::missing150–154 ↗
fn missing(path: &Path) -> Self

作用:记录一个本来不存在、但不希望沙箱程序偷偷创建出来的敏感路径。

数据流:输入一个路径 → 复制成 PathBuf 保存 → 返回 ProtectedCreateTarget。

调用关系:append_protected_create_targets_for_writable_root 在发现某些敏感元数据路径缺失时会创建这个记录,后续清理逻辑会检查有没有被违规创建。

调用图:被 3 处调用(append_protected_create_targets_for_writable_root, cleanup_protected_create_targets_removes_created_path_and_reports_violation, cleanup_protected_create_targets_waits_for_other_active_registrations);外部调用 1 个(to_path_buf)。

ProtectedCreateTarget::path156–158 ↗
fn path(&self) -> &Path

作用:取出受保护创建目标里保存的路径。

数据流:输入自身对象 → 读取内部 path 字段 → 输出路径引用,不改动任何东西。

调用关系:清理和检查代码会调用它,知道到底要检查哪个路径是否被沙箱程序创建了。

调用图:被 1 处调用(try_remove_protected_create_target)。

SyntheticMountTarget::missing162–168 ↗
fn missing(path: &Path) -> Self

作用:记录一个为了挡住缺失路径而临时伪造的空文件挂载目标。

数据流:输入路径 → 保存路径、标记为空文件、标记没有预先存在的真实文件 → 返回 SyntheticMountTarget。

调用关系:append_missing_empty_file_bind_data_args 会用它登记临时目标,沙箱结束后清理代码再按这个记录判断能不能删。

调用图:被 4 处调用(append_missing_empty_file_bind_data_args, cleanup_synthetic_mount_targets_removes_only_empty_mount_targets, cleanup_synthetic_mount_targets_removes_transient_file_after_concurrent_owner_exits, cleanup_synthetic_mount_targets_waits_for_other_active_registrations);外部调用 1 个(to_path_buf)。

SyntheticMountTarget::missing_empty_directory170–176 ↗
fn missing_empty_directory(path: &Path) -> Self

作用:记录一个为了保护缺失的敏感目录而临时伪造的空目录挂载目标。

数据流:输入路径 → 保存路径、标记为空目录、标记没有预先存在的真实目录 → 返回 SyntheticMountTarget。

调用关系:append_missing_read_only_subpath_args 在遇到 .git.agents.codex 这类缺失元数据目录时会用它。

调用图:被 2 处调用(append_missing_read_only_subpath_args, cleanup_synthetic_mount_targets_removes_only_empty_mount_targets);外部调用 1 个(to_path_buf)。

SyntheticMountTarget::existing_empty_file178–184 ↗
fn existing_empty_file(path: &Path, metadata: &Metadata) -> Self

作用:记录一个已经存在的空文件,它会被临时当作保护用的挂载目标,但清理时不能误删。

数据流:输入路径和元数据 → 复制路径,并从元数据提取原文件身份 → 返回带有原始身份的 SyntheticMountTarget。

调用关系:append_existing_empty_file_bind_data_args 用它处理并发沙箱留下的空文件,后续 should_remove_after_bwrap 会用身份信息判断是否该删。

调用图:调用 1 个内部函数(from_metadata);被 3 处调用(append_existing_empty_file_bind_data_args, cleanup_synthetic_mount_targets_preserves_real_pre_existing_empty_file, cleanup_synthetic_mount_targets_removes_transient_file_after_concurrent_owner_exits);外部调用 1 个(to_path_buf)。

SyntheticMountTarget::existing_empty_directory186–192 ↗
fn existing_empty_directory(path: &Path, metadata: &Metadata) -> Self

作用:记录一个已经存在的空目录,它会被临时当作保护用目录,但要记住它是不是用户原本的目录。

数据流:输入路径和元数据 → 保存路径、标记为空目录、保存原目录身份 → 返回 SyntheticMountTarget。

调用关系:append_existing_empty_directory_args 会调用它,给清理阶段留下安全判断依据。

调用图:调用 1 个内部函数(from_metadata);被 1 处调用(append_existing_empty_directory_args);外部调用 1 个(to_path_buf)。

SyntheticMountTarget::preserves_pre_existing_path194–196 ↗
fn preserves_pre_existing_path(&self) -> bool

作用:告诉外部:这个临时目标是否对应一个原本就存在的路径。

数据流:输入自身对象 → 查看 pre_existing_path 是否有值 → 输出布尔值。

调用关系:生成临时挂载标记内容时会用它,区分“沙箱临时造的”和“用户本来就有的”。

调用图:被 1 处调用(synthetic_mount_marker_contents)。

SyntheticMountTarget::path198–200 ↗
fn path(&self) -> &Path

作用:取出临时挂载目标对应的路径。

数据流:输入自身对象 → 读取内部 path → 输出路径引用。

调用关系:清理临时挂载目标时会调用它,知道要检查和删除哪个地方。

调用图:被 1 处调用(remove_synthetic_mount_target)。

SyntheticMountTarget::kind202–204 ↗
fn kind(&self) -> SyntheticMountTargetKind

作用:说明这个临时目标是空文件还是空目录。

数据流:输入自身对象 → 读取 kind 字段 → 输出 EmptyFile 或 EmptyDirectory。

调用关系:remove_synthetic_mount_target 会根据这个类型选择文件或目录的清理方式。

调用图:被 1 处调用(remove_synthetic_mount_target)。

SyntheticMountTarget::should_remove_after_bwrap206–224 ↗
fn should_remove_after_bwrap(&self, metadata: &Metadata) -> bool

作用:判断沙箱结束后某个临时目标是否可以安全删除。

数据流:输入当前磁盘上的元数据 → 先确认它还是预期的空文件或目录 → 再和原先记录的文件身份比较 → 输出是否应该删除。

调用关系:清理代码调用它,避免把用户原本的空 .git 文件或其他真实路径误删。

调用图:调用 1 个内部函数(from_metadata);被 1 处调用(remove_synthetic_mount_target);外部调用 2 个(file_type, len)。

create_bwrap_command_args234–265 ↗
fn create_bwrap_command_args(
    command: Vec<String>,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    sandbox_policy_cwd: &Path,
    command_cwd: &Path,
    options: BwrapOptions,
) ->

作用:这是本文件的主要入口:把原始命令和沙箱策略变成最终要传给 bubblewrap 的参数。

数据流:输入命令、文件系统策略、工作目录和选项 → 判断是否真的需要沙箱,必要时展开不可读通配符 → 输出 BwrapArgs,里面有参数、保留文件和清理目标。

调用关系:上层 build_bwrap_argv 会调用它;它会根据情况直接返回原命令、调用 create_bwrap_flags_full_filesystem,或进入完整的 create_bwrap_flags。

调用图:调用 4 个内部函数(create_bwrap_flags, create_bwrap_flags_full_filesystem, get_unreadable_globs_with_cwd, has_full_disk_write_access);被 5 处调用(full_disk_write_full_network_returns_unwrapped_command, full_disk_write_proxy_only_keeps_full_filesystem_but_unshares_network, full_disk_write_with_unreadable_glob_still_wraps_and_masks_match, restricted_policy_chdirs_to_canonical_command_cwd, build_bwrap_argv);外部调用 1 个(new)。

create_bwrap_flags_full_filesystem267–294 ↗
fn create_bwrap_flags_full_filesystem(command: Vec<String>, options: BwrapOptions) -> BwrapArgs

作用:在文件系统完全开放、但仍可能需要网络隔离时,生成一套最轻量的 bubblewrap 参数。

数据流:输入原始命令和选项 → 绑定整个 /,加上会话、父进程死亡退出、用户和进程空间参数,并按需加网络隔离和 /proc → 输出 BwrapArgs。

调用关系:create_bwrap_command_args 在“磁盘全开放但网络要隔离”的情况下调用它,避免不必要地重建复杂文件系统视图。

调用图:被 1 处调用(create_bwrap_command_args);外部调用 2 个(new, vec!)。

create_bwrap_flags297–349 ↗
fn create_bwrap_flags(
    command: Vec<String>,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    sandbox_policy_cwd: &Path,
    command_cwd: &Path,
    options: BwrapOptions,
) -> Result

作用:生成完整 bubblewrap 参数,包括文件系统挂载、网络隔离、进程隔离和工作目录修正。

数据流:输入命令、策略、两个工作目录和选项 → 先让 create_filesystem_args 生成文件系统部分,再补上命名空间、/proc--chdir 和原命令 → 输出 BwrapArgs。

调用关系:create_bwrap_command_args 在需要真正沙箱化时调用它;它把最复杂的文件系统细节交给 create_filesystem_args。

调用图:调用 3 个内部函数(create_filesystem_args, normalize_command_cwd_for_bwrap, path_to_string);被 1 处调用(create_bwrap_command_args);外部调用 1 个(new)。

create_filesystem_args367–630 ↗
fn create_filesystem_args(
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    cwd: &Path,
    glob_scan_max_depth: Option<usize>,
) -> Result<BwrapArgs>

作用:这是文件系统沙箱的核心:决定哪些目录怎么挂进去、哪些地方只读、哪些地方写、哪些地方彻底遮住。

数据流:输入文件系统策略、当前目录和通配符扫描深度 → 收集可写根、可读根、不可读路径和敏感元数据路径,按严格顺序拼出 bubblewrap 挂载参数 → 输出 BwrapArgs,并附带临时挂载和保护创建记录。

调用关系:create_bwrap_flags 调用它生成文件系统部分;它会调多个 append_* 小函数,像搭积木一样按正确顺序铺好只读层、可写层和遮罩层。

调用图:调用 17 个内部函数(append_metadata_path_masks_for_writable_root, append_mount_target_parent_dir_args, append_protected_create_targets_for_writable_root, append_read_only_subpath_args, append_unreadable_root_args, canonical_target_if_symlinked_path, expand_unreadable_globs_with_ripgrep, path_to_string, remap_paths_for_symlink_target, get_readable_roots_with_cwd (+7 more));被 24 处调用(create_bwrap_flags, ignores_missing_writable_roots, missing_child_git_under_parent_repo_uses_protected_create_target, missing_project_root_metadata_carveouts_use_metadata_path_masks, missing_read_only_subpath_uses_empty_file_bind_data, missing_user_project_root_subpath_rules_are_still_enforced, mounts_dev_before_writable_dev_binds, protected_symlinked_directory_subpaths_fail_closed, restricted_read_only_uses_scoped_read_roots_instead_of_erroring, restricted_read_only_with_platform_defaults_includes_usr_when_present (+14 more));外部调用 3 个(new, with_capacity, vec!)。

append_protected_create_targets_for_writable_root632–653 ↗
fn append_protected_create_targets_for_writable_root(
    bwrap_args: &mut BwrapArgs,
    protected_metadata_names: &[String],
    root: &Path,
    symlink_target: Option<&Path>,
    read_only_subpath

作用:为可写目录里缺失的敏感元数据路径登记“禁止新建”的检查点。

数据流:输入 BwrapArgs、敏感名字、可写根、符号链接目标和已只读路径 → 找出不存在且没有被只读挂载挡住的敏感路径 → 把它们加入 protected_create_targets。

调用关系:create_filesystem_args 在处理每个可写根时调用它;这些记录留给沙箱结束后的检查和清理使用。

调用图:调用 1 个内部函数(missing);被 1 处调用(create_filesystem_args);外部调用 2 个(join, iter)。

append_metadata_path_masks_for_writable_root655–670 ↗
fn append_metadata_path_masks_for_writable_root(
    read_only_subpaths: &mut Vec<PathBuf>,
    root: &Path,
    mount_root: &Path,
    protected_metadata_names: &[String],
)

作用:把 .git.agents.codex 这类项目元数据路径加入只读保护清单。

数据流:输入只读子路径列表、可写根、实际挂载根和敏感名字 → 对每个敏感名字判断是否该保护 → 把需要保护的路径追加进列表。

调用关系:create_filesystem_args 在给可写根恢复写权限后调用它,确保敏感子目录即使在可写目录里面也仍然只读。

调用图:调用 1 个内部函数(should_leave_missing_git_for_parent_repo_discovery);被 1 处调用(create_filesystem_args);外部调用 1 个(join)。

should_leave_missing_git_for_parent_repo_discovery672–683 ↗
fn should_leave_missing_git_for_parent_repo_discovery(mount_root: &Path, name: &str) -> bool

作用:判断一个缺失的 .git 是否应该先别用空目录挡住,以免影响向上查找父级 Git 仓库。

数据流:输入挂载根和名字 → 只有名字是 .git、当前 .git 不存在、上级目录存在 Git 元数据时才返回 true → 否则返回 false。

调用关系:append_metadata_path_masks_for_writable_root 用它处理一个细节:子目录没有 .git 时,不能妨碍工具发现父目录仓库。

调用图:被 1 处调用(append_metadata_path_masks_for_writable_root);外部调用 3 个(ancestors, join, matches!)。

ancestor_has_git_metadata685–698 ↗
fn ancestor_has_git_metadata(ancestor: &Path) -> bool

作用:检查某个上级目录是否真的像一个 Git 仓库。

数据流:输入目录路径 → 查看该目录下 .git 是目录还是文件,目录要有 HEAD,文件要以 gitdir: 开头 → 输出是否发现 Git 元数据。

调用关系:should_leave_missing_git_for_parent_repo_discovery 在向上查找父级仓库时会用它。

调用图:外部调用 2 个(join, read_to_string)。

expand_unreadable_globs_with_ripgrep700–744 ↗
fn expand_unreadable_globs_with_ripgrep(
    patterns: &[String],
    cwd: &Path,
    max_depth: Option<usize>,
) -> Result<Vec<AbsolutePathBuf>>

作用:把“不可读”的通配符规则变成当前磁盘上实际匹配到的具体路径。

数据流:输入通配符列表、当前目录和扫描深度 → 按搜索根分组,用 ripgrep 或备用遍历找匹配文件,并补上符号链接真实目标 → 输出绝对路径列表,匹配太多会报错。

调用关系:create_filesystem_args 调用它,因为 bubblewrap 只能遮具体路径,不能直接理解所有通配符规则。

调用图:调用 4 个内部函数(canonical_target_if_symlinked_path, ripgrep_files, split_pattern_for_ripgrep, from_absolute_path_checked);被 1 处调用(create_filesystem_args);外部调用 5 个(new, new, new, format!, Fatal)。

split_pattern_for_ripgrep746–773 ↗
fn split_pattern_for_ripgrep(pattern: &str, cwd: &Path) -> Option<(AbsolutePathBuf, String)>

作用:把一个通配符路径拆成“从哪里开始搜”和“用什么 glob 规则匹配”。glob 是类似 *.env 的通配符表达式。

数据流:输入模式字符串和当前目录 → 先转成绝对路径,找到第一个通配符,确定搜索根并修正未闭合的 [ → 输出搜索根和 glob;过宽的根级扫描会返回 None。

调用关系:expand_unreadable_globs_with_ripgrep 用它把规则分组,避免从整个 / 根目录开始扫。

调用图:调用 3 个内部函数(escape_unclosed_glob_classes, from_absolute_path_checked, resolve_path_against_base);被 2 处调用(expand_unreadable_globs_with_ripgrep, unclosed_character_classes_are_escaped_for_ripgrep);外部调用 1 个(from)。

escape_unclosed_glob_classes775–808 ↗
fn escape_unclosed_glob_classes(glob: &str) -> String

作用:把没有闭合的 [ 当作普通字符处理,避免 ripgrep 把它当成错误 glob 语法。

数据流:输入 glob 字符串 → 从头扫描字符,遇到未闭合的 [ 就加转义 → 输出修正后的字符串。

调用关系:split_pattern_for_ripgrep 在交给 ripgrep 前调用它,保持项目策略和 ripgrep 的语法差异不会导致失败。

调用图:被 1 处调用(split_pattern_for_ripgrep);外部调用 2 个(new, with_capacity)。

ripgrep_files810–875 ↗
fn ripgrep_files(
    search_root: &Path,
    globs: &[String],
    max_depth: Option<usize>,
) -> Result<Vec<AbsolutePathBuf>>

作用:调用 rg --files 搜索匹配通配符的文件。ripgrep 是一个很快的文件搜索工具。

数据流:输入搜索根、glob 列表和深度 → 运行 rg,读取以空字符分隔的输出;如果没有 rg 就转用 glob_files → 输出绝对路径列表或错误。

调用关系:expand_unreadable_globs_with_ripgrep 用它做快速扫描;只有找不到 rg 时才走内部备用实现。

调用图:调用 1 个内部函数(glob_files);被 1 处调用(expand_unreadable_globs_with_ripgrep);外部调用 5 个(from_utf8_lossy, new, new, format!, Fatal)。

glob_files877–906 ↗
fn glob_files(
    search_root: &Path,
    globs: &[String],
    max_depth: Option<usize>,
) -> Result<Vec<AbsolutePathBuf>>

作用:在系统没有 ripgrep 时,用项目自己的遍历器完成通配符匹配。

数据流:输入搜索根、glob 列表和深度 → 编译 glob 匹配器,递归扫描目录 → 输出匹配到的绝对路径。

调用关系:ripgrep_files 在 rg 不存在时调用它,保证沙箱仍能建立,不会因为缺少工具直接失效。

调用图:调用 1 个内部函数(collect_glob_files);被 1 处调用(ripgrep_files);外部调用 3 个(new, new, new)。

collect_glob_files908–936 ↗
fn collect_glob_files(
    search_root: &Path,
    dir: &Path,
    glob_set: &GlobSet,
    remaining_depth: Option<usize>,
    paths: &mut Vec<AbsolutePathBuf>,
) -> Result<()>

作用:递归走目录树,把符合 glob 的文件和符号链接收集起来。

数据流:输入搜索根、当前目录、匹配器、剩余深度和结果列表 → 读取目录项,匹配文件或链接,必要时继续进子目录 → 修改结果列表并返回成功或错误。

调用关系:glob_files 调用它完成实际扫描;它是备用通配符搜索的递归工作马。

调用图:调用 1 个内部函数(from_absolute_path_checked);被 1 处调用(glob_files);外部调用 2 个(is_match, read_dir)。

path_to_string938–940 ↗
fn path_to_string(path: &Path) -> String

作用:把路径转成普通字符串,方便放进 bubblewrap 参数里。

数据流:输入 Path → 用宽容方式转成字符串,即使有非标准字节也尽量显示 → 输出 String。

调用关系:几乎所有拼接参数的函数都会用它,避免每处都重复写路径转字符串逻辑。

调用图:被 29 处调用(append_empty_directory_args, append_empty_file_bind_data_args, append_existing_unreadable_path_args, append_mount_target_parent_dir_args, append_read_only_subpath_args, create_bwrap_flags, create_filesystem_args, assert_empty_directory_mounted_read_only, assert_empty_file_bound_without_perms, assert_file_masked (+15 more));外部调用 1 个(to_string_lossy)。

path_depth942–944 ↗
fn path_depth(path: &Path) -> usize

作用:计算路径有几层,用来决定挂载和遮罩的先后顺序。

数据流:输入路径 → 数它的组成部分个数 → 输出层数。

调用关系:create_filesystem_args 和相关挂载函数用它排序,通常先处理浅层父目录,再处理深层子目录。

调用图:外部调用 1 个(components)。

canonical_target_if_symlinked_path946–980 ↗
fn canonical_target_if_symlinked_path(path: &Path) -> Option<PathBuf>

作用:如果路径中某一段是符号链接,就找出它最终指向的真实路径。

数据流:输入路径 → 逐段检查是否遇到符号链接 → 如果遇到就 canonicalize 成真实路径,否则返回 None。

调用关系:create_filesystem_args 用它防止可写根或屏蔽路径被快捷方式绕过;expand_unreadable_globs_with_ripgrep 也用它补充真实目标。

调用图:被 2 处调用(create_filesystem_args, expand_unreadable_globs_with_ripgrep);外部调用 5 个(components, new, new, canonicalize, symlink_metadata)。

normalize_command_cwd_for_bwrap995–999 ↗
fn normalize_command_cwd_for_bwrap(command_cwd: &Path) -> PathBuf

作用:把命令的工作目录尽量转成真实路径,避免沙箱里符号链接别名消失后找不到目录。

数据流:输入命令工作目录 → 尝试 canonicalize 成真实路径,失败就保留原路径 → 输出规范化后的 PathBuf。

调用关系:create_bwrap_flags 用它决定是否需要加 --chdir,保证命令进入沙箱后相对路径仍然对得上。

调用图:被 1 处调用(create_bwrap_flags);外部调用 1 个(canonicalize)。

append_mount_target_parent_dir_args1001–1019 ↗
fn append_mount_target_parent_dir_args(args: &mut Vec<String>, mount_target: &Path, anchor: &Path)

作用:在被遮住的父目录里,提前造出子挂载点所需的父目录。

数据流:输入参数列表、要挂载的目标和锚点目录 → 找出从锚点到目标父目录之间缺的层级 → 往参数列表追加 --dir

调用关系:create_filesystem_args 和 append_existing_unreadable_path_args 会在重新开放更深层可写路径前调用它。

调用图:调用 1 个内部函数(path_to_string);被 2 处调用(append_existing_unreadable_path_args, create_filesystem_args);外部调用 2 个(is_dir, parent)。

append_read_only_subpath_args1021–1072 ↗
fn append_read_only_subpath_args(
    bwrap_args: &mut BwrapArgs,
    subpath: &Path,
    allowed_write_paths: &[PathBuf],
) -> Result<()>

作用:为可写目录里的某个子路径重新加只读保护。

数据流:输入 BwrapArgs、子路径和允许写的路径列表 → 检查是否经过可写符号链接,处理缺失路径或临时空元数据路径,存在时追加 --ro-bind → 修改 BwrapArgs 或返回错误。

调用关系:create_filesystem_args 在处理每个可写根的受保护子路径时调用它;它会把具体情况分派给多个 append_existing 或 append_missing 函数。

调用图:调用 8 个内部函数(append_existing_empty_directory_args, append_existing_empty_file_bind_data_args, append_missing_read_only_subpath_args, find_first_non_existent_component, first_writable_symlink_component_in_path, is_within_allowed_write_paths, path_to_string, transient_empty_metadata_path);被 1 处调用(create_filesystem_args);外部调用 3 个(exists, format!, Fatal)。

append_empty_file_bind_data_args1074–1083 ↗
fn append_empty_file_bind_data_args(bwrap_args: &mut BwrapArgs, path: &Path) -> Result<()>

作用:用 /dev/null 提供一个只读空文件,挂到某个路径上。

数据流:输入 BwrapArgs 和目标路径 → 必要时打开并保存 /dev/null 文件描述符,然后追加 --ro-bind-data FD 路径 → 修改参数和 preserved_files。

调用关系:多种“遮住文件”场景都会调用它,比如屏蔽不可读文件、挡住缺失路径、处理空元数据文件。

调用图:调用 1 个内部函数(path_to_string);被 3 处调用(append_existing_empty_file_bind_data_args, append_existing_unreadable_path_args, append_missing_empty_file_bind_data_args);外部调用 1 个(open)。

append_empty_directory_args1085–1092 ↗
fn append_empty_directory_args(bwrap_args: &mut BwrapArgs, path: &Path)

作用:在沙箱里给某个路径挂一个只读空目录。

数据流:输入 BwrapArgs 和路径 → 追加权限 555、tmpfs、remount-ro 等参数 → 修改参数列表。

调用关系:append_missing_read_only_subpath_args 和 append_existing_empty_directory_args 用它保护敏感目录名。

调用图:调用 1 个内部函数(path_to_string);被 2 处调用(append_existing_empty_directory_args, append_missing_read_only_subpath_args)。

append_missing_read_only_subpath_args1094–1104 ↗
fn append_missing_read_only_subpath_args(bwrap_args: &mut BwrapArgs, path: &Path) -> Result<()>

作用:处理一个应该只读、但当前并不存在的子路径。

数据流:输入 BwrapArgs 和缺失路径 → 如果文件名是受保护元数据名,就挂只读空目录并记录;否则挂只读空文件并记录 → 修改 BwrapArgs。

调用关系:append_read_only_subpath_args 在发现保护路径缺失时调用它,防止沙箱里的程序把这个路径新建出来。

调用图:调用 3 个内部函数(missing_empty_directory, append_empty_directory_args, append_missing_empty_file_bind_data_args);被 1 处调用(append_read_only_subpath_args);外部调用 1 个(file_name)。

append_missing_empty_file_bind_data_args1106–1112 ↗
fn append_missing_empty_file_bind_data_args(bwrap_args: &mut BwrapArgs, path: &Path) -> Result<()>

作用:用只读空文件挡住一个缺失路径,并登记它是临时目标。

数据流:输入 BwrapArgs 和路径 → 先追加空文件挂载参数 → 再把该路径加入 synthetic_mount_targets → 返回结果。

调用关系:append_missing_read_only_subpath_args 和 append_unreadable_root_args 会调用它,用来挡住本来不存在但不该被创建的路径。

调用图:调用 2 个内部函数(missing, append_empty_file_bind_data_args);被 2 处调用(append_missing_read_only_subpath_args, append_unreadable_root_args)。

append_existing_empty_file_bind_data_args1114–1124 ↗
fn append_existing_empty_file_bind_data_args(
    bwrap_args: &mut BwrapArgs,
    path: &Path,
    metadata: &Metadata,
) -> Result<()>

作用:处理已经存在的空文件:用空文件挂载保护它,同时记住原文件身份。

数据流:输入 BwrapArgs、路径和元数据 → 追加空文件挂载参数 → 登记 existing_empty_file 目标 → 修改 BwrapArgs。

调用关系:append_read_only_subpath_args 在发现敏感路径是临时空文件时调用它,保证后续清理不误删原文件。

调用图:调用 2 个内部函数(existing_empty_file, append_empty_file_bind_data_args);被 1 处调用(append_read_only_subpath_args)。

append_existing_empty_directory_args1126–1137 ↗
fn append_existing_empty_directory_args(
    bwrap_args: &mut BwrapArgs,
    path: &Path,
    metadata: &Metadata,
)

作用:处理已经存在的空目录:挂一个只读空目录,并记住原目录身份。

数据流:输入 BwrapArgs、路径和元数据 → 追加空目录挂载参数 → 登记 existing_empty_directory 目标 → 修改 BwrapArgs。

调用关系:append_read_only_subpath_args 在遇到空的敏感元数据目录时调用它。

调用图:调用 2 个内部函数(existing_empty_directory, append_empty_directory_args);被 1 处调用(append_read_only_subpath_args)。

append_unreadable_root_args1139–1171 ↗
fn append_unreadable_root_args(
    bwrap_args: &mut BwrapArgs,
    unreadable_root: &Path,
    allowed_write_paths: &[PathBuf],
) -> Result<()>

作用:为一个禁止读取的路径添加遮罩,让沙箱里的程序看不到或打不开它。

数据流:输入 BwrapArgs、不可读路径和可写路径列表 → 检查危险符号链接,处理不存在路径,存在时交给 append_existing_unreadable_path_args → 修改参数或返回错误。

调用关系:create_filesystem_args 在多个阶段调用它,确保不可读规则在可写绑定前后都能生效。

调用图:调用 5 个内部函数(append_existing_unreadable_path_args, append_missing_empty_file_bind_data_args, find_first_non_existent_component, first_writable_symlink_component_in_path, is_within_allowed_write_paths);被 1 处调用(create_filesystem_args);外部调用 3 个(exists, format!, Fatal)。

append_existing_unreadable_path_args1173–1215 ↗
fn append_existing_unreadable_path_args(
    bwrap_args: &mut BwrapArgs,
    unreadable_root: &Path,
    allowed_write_paths: &[PathBuf],
) -> Result<()>

作用:把已经存在的禁止读取路径真正遮住。

数据流:输入 BwrapArgs、不可读路径和可写路径列表 → 如果是目录就用 tmpfs 和权限遮住,并为需要重新开放的子路径造挂载点;如果是文件就用 000 权限的空文件遮住 → 修改参数。

调用关系:append_unreadable_root_args 在确认路径存在后调用它,是“deny read”规则落地的核心小工具。

调用图:调用 3 个内部函数(append_empty_file_bind_data_args, append_mount_target_parent_dir_args, path_to_string);被 1 处调用(append_unreadable_root_args);外部调用 2 个(is_dir, iter)。

is_within_allowed_write_paths1218–1222 ↗
fn is_within_allowed_write_paths(path: &Path, allowed_write_paths: &[PathBuf]) -> bool

作用:判断某个路径是否位于允许写入的区域里。

数据流:输入路径和可写根列表 → 检查路径是否以任一可写根开头 → 输出 true 或 false。

调用关系:只读保护、不可读遮罩和符号链接安全检查都会用它判断路径是否可能被沙箱程序修改。

调用图:被 3 处调用(append_read_only_subpath_args, append_unreadable_root_args, first_writable_symlink_component_in_path);外部调用 1 个(iter)。

transient_empty_metadata_path1229–1244 ↗
fn transient_empty_metadata_path(path: &Path) -> Option<EmptyProtectedMetadataPath>

作用:识别敏感元数据路径上是否有一个临时留下的空文件或空目录。

数据流:输入路径 → 先确认文件名是受保护元数据名,再读取元数据,判断是否空文件或空目录 → 输出对应类型或 None。

调用关系:append_read_only_subpath_args 用它区分真实内容和并发沙箱可能留下的临时空目标。

调用图:调用 1 个内部函数(directory_is_empty);被 1 处调用(append_read_only_subpath_args);外部调用 4 个(file_name, symlink_metadata, Directory, File)。

directory_is_empty1246–1251 ↗
fn directory_is_empty(path: &Path) -> bool

作用:检查一个目录里面有没有任何条目。

数据流:输入目录路径 → 尝试读取目录 → 如果第一个条目都没有就返回 true,否则 false。

调用关系:transient_empty_metadata_path 调用它判断空目录是否可以当作临时元数据目标处理。

调用图:被 1 处调用(transient_empty_metadata_path);外部调用 1 个(read_dir)。

find_first_non_existent_component1300–1325 ↗
fn find_first_non_existent_component(target_path: &Path) -> Option<PathBuf>

作用:沿着一个路径往下走,找到第一个不存在的路径段。

数据流:输入目标路径 → 从根开始逐段拼接并检查是否存在 → 返回第一个不存在的部分,全部存在则返回 None。

调用关系:只读保护和不可读遮罩在目标不存在时会调用它,把第一段缺失路径挡住,防止程序创建整条目录链。

调用图:被 2 处调用(append_read_only_subpath_args, append_unreadable_root_args);外部调用 3 个(components, new, new)。

tests::default_unreadable_glob_scan_has_no_depth_cap1343–1345 ↗
fn default_unreadable_glob_scan_has_no_depth_cap()

作用:测试默认通配符扫描没有深度上限。

数据流:读取 BwrapOptions 默认值 → 检查 glob_scan_max_depth → 断言它是 None。

调用关系:这是默认配置的回归测试,防止以后有人不小心把默认扫描改浅。

调用图:外部调用 1 个(assert_eq!)。

tests::unreadable_glob_entry1347–1352 ↗
fn unreadable_glob_entry(pattern: String) -> FileSystemSandboxEntry

作用:帮测试快速生成一条“这个通配符匹配到的文件不可读”的策略项。

数据流:输入 pattern 字符串 → 包成 GlobPattern 和 Deny 访问模式 → 返回 FileSystemSandboxEntry。

调用关系:多个通配符相关测试用它少写重复代码。

tests::default_policy_with_unreadable_glob1354–1358 ↗
fn default_policy_with_unreadable_glob(pattern: String) -> FileSystemSandboxPolicy

作用:帮测试生成一个默认策略,并额外加上一条不可读通配符规则。

数据流:输入通配符 → 创建默认策略,追加 unreadable_glob_entry → 返回策略。

调用关系:通配符展开测试用它快速搭建测试场景。

调用图:调用 1 个内部函数(default);外部调用 1 个(unreadable_glob_entry)。

tests::full_disk_write_full_network_returns_unwrapped_command1361–1377 ↗
fn full_disk_write_full_network_returns_unwrapped_command()

作用:测试磁盘和网络都完全开放时,不应该额外套 bubblewrap。

数据流:输入一条 /bin/true 命令和无限制策略 → 调 create_bwrap_command_args → 断言输出参数就是原命令。

调用关系:覆盖 create_bwrap_command_args 的快速返回分支,避免无意义的沙箱开销。

调用图:调用 2 个内部函数(create_bwrap_command_args, unrestricted);外部调用 4 个(default, new, assert_eq!, vec!)。

tests::full_disk_write_proxy_only_keeps_full_filesystem_but_unshares_network1380–1412 ↗
fn full_disk_write_proxy_only_keeps_full_filesystem_but_unshares_network()

作用:测试文件系统全开放但网络代理/隔离时,仍会使用 bubblewrap 来隔离网络。

数据流:构造无限制文件系统策略和 ProxyOnly 网络模式 → 生成参数 → 断言包含绑定 /--unshare-net

调用关系:覆盖 create_bwrap_flags_full_filesystem,说明网络隔离和文件系统权限是两件事。

调用图:调用 2 个内部函数(create_bwrap_command_args, unrestricted);外部调用 4 个(default, new, assert_eq!, vec!)。

tests::full_disk_write_with_unreadable_glob_still_wraps_and_masks_match1415–1448 ↗
fn full_disk_write_with_unreadable_glob_still_wraps_and_masks_match()

作用:测试即使有全盘写权限,只要有不可读通配符规则,也必须套沙箱遮住匹配文件。

数据流:创建临时 .env 文件和 deny glob 策略 → 调 create_bwrap_command_args → 断言输出不等于原命令且文件被遮罩。

调用关系:覆盖 create_bwrap_command_args 对 unreadable globs 的特殊处理。

调用图:调用 3 个内部函数(default, create_bwrap_command_args, restricted);外部调用 6 个(new, assert_ne!, assert_file_masked, ripgrep_available, write, vec!)。

tests::restricted_policy_chdirs_to_canonical_command_cwd1452–1526 ↗
fn restricted_policy_chdirs_to_canonical_command_cwd()

作用:测试命令工作目录经过符号链接时,沙箱会切到真实目录。

数据流:创建真实目录和链接目录 → 构造受限策略 → 生成参数 → 检查 --chdir 和挂载都使用真实路径而非链接别名。

调用关系:覆盖 normalize_command_cwd_for_bwrap 和 create_bwrap_flags 的工作目录修正逻辑。

调用图:调用 5 个内部函数(default, create_bwrap_command_args, path_to_string, restricted, from_absolute_path);外部调用 5 个(new, assert!, create_dir_all, symlink, vec!)。

tests::symlinked_writable_roots_bind_real_target_and_remap_carveouts1530–1572 ↗
fn symlinked_writable_roots_bind_real_target_and_remap_carveouts()

作用:测试可写根如果是符号链接,实际绑定和屏蔽应落到真实目标上。

数据流:创建真实目录、链接目录和被禁止子目录 → 生成文件系统参数 → 检查 bind 和 deny mask 使用真实路径。

调用关系:覆盖 canonical_target_if_symlinked_path 和 remap_paths_for_symlink_target 在可写根上的配合。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 5 个(new, assert!, create_dir_all, symlink, vec!)。

tests::writable_roots_under_symlinked_ancestors_bind_real_target1576–1619 ↗
fn writable_roots_under_symlinked_ancestors_bind_real_target()

作用:测试可写路径的上级有符号链接时,也要绑定最终真实目标。

数据流:创建类似 home/.codex -> real-codex 的结构 → 生成参数 → 断言绑定真实 memories 目录,不绑定逻辑链接路径。

调用关系:覆盖 canonical_target_if_symlinked_path 对路径中间组件是符号链接的情况。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 5 个(new, assert!, create_dir_all, symlink, vec!)。

tests::protected_symlinked_directory_subpaths_fail_closed1623–1648 ↗
fn protected_symlinked_directory_subpaths_fail_closed()

作用:测试受保护子路径如果经过可写符号链接,系统会直接失败,而不是做不可靠保护。

数据流:在可写根下创建 .agents 符号链接 → 调 create_filesystem_args → 断言返回错误并提到该链接。

调用关系:覆盖 append_read_only_subpath_args 和 first_writable_symlink_component_in_path 的安全失败分支。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 5 个(new, assert!, create_dir_all, symlink, vec!)。

tests::missing_read_only_subpath_uses_empty_file_bind_data1692–1736 ↗
fn missing_read_only_subpath_uses_empty_file_bind_data()

作用:测试缺失的只读子路径会用空文件挡住,同时默认敏感目录会被空目录保护。

数据流:创建工作区但不创建 blocked → 生成参数 → 检查 blocked 被空文件挂载,.git 等被只读空目录挂载,并记录临时目标。

调用关系:覆盖 append_missing_read_only_subpath_args 和相关 synthetic target 记录。

调用图:调用 3 个内部函数(create_filesystem_args, restricted, from_absolute_path);外部调用 7 个(new, assert!, assert_eq!, assert_empty_directory_mounted_read_only, assert_empty_file_bound_without_perms, create_dir_all, vec!)。

tests::transient_empty_preserved_file_uses_empty_file_bind_data1739–1783 ↗
fn transient_empty_preserved_file_uses_empty_file_bind_data()

作用:测试已有空 .git 文件会被当作临时保护目标处理,但不会被清理误删。

数据流:创建空 .git 文件 → 生成参数 → 检查使用空文件挂载、没有普通 ro-bind,并验证 should_remove_after_bwrap 不建议删除。

调用关系:覆盖 transient_empty_metadata_path、append_existing_empty_file_bind_data_args 和清理判断。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 9 个(create, new, assert!, assert_eq!, assert_empty_directory_mounted_read_only, assert_empty_file_bound_without_perms, create_dir_all, symlink_metadata, vec!)。

tests::missing_child_git_under_parent_repo_uses_protected_create_target1786–1825 ↗
fn missing_child_git_under_parent_repo_uses_protected_create_target()

作用:测试父目录已经是 Git 仓库时,子目录缺失的 .git 不应被空目录挡住。

数据流:创建父级 .git/HEAD 和子工作区 → 生成参数 → 检查子 .git 没被挂空目录,而是登记为禁止创建目标。

调用关系:覆盖 should_leave_missing_git_for_parent_repo_discovery 和 protected_create_targets 的配合。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 7 个(new, assert!, assert_eq!, assert_empty_directory_mounted_read_only, create_dir_all, write, vec!)。

tests::symlinked_missing_child_git_under_parent_repo_uses_effective_mount_root1829–1872 ↗
fn symlinked_missing_child_git_under_parent_repo_uses_effective_mount_root()

作用:测试经过符号链接的工作区也能正确识别父级 Git 仓库,避免误挡子 .git

数据流:创建真实仓库、链接仓库和子工作区 → 生成参数 → 检查真实路径下的 .git 被登记为 protected create,而不是 synthetic mount。

调用关系:覆盖符号链接场景下的父级 Git 发现逻辑。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 8 个(new, assert!, assert_eq!, assert_empty_directory_mounted_read_only, create_dir_all, write, symlink, vec!)。

tests::ignores_missing_writable_roots1875–1906 ↗
fn ignores_missing_writable_roots()

作用:测试不存在的可写根会被忽略,不会导致 Linux 沙箱启动失败。

数据流:创建一个存在目录和一个不存在目录 → 生成参数 → 检查只绑定存在目录,参数里没有不存在路径。

调用关系:覆盖 create_filesystem_args 过滤 missing writable roots 的行为。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, workspace_write, try_from);外部调用 3 个(new, assert!, create_dir)。

tests::missing_project_root_metadata_carveouts_use_metadata_path_masks1909–1964 ↗
fn missing_project_root_metadata_carveouts_use_metadata_path_masks()

作用:测试自动保护的项目元数据路径缺失时,会用只读空目录挡住。

数据流:构造项目根可写、.git/.agents/.codex 只读的策略 → 生成参数 → 检查三个路径都有只读空目录和 synthetic target。

调用关系:覆盖 create_filesystem_args 对自动元数据只读规则的特殊处理。

调用图:调用 3 个内部函数(create_filesystem_args, path_to_string, restricted);外部调用 7 个(new, new, assert!, assert_eq!, assert_empty_directory_mounted_read_only, synthetic_mount_target_paths, vec!)。

tests::missing_user_project_root_subpath_rules_are_still_enforced1967–2004 ↗
fn missing_user_project_root_subpath_rules_are_still_enforced()

作用:测试用户自己写的项目子路径规则,即使路径不存在,也照常执行。

数据流:构造 .vscode 只读和 .secrets 禁读规则 → 生成参数 → 检查它们都被空文件挡住。

调用关系:防止自动元数据路径的特殊逻辑误伤用户自定义规则。

调用图:调用 3 个内部函数(create_filesystem_args, path_to_string, restricted);外部调用 4 个(new, new, assert_empty_file_bound_without_perms, vec!)。

tests::mounts_dev_before_writable_dev_binds2007–2094 ↗
fn mounts_dev_before_writable_dev_binds()

作用:测试 /dev 会先作为设备目录挂入,再处理可写 /dev 绑定。

数据流:构造 /dev 可写策略 → 生成参数 → 断言参数顺序严格符合预期,并记录根和 /dev 下的敏感目录保护。

调用关系:覆盖 create_filesystem_args 中 /dev 挂载顺序这个容易出错的细节。

调用图:调用 3 个内部函数(create_filesystem_args, workspace_write, try_from);外部调用 3 个(new, assert!, assert_eq!)。

tests::restricted_read_only_uses_scoped_read_roots_instead_of_erroring2097–2125 ↗
fn restricted_read_only_uses_scoped_read_roots_instead_of_erroring()

作用:测试受限只读策略会从空根开始,只挂允许读的目录,而不是报错。

数据流:创建一个可读目录 → 生成参数 → 检查参数以 --tmpfs / 开头,并只读绑定该目录。

调用关系:覆盖 create_filesystem_args 在非全盘读策略下的基本路径。

调用图:调用 3 个内部函数(create_filesystem_args, path_to_string, restricted);外部调用 5 个(new, assert!, assert_eq!, create_dir, vec!)。

tests::restricted_read_only_with_platform_defaults_includes_usr_when_present2128–2153 ↗
fn restricted_read_only_with_platform_defaults_includes_usr_when_present()

作用:测试 minimal 读策略会加入常见系统目录,比如存在时的 /usr

数据流:构造 minimal 读策略 → 生成参数 → 如果系统有 /usr,断言它被只读绑定。

调用关系:覆盖 LINUX_PLATFORM_DEFAULT_READ_ROOTS 的平台默认读目录逻辑。

调用图:调用 2 个内部函数(create_filesystem_args, restricted);外部调用 4 个(new, new, assert!, vec!)。

tests::split_policy_reapplies_unreadable_carveouts_after_writable_binds2156–2225 ↗
fn split_policy_reapplies_unreadable_carveouts_after_writable_binds()

作用:测试可写根里的禁读子目录,会在可写绑定之后重新遮住。

数据流:创建 workspace/blocked → 生成参数 → 比较参数位置,确认 workspace 先可写绑定,blocked 后遮罩。

调用关系:覆盖 create_filesystem_args 的挂载顺序,确保 deny-read 能赢过父目录 write。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 4 个(new, assert!, create_dir_all, vec!)。

tests::split_policy_reenables_nested_writable_subpaths_after_read_only_parent2228–2281 ↗
fn split_policy_reenables_nested_writable_subpaths_after_read_only_parent()

作用:测试只读父目录下面的更深可写子目录可以重新开放。

数据流:创建 workspace/docs/public → 设置 workspace 写、docs 读、docs/public 写 → 生成参数并检查 docs 只读在前,public 可写在后。

调用关系:覆盖按路径深度排序的分层挂载逻辑。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 4 个(new, assert!, create_dir_all, vec!)。

tests::split_policy_reenables_writable_subpaths_after_unreadable_parent2284–2345 ↗
fn split_policy_reenables_writable_subpaths_after_unreadable_parent()

作用:测试不可读父目录下面明确允许写的子目录仍能被重新打开。

数据流:创建 blocked/allowed → 设置 blocked 禁读、allowed 可写 → 生成参数 → 检查先遮父目录,再造子挂载点,再绑定子目录。

调用关系:覆盖 append_existing_unreadable_path_args 和 append_mount_target_parent_dir_args 的配合。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 4 个(new, assert!, create_dir_all, vec!)。

tests::split_policy_reenables_writable_files_after_unreadable_parent2348–2426 ↗
fn split_policy_reenables_writable_files_after_unreadable_parent()

作用:测试不可读父目录下面的可写文件也能重新开放,并且不会把文件当目录创建。

数据流:创建 blocked/allowed/note.txt → 生成参数 → 检查只创建父目录挂载点,不创建文件目录,并最终绑定文件。

调用关系:覆盖挂载点父目录创建逻辑对文件目标的处理。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 5 个(new, assert!, create_dir_all, write, vec!)。

tests::split_policy_reenables_nested_writable_roots_after_unreadable_parent2429–2482 ↗
fn split_policy_reenables_nested_writable_roots_after_unreadable_parent()

作用:测试可写根内有禁读目录,但禁读目录下又有可写子根时,顺序仍然正确。

数据流:创建 workspace/blocked/allowed → 设置 workspace 写、blocked 禁读、allowed 写 → 生成参数 → 检查先遮 blocked,再造 allowed,再绑定 allowed。

调用关系:覆盖复杂嵌套规则下 create_filesystem_args 的层叠顺序。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 4 个(new, assert!, create_dir_all, vec!)。

tests::split_policy_masks_root_read_directory_carveouts2485–2525 ↗
fn split_policy_masks_root_read_directory_carveouts()

作用:测试全盘只读时,禁读目录仍会被遮住。

数据流:创建 blocked 目录 → 设置根只读和 blocked 禁读 → 生成参数 → 检查有 / 只读绑定,也有 blocked 的 tmpfs 遮罩。

调用关系:覆盖全盘读 baseline 加 deny carveout 的组合。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 4 个(new, assert!, create_dir_all, vec!)。

tests::split_policy_masks_root_read_file_carveouts2528–2562 ↗
fn split_policy_masks_root_read_file_carveouts()

作用:测试全盘只读时,禁读文件会被 000 权限的空文件遮住。

数据流:创建 blocked.txt → 设置根只读和该文件禁读 → 生成参数 → 检查 preserved_files 和 --ro-bind-data 遮罩。

调用关系:覆盖 append_existing_unreadable_path_args 对文件而不是目录的处理。

调用图:调用 4 个内部函数(create_filesystem_args, path_to_string, restricted, from_absolute_path);外部调用 5 个(new, assert!, assert_eq!, write, vec!)。

tests::unreadable_globs_expand_existing_matches_with_configured_depth2565–2595 ↗
fn unreadable_globs_expand_existing_matches_with_configured_depth()

作用:测试不可读通配符会按配置深度展开,太深的匹配不会加入参数。

数据流:创建多层 .env 文件并设置最大深度 2 → 生成参数 → 检查浅层文件被遮住,深层文件没被加入。

调用关系:覆盖 expand_unreadable_globs_with_ripgrep、ripgrep_files 的深度限制。

调用图:调用 1 个内部函数(create_filesystem_args);外部调用 8 个(new, assert!, format!, assert_file_masked, default_policy_with_unreadable_glob, ripgrep_available, create_dir_all, write)。

tests::root_prefix_unreadable_globs_are_too_broad_for_linux_expansion2621–2626 ↗
fn root_prefix_unreadable_globs_are_too_broad_for_linux_expansion()

作用:测试从根目录开始的超宽通配符不会被 Linux 启动时展开。

数据流:输入 /**/*.env → 调 split_pattern_for_ripgrep → 断言结果为 None。

调用关系:覆盖 split_pattern_for_ripgrep 避免从 / 扫全盘的保护。

调用图:外部调用 1 个(assert_eq!)。

tests::unclosed_character_classes_are_escaped_for_ripgrep2629–2635 ↗
fn unclosed_character_classes_are_escaped_for_ripgrep()

作用:测试未闭合的 [ 会被转义,避免 ripgrep 语法报错。

数据流:输入 /tmp/[*.env → 拆分模式 → 断言搜索根是 /tmp,glob 是转义后的 \[*.env

调用关系:覆盖 escape_unclosed_glob_classes 和 split_pattern_for_ripgrep 的配合。

调用图:调用 1 个内部函数(split_pattern_for_ripgrep);外部调用 2 个(new, assert_eq!)。

tests::ripgrep_available2637–2642 ↗
fn ripgrep_available() -> bool

作用:测试辅助函数:判断当前环境有没有可用的 rg 命令。

数据流:运行 rg --version → 看命令是否成功 → 返回 true 或 false。

调用关系:依赖 ripgrep 的测试会先调用它;没有 rg 时跳过相关测试,避免环境差异导致失败。

调用图:外部调用 1 个(new)。

tests::assert_file_masked2647–2658 ↗
fn assert_file_masked(args: &[String], path: &Path)

作用:测试辅助断言:确认某个文件在参数里被空文件遮住了。

数据流:输入参数列表和路径 → 把路径转字符串,查找 --perms 000 --ro-bind-data FD PATH 模式 → 找不到就让测试失败。

调用关系:多个禁读文件和通配符测试用它检查最终 bubblewrap 参数是否真的屏蔽了文件。

调用图:调用 1 个内部函数(path_to_string);外部调用 1 个(assert!)。

tests::assert_empty_file_bound_without_perms2662–2678 ↗
fn assert_empty_file_bound_without_perms(args: &[String], path: &Path)

作用:测试辅助断言:确认某路径被空文件绑定,但没有额外设置 000 权限。

数据流:输入参数和路径 → 查找 --ro-bind-data 绑定,同时确认没有 --perms 000 版本 → 不符合就测试失败。

调用关系:缺失只读路径测试用它区分“挡住创建”和“完全禁读”两种参数形态。

调用图:调用 1 个内部函数(path_to_string);外部调用 1 个(assert!)。

tests::assert_empty_directory_mounted_read_only2680–2692 ↗
fn assert_empty_directory_mounted_read_only(args: &[String], path: &Path)

作用:测试辅助断言:确认某路径被挂成只读空目录。

数据流:输入参数和路径 → 查找 --perms 555 --tmpfs PATH--remount-ro PATH → 不存在就测试失败。

调用关系:元数据目录保护相关测试用它检查 .git.agents.codex 是否被正确挡住。

调用图:调用 1 个内部函数(path_to_string);外部调用 1 个(assert!)。

tests::synthetic_mount_target_paths2694–2699 ↗
fn synthetic_mount_target_paths(args: &BwrapArgs) -> Vec<PathBuf>

作用:测试辅助函数:取出所有临时挂载目标的路径,方便比较。

数据流:输入 BwrapArgs → 遍历 synthetic_mount_targets,读取每个 target.path() → 输出 PathBuf 列表。

调用关系:多个测试用它确认哪些路径被登记为沙箱结束后可能要清理的临时目标。

tests::protected_create_target_paths2701–2706 ↗
fn protected_create_target_paths(args: &BwrapArgs) -> Vec<PathBuf>

作用:测试辅助函数:取出所有受保护创建目标的路径,方便断言。

数据流:输入 BwrapArgs → 遍历 protected_create_targets,读取每个 target.path() → 输出 PathBuf 列表。

调用关系:父级 Git 仓库相关测试用它确认缺失子 .git 被登记为禁止创建,而不是被挂空目录。

linux-sandbox/src/bundled_bwrap.rs源码 ↗
orchestrationsandbox launch

这个文件像一个“专用启动器”。它先判断当前安装包里有没有自带的 bwrap,也就是 bubblewrap 可执行文件;找不到时,还会兼容旧版目录结构,到可执行程序旁边、资源目录、开发环境位置里找。找到以后,它不会直接盲目运行,而是先检查这个文件是否真的可执行,并在配置了 SHA-256 摘要时做完整性校验。SHA-256 可以理解成文件的“指纹”,文件内容一变,指纹就变。启动时它会把需要保留的文件句柄设置成子进程也能继续使用,然后通过 Linux 的 execv 把当前进程替换成 bwrap。这里的“替换”很重要:成功后原程序不会再往下跑,而是变成沙盒程序本身。文件末尾的测试覆盖了各种安装布局、摘要校验和十六进制解析,确保打包方式变化时也不容易把沙盒启动弄坏。

函数细节19
launcher28–33 ↗
fn launcher() -> Option<BundledBwrapLauncher>

作用:创建一个可用的 bundled bubblewrap 启动器。它的作用是回答一个问题:当前机器和安装目录里,能不能找到项目自带的 bwrap。

数据流:进去的是当前运行环境的信息:当前可执行文件路径和安装上下文。它先按新的安装布局找资源目录里的 bwrap,找不到再按旧布局从程序旁边等位置找。出来的是一个带有 bwrap 绝对路径的 BundledBwrapLauncher;如果哪里都找不到,就返回空。

调用关系:这是外部代码想启动自带 bwrap 时的入口。它会询问 InstallContext::current 得到安装信息,交给 find_for_install_context 查新布局;如果失败,再根据 current_exe 得到的当前程序路径走旧版查找路线。

调用图:调用 2 个内部函数(current, find_for_install_context);外部调用 1 个(current_exe)。

BundledBwrapLauncher::exec36–69 ↗
fn exec(&self, argv: Vec<String>, preserved_files: Vec<File>) -> !

作用:真正启动自带的 bubblewrap。它会先打开并校验 bwrap 文件,再把当前进程替换成 bwrap 进程。

数据流:进去的是要传给 bwrap 的命令行参数 argv,以及需要保留下来的文件 preserved_files。它打开 bwrap 文件,按 expected_sha256 给出的预期指纹调用 verify_digest 做校验,把保留文件设成可继承,把参数转成 C 语言能读的字符串数组,最后调用 execv。成功时没有普通返回值,因为当前进程已经变成 bwrap;失败时会读取系统错误并直接报错退出。

调用关系:这是 BundledBwrapLauncher 找到程序之后的核心动作。它先依赖 expected_sha256 和 verify_digest 保证运行的是可信文件,再用 make_files_inheritable 和 argv_to_cstrings 做 execv 前的准备,最后把控制权交给操作系统的 execv。

调用图:调用 5 个内部函数(expected_sha256, verify_digest, argv_to_cstrings, make_files_inheritable, as_path);外部调用 7 个(new, open, last_os_error, format!, execv, panic!, null)。

find_for_install_context72–76 ↗
fn find_for_install_context(context: &InstallContext) -> Option<AbsolutePathBuf>

作用:按当前安装包的正式资源目录寻找 bwrap。它优先使用安装上下文提供的资源位置,避免到处猜路径。

数据流:进去的是 InstallContext,也就是程序知道的安装布局。它向上下文询问名为 bwrap 的 bundled resource,并检查这个路径是不是一个可执行文件。出来的是 bwrap 的绝对路径;如果资源不存在或不能执行,就返回空。

调用关系:launcher 会先调用它,因为这是新安装布局下最可靠的查找方式。它把具体路径查找交给 bundled_resource,并用 is_executable_file 做最后把关。

调用图:调用 1 个内部函数(bundled_resource);被 1 处调用(launcher)。

find_legacy_for_exe78–90 ↗
fn find_legacy_for_exe(exe: &Path) -> Option<AbsolutePathBuf>

作用:按旧版或开发环境的目录规则寻找 bwrap。它是兼容层,防止老安装包或特殊打包方式突然不能用了。

数据流:进去的是当前可执行程序 exe 的路径。它先让 legacy_candidates_for_exe 列出一串可能位置,再从中挑第一个真正可执行的文件,并把路径规范成 AbsolutePathBuf。出来的是可用的 bwrap 绝对路径;没有候选命中就返回空。

调用关系:launcher 在正式安装上下文找不到 bwrap 时会走到这里。它本身不猜每个细节,而是让 legacy_candidates_for_exe 生成候选列表,再用 is_executable_file 筛掉不存在或没执行权限的文件。

调用图:调用 1 个内部函数(legacy_candidates_for_exe)。

legacy_candidates_for_exe92–107 ↗
fn legacy_candidates_for_exe(exe: &Path) -> Vec<PathBuf>

作用:根据当前程序的位置,列出旧版布局里 bwrap 可能出现的路径。它像是在列“可能藏钥匙的地方”。

数据流:进去的是当前可执行程序 exe 的路径。它取出 exe 所在目录,依次拼出 codex-resources/bwrap、上一级目录里的 codex-resources/bwrap、同目录 bwrap,以及 bazel 开发环境给出的候选路径。出来的是一个候选路径列表;如果连 exe 的父目录都没有,就返回空列表。

调用关系:find_legacy_for_exe 会调用它来拿候选清单。它还会询问 bazel_bwrap::candidate,把 Bazel 构建环境下可能生成的 bwrap 也纳入查找范围。

调用图:调用 1 个内部函数(candidate);被 1 处调用(find_legacy_for_exe);外部调用 2 个(parent, new)。

is_executable_file109–114 ↗
fn is_executable_file(path: &Path) -> bool

作用:判断一个路径是不是“真的可以运行的文件”。只存在还不够,它必须是普通文件,并且带执行权限。

数据流:进去的是一个文件路径。它读取文件元数据,看它是不是文件,再看 Unix 权限位里有没有执行权限。出来是 true 或 false;如果读取元数据失败,比如文件不存在,也直接返回 false。

调用关系:查找 bwrap 的函数会用它做门卫。find_for_install_context 和 find_legacy_for_exe 都需要靠它避免把目录、坏路径或不可执行文件当成 bwrap。

调用图:外部调用 1 个(metadata)。

expected_sha256116–124 ↗
fn expected_sha256() -> Option<[u8; 32]>

作用:读取编译时写进程序里的 bwrap 预期 SHA-256 指纹。它用来判断自带的 bwrap 有没有被换掉或损坏。

数据流:进去的是编译环境变量 CODEX_BWRAP_SHA256 的值,运行时不再从用户环境重新读。它只初始化一次,把 64 位十六进制字符串解析成 32 字节指纹;如果是全 0 指纹,就当作没有启用校验。出来的是可选的指纹。

调用关系:BundledBwrapLauncher::exec 在启动前调用它。它会借助 OnceLock 做缓存,也就是第一次算好后后面直接复用,并通过 parse_sha256_hex 完成字符串到字节的转换。

调用图:被 1 处调用(exec);外部调用 1 个(new)。

verify_digest126–160 ↗
fn verify_digest(file: &File, expected: Option<[u8; 32]>, path: &Path) -> Result<(), String>

作用:校验 bwrap 文件内容是否等于预期指纹。它是启动前的安全检查,防止运行了不该运行的二进制文件。

数据流:进去的是已打开的文件、可选的预期 SHA-256 指纹,以及用于报错显示的路径。如果没有预期指纹,它直接放行;如果有,它克隆文件句柄,分块读取文件内容,计算实际 SHA-256,再和预期值比较。出来是成功或错误信息;文件内容不会被改动。

调用关系:BundledBwrapLauncher::exec 会在真正 execv 之前调用它。测试里的 digest_verification_skips_missing_expected_digest、digest_verification_accepts_matching_digest 和 digest_verification_rejects_mismatched_digest 分别检查跳过、通过和失败三种情况。它在报错时会用 bytes_to_hex 把指纹变成人能读的十六进制文本。

调用图:被 4 处调用(exec, digest_verification_accepts_matching_digest, digest_verification_rejects_mismatched_digest, digest_verification_skips_missing_expected_digest);外部调用 4 个(read, try_clone, new, format!)。

parse_sha256_hex162–177 ↗
fn parse_sha256_hex(raw: &str) -> Result<[u8; 32], String>

作用:把 64 个十六进制字符解析成 32 字节的 SHA-256 指纹。它负责把人类可读的“文件指纹文字”变成程序可比较的字节数组。

数据流:进去的是一个字符串。它先检查长度必须正好是 64,然后每两个字符解析成一个字节,依次填满 32 字节数组。出来是解析好的指纹;如果长度不对或某一段不是合法十六进制,就返回说明原因的错误。

调用关系:expected_sha256 会用它解析 CODEX_BWRAP_SHA256。测试 tests::parses_sha256_hex_digest 直接覆盖了正常值、全 0 值、长度错误和非法字符。

调用图:外部调用 2 个(format!, from_str_radix)。

bytes_to_hex179–187 ↗
fn bytes_to_hex(bytes: &[u8; 32]) -> String

作用:把 32 字节的指纹转回十六进制字符串。它主要用于把校验失败的信息打印得清楚。

数据流:进去的是 32 字节数组。它把每个字节拆成高 4 位和低 4 位,各自映射成 0 到 f 的字符。出来是 64 个字符的十六进制字符串。

调用关系:verify_digest 在发现实际指纹和预期指纹不一致时会用它生成错误消息,让人能看到 expected 和 got 分别是什么。

调用图:外部调用 1 个(with_capacity)。

tests::finds_package_layout_bwrap_from_install_context201–226 ↗
fn finds_package_layout_bwrap_from_install_context()

作用:测试新安装布局下能不能从安装上下文找到 bwrap。它保证正式打包后的资源目录规则没有被破坏。

数据流:进去的是测试临时目录。它创建一个假的 package/bin 和 codex-resources/bwrap,把 bwrap 写成可执行文件,再构造 InstallContext。出来是断言结果:find_for_install_context 应该返回这个资源目录里的 bwrap 绝对路径。

调用关系:这个测试直接验证 find_for_install_context。它使用 tests::write_executable 准备假文件,并用 from_absolute_path 和 assert_eq! 检查返回路径是否符合预期。

调用图:调用 1 个内部函数(from_absolute_path);外部调用 4 个(assert_eq!, create_dir_all, write_executable, tempdir)。

tests::finds_legacy_standalone_bundled_bwrap_next_to_exe_resources229–240 ↗
fn finds_legacy_standalone_bundled_bwrap_next_to_exe_resources()

作用:测试旧版独立安装形式:程序旁边的 codex-resources/bwrap 能被找到。它防止升级查找逻辑时忘记兼容老包。

数据流:进去的是测试临时目录。它放一个假的 codex 可执行文件,再在同目录下的 codex-resources 里放可执行 bwrap。出来是断言结果:find_legacy_for_exe 应该找到这个 bwrap。

调用关系:这个测试覆盖 find_legacy_for_exe 的旧独立包路径。它通过 tests::write_executable 准备文件,然后把实际结果和预期绝对路径比较。

调用图:外部调用 3 个(assert_eq!, write_executable, tempdir)。

tests::finds_npm_bundled_bwrap_next_to_target_vendor_dir243–255 ↗
fn finds_npm_bundled_bwrap_next_to_target_vendor_dir()

作用:测试 npm 或类似打包布局下,bwrap 放在目标平台目录旁边时能不能被找到。它保护的是跨平台打包目录规则。

数据流:进去的是测试临时目录。它构造 vendor/x86_64-unknown-linux-musl/codex/codex 这样的程序路径,并把 bwrap 放在上一级目标目录的 codex-resources 下。出来是断言结果:find_legacy_for_exe 应该返回那个 bwrap。

调用关系:这个测试验证 legacy_candidates_for_exe 里“往上一层找资源目录”的候选路径。它同样用 tests::write_executable 准备假可执行文件。

调用图:外部调用 3 个(assert_eq!, write_executable, tempdir)。

tests::finds_adjacent_dev_bwrap258–269 ↗
fn finds_adjacent_dev_bwrap()

作用:测试开发环境里,bwrap 和 codex 放在同一个目录时能不能被找到。这样本地开发不一定要模拟完整安装包。

数据流:进去的是测试临时目录。它创建一个假的 codex 文件和同目录下的 bwrap 文件,并都设置成可执行。出来是断言结果:find_legacy_for_exe 应该选择同目录的 bwrap。

调用关系:这个测试覆盖 legacy_candidates_for_exe 中的“exe_dir/bwrap”候选项。它帮助开发者确认简单本地布局也能正常启动沙盒。

调用图:外部调用 3 个(assert_eq!, write_executable, tempdir)。

tests::digest_verification_skips_missing_expected_digest272–278 ↗
fn digest_verification_skips_missing_expected_digest()

作用:测试没有配置预期指纹时,摘要校验会被跳过。这样开发或未嵌入指纹的构建不会因为缺少指纹而无法运行。

数据流:进去的是一个写有 contents 的临时文件,以及 None 形式的预期指纹。它调用 verify_digest。出来是成功结果,表示没有预期值时不检查文件内容。

调用关系:这个测试直接调用 verify_digest,覆盖 BundledBwrapLauncher::exec 中可能遇到的“未启用校验”分支。

调用图:调用 1 个内部函数(verify_digest);外部调用 2 个(new, write)。

tests::digest_verification_accepts_matching_digest281–288 ↗
fn digest_verification_accepts_matching_digest()

作用:测试文件内容和预期 SHA-256 一致时,校验会通过。它确认安全检查不会误伤正确文件。

数据流:进去的是一个写有 contents 的临时文件。测试先用 Sha256::digest 算出 contents 的真实指纹,再把这个指纹传给 verify_digest。出来是成功结果。

调用关系:这个测试直接覆盖 verify_digest 的正常通过路径。它模拟 exec 启动前发现 bwrap 指纹正确的情况。

调用图:调用 1 个内部函数(verify_digest);外部调用 3 个(new, digest, write)。

tests::digest_verification_rejects_mismatched_digest291–298 ↗
fn digest_verification_rejects_mismatched_digest()

作用:测试文件内容和预期指纹不一致时,校验会失败。它保证被篡改或放错的 bwrap 不会悄悄运行。

数据流:进去的是一个写有 contents 的临时文件,以及故意伪造的全 0xab 指纹。它调用 verify_digest,预期得到错误,并检查错误文字里包含 digest mismatch。出来是测试断言通过或失败。

调用关系:这个测试覆盖 verify_digest 的失败路径,也间接验证 bytes_to_hex 生成的错误信息能用于排查问题。

调用图:调用 1 个内部函数(verify_digest);外部调用 3 个(new, assert!, write)。

tests::parses_sha256_hex_digest301–306 ↗
fn parses_sha256_hex_digest()

作用:测试 SHA-256 十六进制字符串解析是否正确。它保证环境里写入的指纹格式错了时会被及时发现。

数据流:进去的是几组字符串:合法的 ab 重复、合法的 00 重复、太短的 ab、以及末尾带 xx 的非法字符串。它调用 parse_sha256_hex 并比较成功或失败结果。出来是断言是否符合预期。

调用关系:这个测试直接保护 parse_sha256_hex,也间接保护 expected_sha256,因为 expected_sha256 依赖这个解析结果决定启动前校验用什么指纹。

调用图:外部调用 2 个(assert!, assert_eq!)。

tests::write_executable308–315 ↗
fn write_executable(path: &Path)

作用:给测试用的辅助函数:创建一个空文件,并把它设置成可执行。它让各个查找测试不用重复写准备文件的代码。

数据流:进去的是目标路径。它先创建父目录,再写入空内容,最后把权限设置为 0755,也就是所有者可读写执行,其他人可读执行。出来没有返回值,但磁盘上会出现一个可执行文件。

调用关系:多个测试会调用它来伪造 codex 或 bwrap。它不参与正式运行,只服务于测试里的安装布局和权限检查。

调用图:外部调用 5 个(parent, from_mode, create_dir_all, set_permissions, write)。

linux-sandbox/src/launcher.rs源码 ↗
orchestrationsandbox launch

这个文件像沙箱启动前的“门卫和发车员”。它先找系统里有没有可用的 bwrap(bubblewrap 的命令名),并检查这个 bwrap 是否支持项目需要的选项;如果系统的版本不合适,就改用项目自带的 bwrap;如果两者都没有,就直接报错,避免假装已经隔离但其实没启动。它还会记住第一次挑选出的结果,后面不用重复检查。真正启动系统 bwrap 时,它会先把需要保留的文件句柄设成“跨进程替换后仍可用”,再把 Rust 字符串转成 C 程序能吃的参数格式,最后调用 execv。execv 的意思是“用另一个程序替换当前进程”,成功后不会再回到这里。文件里还特别处理了老版本 bwrap 不支持 --argv0 的情况,避免在旧系统上因为一个参数不认识就启动失败。

函数细节11
exec_bwrap36–49 ↗
fn exec_bwrap(argv: Vec<String>, preserved_files: Vec<File>) -> !

作用:这是对外的主入口:给它一组 bwrap 参数和要保留的文件,它会选择合适的 bwrap 并启动。它的返回类型表示“永远不会正常返回”,因为成功后当前进程会被 bubblewrap 替换掉。

数据流:进去的是命令参数列表 argv 和 preserved_files 这些要带进新进程的文件 → 它先问 preferred_bwrap_launcher 该用系统版、自带版还是都没有 → 如果是系统版就交给 exec_system_bwrap,如果是自带版就调用自带启动器,如果没有可用 bwrap 就 panic 报错。

调用关系:run_bwrap_in_child_capture_stderr、run_bwrap_in_child_with_synthetic_mount_cleanup 和 run_or_exec_bwrap 会在需要真正进入沙箱时调用它。它本身不做复杂判断,而是先让 preferred_bwrap_launcher 选工具,再把执行动作交给系统版或自带版的具体启动代码。

调用图:调用 2 个内部函数(exec_system_bwrap, preferred_bwrap_launcher);被 3 处调用(run_bwrap_in_child_capture_stderr, run_bwrap_in_child_with_synthetic_mount_cleanup, run_or_exec_bwrap);外部调用 1 个(panic!)。

preferred_bwrap_launcher51–67 ↗
fn preferred_bwrap_launcher() -> BubblewrapLauncher

作用:这个函数负责选出“最应该用的 bubblewrap”。它优先用系统 PATH 里的 bwrap,系统版不合格时才用项目自带的版本。

数据流:它不需要外部输入 → 第一次调用时会查找系统 bwrap,检查它是否可用;不行就尝试拿自带 bwrap;结果会放进 OnceLock(一次性缓存,保证只初始化一次)→ 后续调用直接拿缓存的选择结果,返回系统版、自带版或不可用。

调用关系:exec_bwrap 靠它决定下一步怎么启动;preferred_bwrap_supports_argv0 也靠它判断当前选中的 bwrap 支不支持 --argv0。它位于整个启动流程的前置选择阶段。

调用图:被 2 处调用(exec_bwrap, preferred_bwrap_supports_argv0);外部调用 1 个(new)。

system_bwrap_launcher_for_path69–71 ↗
fn system_bwrap_launcher_for_path(system_bwrap_path: &Path) -> Option<SystemBwrapLauncher>

作用:这个函数检查某个路径是不是一个合格的系统 bwrap,并把它包装成可启动对象。它是普通运行时使用的简化入口。

数据流:进去的是一个文件路径 → 它把路径和真实的能力检测函数 system_bwrap_capabilities 一起交给 system_bwrap_launcher_for_path_with_probe → 出来的是 Some 系统启动器,或者 None 表示这个路径不能用。

调用关系:它是 system_bwrap_launcher_for_path_with_probe 的薄包装。正常代码用它来检查真实系统文件,测试则可以绕过它,直接给 with_probe 传假的检测结果。

调用图:调用 1 个内部函数(system_bwrap_launcher_for_path_with_probe)。

system_bwrap_launcher_for_path_with_probe73–99 ↗
fn system_bwrap_launcher_for_path_with_probe(
    system_bwrap_path: &Path,
    system_bwrap_capabilities: impl FnOnce(&Path) -> Option<SystemBwrapCapabilities>,
) -> Option<SystemBwrapLauncher>

作用:这个函数是判断系统 bwrap 是否合格的核心。它不只看文件在不在,还会确认这个 bwrap 支持项目必须用到的功能。

数据流:进去的是 bwrap 路径,以及一个“能力探测器”函数 → 它先确认路径确实是文件,再调用探测器拿到 supports_argv0 和 supports_perms;如果不支持 --perms 就拒绝;如果合格,就把路径规范成绝对路径并生成 SystemBwrapLauncher → 输出 Some 启动器或 None。

调用关系:system_bwrap_launcher_for_path 会调用它做真实检查。测试函数也直接调用它,并传入假探测器,这样不用真的安装不同版本的 bwrap 就能测试各种情况。它内部会调用 system_bwrap_capabilities 做默认能力检查。

调用图:调用 2 个内部函数(system_bwrap_capabilities, from_absolute_path);被 1 处调用(system_bwrap_launcher_for_path);外部调用 2 个(is_file, panic!)。

preferred_bwrap_supports_argv0101–106 ↗
fn preferred_bwrap_supports_argv0() -> bool

作用:这个函数告诉别的代码:当前选中的 bwrap 能不能使用 --argv0。--argv0 可以理解成“让新程序看到的自己名字”,有些老版本 bwrap 不支持。

数据流:它不接收输入 → 先调用 preferred_bwrap_launcher 看现在选的是谁 → 如果是系统版,就返回系统版检测出的 supports_argv0;如果是自带版或还没法启动,则按支持处理,返回 true。

调用关系:apply_inner_command_argv0 会用它来决定是否可以给内部命令设置 argv0。它让构造命令参数的代码不用知道 bwrap 是系统版还是自带版。

调用图:调用 1 个内部函数(preferred_bwrap_launcher);被 1 处调用(apply_inner_command_argv0)。

system_bwrap_capabilities108–124 ↗
fn system_bwrap_capabilities(system_bwrap_path: &Path) -> Option<SystemBwrapCapabilities>

作用:这个函数通过运行 bwrap --help 来判断一个系统 bwrap 支持哪些选项。它用最朴素的办法:看帮助文字里有没有对应参数名。

数据流:进去的是 bwrap 程序路径 → 它执行这个程序并传入 --help,读取标准输出和标准错误,把字节内容按 UTF-8 文本宽松转换 → 返回 SystemBwrapCapabilities,里面标出是否看到 --argv0 和 --perms;如果程序运行失败就返回 None。

调用关系:system_bwrap_launcher_for_path_with_probe 会调用它来做真实探测。它是选择系统 bwrap 是否安全可用的依据,尤其用来避开缺少 --perms 的版本。

调用图:被 1 处调用(system_bwrap_launcher_for_path_with_probe);外部调用 2 个(from_utf8_lossy, new)。

exec_system_bwrap126–152 ↗
fn exec_system_bwrap(
    program: &AbsolutePathBuf,
    argv: Vec<String>,
    preserved_files: Vec<File>,
) -> !

作用:这个函数真正启动系统安装的 bwrap。它会用 execv 把当前进程替换成 bwrap,所以成功时不会返回。

数据流:进去的是系统 bwrap 的绝对路径、参数 argv、以及要保留的文件 preserved_files → 它先调用 make_files_inheritable,让这些文件在 exec 后仍然打开;再把程序路径和参数转成 C 字符串数组;最后调用 libc::execv 执行替换 → 成功就没有返回,失败则读取系统错误并 panic。

调用关系:exec_bwrap 在确认要用系统 bwrap 后调用它。它还依赖 argv_to_cstrings 做参数转换,依赖 make_files_inheritable 处理文件句柄,是从 Rust 世界跨到操作系统执行接口的最后一步。

调用图:调用 3 个内部函数(argv_to_cstrings, make_files_inheritable, as_path);被 1 处调用(exec_bwrap);外部调用 6 个(new, last_os_error, execv, panic!, null, as_ptr)。

tests::prefers_system_bwrap_when_help_lists_argv0161–178 ↗
fn prefers_system_bwrap_when_help_lists_argv0()

作用:这个测试确认:当系统 bwrap 同时支持 --argv0 和 --perms 时,代码会接受它并记录它支持 --argv0。

数据流:它创建一个临时文件当作假的 bwrap 路径 → 给 system_bwrap_launcher_for_path_with_probe 传入一个假的探测结果,声明两个能力都支持 → 断言输出是一个 SystemBwrapLauncher,路径正确,supports_argv0 为 true。

调用关系:它直接测试 system_bwrap_launcher_for_path_with_probe 的选择规则,不需要真的调用系统 bwrap。这样可以稳定验证“新版系统 bwrap 优先使用”的情况。

调用图:调用 1 个内部函数(from_absolute_path);外部调用 2 个(new, assert_eq!)。

tests::prefers_system_bwrap_when_system_bwrap_lacks_argv0181–197 ↗
fn prefers_system_bwrap_when_system_bwrap_lacks_argv0()

作用:这个测试确认:系统 bwrap 即使不支持 --argv0,只要支持必须的 --perms,也仍然可以使用。

数据流:它创建临时文件作为假的 bwrap → 假探测器返回 supports_argv0 为 false、supports_perms 为 true → 断言函数仍然返回系统启动器,只是把 supports_argv0 记录为 false。

调用关系:它覆盖了老版本发行版里常见的情况。后续 preferred_bwrap_supports_argv0 和构造参数的代码可以根据这个 false 走兼容路径。

调用图:外部调用 2 个(new, assert_eq!)。

tests::ignores_system_bwrap_when_system_bwrap_lacks_perms200–212 ↗
fn ignores_system_bwrap_when_system_bwrap_lacks_perms()

作用:这个测试确认:如果系统 bwrap 不支持 --perms,就不能用它。--perms 是项目需要的权限设置参数,缺了会影响沙箱正确搭建。

数据流:它创建临时文件作为假的 bwrap → 假探测器返回 supports_perms 为 false → 断言 system_bwrap_launcher_for_path_with_probe 返回 None,表示拒绝这个系统 bwrap。

调用关系:它测试 system_bwrap_launcher_for_path_with_probe 的安全门槛。这个门槛能让 preferred_bwrap_launcher 在系统版不合格时继续尝试自带版。

调用图:外部调用 2 个(new, assert_eq!)。

tests::ignores_system_bwrap_when_system_bwrap_is_missing215–220 ↗
fn ignores_system_bwrap_when_system_bwrap_is_missing()

作用:这个测试确认:路径不存在时,代码不会把它当成可用的 bwrap。

数据流:进去的是一个明显不存在的路径 /definitely/not/a/bwrap → system_bwrap_launcher_for_path 检查发现它不是文件 → 返回 None,测试断言这个结果正确。

调用关系:它测试 system_bwrap_launcher_for_path 这层真实入口的基本防线。这样启动器不会因为 PATH 里找到坏路径或不存在路径而继续往下执行。

调用图:外部调用 1 个(assert_eq!)。

Unix shell 提权协议

这些文件定义 Unix shell 提权库接口及其客户端-服务器协议,用于拦截 exec 调用、查询策略并执行提权启动。

shell-escalation/src/lib.rs源码 ↗
othercompile-time and cross-cutting

这个文件本身不做具体的提权、执行命令或权限判断。它的作用更像一家店的前台:真正干活的人在后面的 unix 模块里,前台把这些服务的名字摆出来,方便别人使用。这里所有内容都带着 #[cfg(unix)],意思是“只在 Unix 类系统上编译”,比如 Linux、macOS 这类系统。因为 shell 提权、execve 这类能力本来就和 Unix 系统机制紧密相关,所以在非 Unix 平台上这些接口不会出现。它公开了提权服务器、提权策略、执行参数、执行结果、权限配置、命令执行器等类型,也公开了 execve 包装入口函数。没有这个文件,外部使用者就得直接了解内部 unix 模块的结构,代码会更乱,也更容易被内部改动影响。

shell-escalation/src/unix/mod.rs源码 ↗
orchestrationcross-cutting

这套机制解决的是一个很实际的问题:当一个被改造过的 shell 要执行命令时,系统需要先问一声“这个命令能不能直接跑,还是要交给更高权限的一端来跑”。这里的“shell”可以理解成命令行外壳,“exec”就是把当前程序换成另一个程序来运行。这个文件本身不写具体执行逻辑,而是像一个目录和前台接待处:它声明了下面有哪些模块,比如客户端、服务端、协议、权限策略、exec 包装器、socket 通信、计时器等;同时把最常用的类型和函数重新导出,方便外部直接引用。注释里说明了关键设计:每次执行请求都会带一个专门用于回复的文件描述符(可以理解成一根临时电话线),这样多个命令同时请求时,服务端也能分别回话,不会串线。没有这个总模块,外部代码就得到处找内部文件,接口也会更散、更难用。

shell-escalation/src/unix/escalation_policy.rs源码 ↗
domain_logicrequest handling

这个文件像是门卫工作的岗位说明书。客户端发来一个 execve 请求,也就是“我要在 Unix 系统里启动某个程序”的请求时,系统不能直接照做,因为这可能涉及提权、拦截或拒绝。这里定义的 EscalationPolicy 特征(trait,可以理解成一份接口合同)要求具体策略根据三个信息做判断:要运行的程序路径、命令参数、当前工作目录。判断结果是 EscalationDecision,也就是后续该采取的行动。这个判断被设计成异步的:异步可以理解成“先发起,稍后拿结果”,这样策略可以去问用户、查配置或做别的等待操作,而不会卡死整个系统。EscalationPolicyFuture 则把这种异步结果包装成统一类型,方便调用方不用关心具体策略内部怎么实现。

shell-escalation/src/unix/escalate_client.rs源码 ↗
io_transportrequest handling

这个文件解决的是一个很实际的问题:当程序想运行某个命令时,不能自己随便变成更高权限,也不能悄悄绕过规则。它会通过 Unix 套接字(一种同一台机器上进程之间说话的管道)联系提权服务,把要执行的文件、参数、当前目录和环境变量发过去。服务会回三种结果:允许普通运行、要求提权运行、或者拒绝。如果要提权,它还会把标准输入、标准输出、标准错误这三个“命令和终端之间的通道”复制一份交给服务,这样真正被提权启动的子进程还能像原来一样读键盘、写屏幕。这里特别小心地复制文件描述符(系统里代表打开文件或管道的小号码),避免把包装器自己还在用的通道提前关掉。如果服务说普通运行,它会直接调用底层 execv,把当前进程替换成目标程序,尽量不多做额外动作。

函数细节4
get_escalate_client19–28 ↗
fn get_escalate_client() -> anyhow::Result<AsyncDatagramSocket>

作用:从环境变量里拿到已经准备好的通信通道,并把它包装成可以异步收发消息的 Unix 数据报套接字。没有这个通道,客户端就没法找到提权服务。

数据流:进去的是当前进程的环境变量,特别是保存套接字文件描述符号码的那一项。它读取这个字符串,把它转成整数,并检查不能是负数;然后把这个已有的系统通道接管成 AsyncDatagramSocket。出来的是一个可用的客户端套接字;如果环境变量缺失、格式不对或号码非法,就返回错误。

调用关系:run_shell_escalation_execve_wrapper 一开始会调用它,用它先和提权服务做握手。它底层借助 from_raw_fd 把原始文件描述符变成程序里的套接字对象;这一步意味着所有权转移,所以注释里提醒未来最好防止重复调用。

调用图:调用 1 个内部函数(from_raw_fd);被 1 处调用(run_shell_escalation_execve_wrapper);外部调用 2 个(anyhow!, var)。

duplicate_fd_for_transfer30–34 ↗
fn duplicate_fd_for_transfer(fd: impl AsFd, name: &str) -> anyhow::Result<OwnedFd>

作用:复制一个文件描述符,专门用于发送给提权服务,同时保留原来的通道不受影响。它就像把钥匙配一把副本交出去,而不是把自己手里的钥匙交走。

数据流:进去的是一个已经打开的通道,比如 stdin、stdout 或 stderr,以及一个用于报错说明的名字。它把这个通道复制成新的 OwnedFd;如果复制失败,就把错误包装成更容易看懂的信息。出来的是一份新的文件描述符;原始通道仍然打开,调用者还能继续用。

调用关系:run_shell_escalation_execve_wrapper 在需要提权时调用它三次,分别复制标准输入、标准输出和标准错误,然后把副本交给服务。测试函数 tests::duplicate_fd_for_transfer_does_not_close_original 也会调用它,确认丢掉副本不会关掉原件。

调用图:被 2 处调用(run_shell_escalation_execve_wrapper, duplicate_fd_for_transfer_does_not_close_original);外部调用 1 个(as_fd)。

run_shell_escalation_execve_wrapper36–124 ↗
async fn run_shell_escalation_execve_wrapper(
    file: String,
    argv: Vec<String>,
) -> anyhow::Result<i32>

作用:这是这个文件的主流程:把“我要执行这个命令”的请求发给提权服务,并根据服务答复决定提权执行、普通执行,还是拒绝执行。调用者会用它来实现一个透明的执行包装器。

数据流:进去的是要运行的程序路径 file 和参数列表 argv。它先拿到和提权服务通信的套接字,再建立一对新的 Unix 套接字作为后续专用通道,把其中一端发给服务。接着它收集当前环境变量,但会去掉提权内部用的变量,连同当前工作目录、程序名和参数一起发成 EscalateRequest。收到 EscalateResponse 后,如果答复是 Escalate,就复制 stdin、stdout、stderr,告诉服务这些通道应该接到目标进程的哪些位置,然后等待服务返回退出码;如果答复是 Run,就用 execv 直接把当前进程替换成目标程序;如果答复是 Deny,就把拒绝原因打印到错误输出,并返回退出码 1。

调用关系:它是客户端侧提权流程的中心,先调用 get_escalate_client 找到服务,再用 AsyncSocket::pair 建专用连接,用 current_dir 记录当前目录。需要提权时会把复制文件描述符的细活交给 duplicate_fd_for_transfer;普通运行时则直接调用系统的 execv。它不通过 std::process::Command,是为了减少额外干预,让执行行为尽量像原生命令启动一样。

调用图:调用 4 个内部函数(duplicate_fd_for_transfer, get_escalate_client, pair, current_dir);外部调用 9 个(new, last_os_error, eprintln!, stderr, stdin, stdout, execv, vars, null)。

tests::duplicate_fd_for_transfer_does_not_close_original133–143 ↗
fn duplicate_fd_for_transfer_does_not_close_original()

作用:这个测试确认复制出来的文件描述符被关闭后,原来的文件描述符还活着。它防止以后有人改坏这个关键安全细节。

数据流:进去的是测试里临时创建的一对 UnixStream 套接字。测试取其中一端的原始文件描述符,调用 duplicate_fd_for_transfer 得到副本,确认副本号码和原件不同;随后丢弃副本,再用 fcntl 检查原始号码仍然有效。结果是测试通过或失败,不会产出业务数据。

调用关系:它只在测试运行时出现,专门验证 duplicate_fd_for_transfer 的行为。这个保证对 run_shell_escalation_execve_wrapper 很重要,因为提权流程需要把 stdin、stdout、stderr 的副本交出去,同时包装器自己不能因此失去这些通道。

调用图:调用 1 个内部函数(duplicate_fd_for_transfer);外部调用 2 个(assert_ne!, pair)。

shell-escalation/src/unix/escalate_server.rs源码 ↗
domain_logicrequest handling

可以把它想成一个“门卫室”。外面的 shell 每次想启动新程序时,会通过一个本地 socket(一种进程之间传话的管道)问门卫:这个程序是照常运行、拒绝,还是用更高权限运行?文件里的 EscalateServer 负责开这个门卫室,给 shell 注入两个环境变量:一个告诉它通信 socket 在哪,一个告诉它用哪个 execve 包装器拦截子命令。EscalationSession 代表一次正在服务的 shell 会话,负责保存这些环境变量,也负责在会话结束时关 socket、取消后台任务。真正收到请求后,handle_escalate_session_with_policy 会把程序路径补成绝对路径,询问 EscalationPolicy(提权规则),然后按结果回复:直接运行、拒绝,或准备并启动一个提权后的子进程。文件后半部分是测试,重点检查 socket 生命周期、路径解析、文件描述符传递、权限参数传递,以及会话丢弃时是否能杀掉已启动的子进程。

函数细节29
EscalationSession::env112–114 ↗
fn env(&self) -> &HashMap<String, String>

作用:把这次会话需要塞给 shell 的环境变量拿出来。调用者会把这些变量合并进 shell 的环境里,让 shell 知道该用哪个包装器、连哪个 socket。

数据流:输入是一个 EscalationSession 本身 → 它读取内部保存的 env 字典 → 返回这份环境变量映射的只读引用,不改动任何状态。

调用关系:这是会话启动后的取票窗口。EscalateServer::exec 会先创建会话,再通过它拿到环境变量;外部代码也可以在启动 shell 前调用它。调用图里它被 inherited_fds 使用,说明有人会通过它确认 socket 文件描述符是否正确传给子进程。

调用图:被 1 处调用(inherited_fds)。

EscalationSession::close_client_socket116–120 ↗
fn close_client_socket(&self)

作用:关闭父进程手里那份客户端 socket。这样 shell 启动后,父进程不会多拿一份本该只给 shell 继承的通信入口。

数据流:输入是会话对象 → 它尝试给 client_socket 上互斥锁(防止同时修改同一份 socket 状态)→ 如果拿到锁,就把里面的 Socket 取走并丢弃,结果是这个 socket 被关闭。

调用关系:它通常在 shell 刚 spawn(创建子进程)之后由 after_spawn 钩子调用,也会在 EscalationSession::drop 里兜底调用。这样可以避免文件描述符泄漏,让会话关闭更干净。

调用图:被 2 处调用(after_spawn, drop)。

EscalationSession::drop124–128 ↗
fn drop(&mut self)

作用:这是会话对象被销毁时自动执行的清理动作。它确保 socket、取消信号、后台任务都不会继续留着。

数据流:输入是即将被释放的会话 → 先调用 close_client_socket 关掉客户端 socket → 再触发 cancellation_token.cancel() 通知后台任务停下 → 最后 abort() 强行中止后台 tokio 任务。

调用关系:它是 EscalationSession 的安全网。即使调用者忘了手动收尾,只要会话对象离开作用域,这里也会通知 escalate_task 和相关工作任务结束,测试 dropping_session_aborts_intercept_workers_and_kills_spawned_child 就专门验证这个行为。

调用图:调用 1 个内部函数(close_client_socket);外部调用 2 个(cancel, abort)。

EscalateServer::new138–147 ↗
fn new(shell_path: PathBuf, execve_wrapper: PathBuf, policy: Policy) -> Self

作用:创建一个提权服务器对象。调用者把 shell 路径、execve 包装器路径和提权规则交给它,它之后就能启动会话或执行命令。

数据流:输入是 shell_path、execve_wrapper 和一个 policy → 它把 policy 放进 Arc(一种可被多个异步任务共享的引用计数指针)→ 输出一个 EscalateServer,里面保存这些配置。

调用关系:这是使用本文件功能的入口之一。外部的 prepare_unified_exec_zsh_fork、try_run_zsh_fork 会用它创建服务器;多个测试也用它搭出受控场景,然后再调用 start_session 或 exec。

调用图:被 5 处调用(prepare_unified_exec_zsh_fork, try_run_zsh_fork, dropping_session_aborts_intercept_workers_and_kills_spawned_child, exec_closes_parent_socket_after_shell_spawn, start_session_exposes_wrapper_env_overlay);外部调用 1 个(new)。

EscalateServer::exec149–182 ↗
async fn exec(
        &self,
        params: ExecParams,
        cancel_rx: CancellationToken,
        command_executor: Arc<dyn ShellCommandExecutor>,
    ) -> anyhow::Result<ExecResult>

作用:执行一条 shell 命令,同时给这条命令开好提权拦截通道。它适合“一次性跑一条命令,但命令内部可能还会启动需要提权的子命令”的场景。

数据流:输入是 ExecParams(命令、工作目录、超时、是否 login shell)、取消信号和命令执行器 → 它先 start_session 开会话,取出环境变量,再拼出 shell 命令数组,例如 bash -lc '...' → 把命令、目录、环境变量和 after_spawn 清理钩子交给 command_executor.run → 输出 ExecResult,也就是退出码、输出、耗时和是否超时。

调用关系:它把“开提权服务”和“真正启动 shell”串起来。开会话的细节交给 EscalateServer::start_session,进程启动和输出捕获交给 ShellCommandExecutor 的实现;after_spawn 会关闭父进程多余的 socket。

调用图:调用 2 个内部函数(start_session, try_from);外部调用 4 个(clone, new, clone, vec!)。

EscalateServer::start_session189–223 ↗
fn start_session(
        &self,
        parent_cancellation_token: CancellationToken,
        command_executor: Arc<dyn ShellCommandExecutor>,
    ) -> anyhow::Result<EscalationSession>

作用:开启一次提权会话,但不启动 shell。它只准备通信 socket、后台监听任务,以及 shell 需要继承的环境变量。

数据流:输入是父级取消信号和命令执行器 → 它创建一对 Unix 数据报 socket(一对本地通信端点),把客户端端点设置成可被 exec 后的 wrapper 继承 → 启动 escalate_task 在后台监听服务端端点 → 输出 EscalationSession,里面包含环境变量、后台任务句柄、客户端 socket 和取消令牌。

调用关系:EscalateServer::exec 会调用它;外部代码也可以单独调用它,然后自己负责启动 shell。它启动的后台任务是 escalate_task,后者会一直等 wrapper 发来的提权请求。

调用图:调用 2 个内部函数(escalate_task, pair);被 1 处调用(exec);外部调用 7 个(clone, new, new, new, new, to_string_lossy, spawn)。

escalate_task226–262 ↗
async fn escalate_task(
    socket: AsyncDatagramSocket,
    policy: Arc<dyn EscalationPolicy>,
    command_executor: Arc<dyn ShellCommandExecutor>,
    parent_cancellation_token: CancellationToken,

作用:这是后台监听循环。它守着会话 socket,等待 shell 里的 wrapper 送来新的连接,然后为每个连接开一个独立处理任务。

数据流:输入是监听用 socket、提权规则、命令执行器和两个取消信号 → 它循环等待 receive_with_fds,也就是接收消息和随消息传来的文件描述符 → 如果收到的不是一个连接 fd,就记错误并继续 → 如果正确,就把 fd 包装成 AsyncSocket,并 spawn 一个异步任务交给 handle_escalate_session_with_policy。

调用关系:它由 EscalateServer::start_session 启动,是一个会话里的总接线员。每次收到 wrapper 的握手,它就把具体请求交给 handle_escalate_session_with_policy;如果父级或会话取消,它就退出。

调用图:调用 2 个内部函数(handle_escalate_session_with_policy, from_fd);被 1 处调用(start_session);外部调用 5 个(clone, clone, select!, spawn, error!)。

handle_escalate_session_with_policy264–379 ↗
async fn handle_escalate_session_with_policy(
    socket: AsyncSocket,
    policy: Arc<dyn EscalationPolicy>,
    command_executor: Arc<dyn ShellCommandExecutor>,
    parent_cancellation_token: Cancel

作用:处理一次具体的提权询问。它决定某个即将执行的程序该照常跑、拒绝,还是由服务器启动一个提权版本。

数据流:输入是与 wrapper 通信的 socket、提权规则、命令执行器和取消信号 → 它先收 EscalateRequest,里面有程序、参数、工作目录和环境变量 → 把相对路径按工作目录补成绝对路径 → 问 policy.determine_action → 若决定 Run,就回传 Run;若 Deny,就回传拒绝原因;若 Escalate,就先告诉客户端要提权,再接收 SuperExecMessage 和文件描述符,调用 command_executor.prepare_escalated_exec 准备真实命令,启动子进程,必要时把收到的文件描述符 dup2 到目标位置,最后把退出码发回。

调用关系:它是整个文件最核心的处理函数。escalate_task 为每个连接调用它;测试会直接调用它来验证三种决策、路径解析、权限传递、文件描述符映射和子进程退出码。它把“规则判断”交给 EscalationPolicy,把“如何构造提权命令”交给 ShellCommandExecutor。

调用图:调用 2 个内部函数(send, resolve_path_against_base);被 6 处调用(escalate_task, handle_escalate_session_accepts_received_fds_that_overlap_destinations, handle_escalate_session_executes_escalated_command, handle_escalate_session_passes_permissions_to_executor, handle_escalate_session_resolves_relative_file_against_request_workdir, handle_escalate_session_respects_run_in_sandbox_decision);外部调用 5 个(null, anyhow!, new, select!, debug!)。

tests::DeterministicEscalationPolicy::determine_action410–417 ↗
fn determine_action(
            &'a self,
            _file: &'a AbsolutePathBuf,
            _argv: &'a [String],
            _workdir: &'a AbsolutePathBuf,
        ) -> EscalationPolicyFuture<'a>

作用:测试用的固定规则:不管请求是什么,都返回预先设好的决定。这样测试可以稳定地模拟“运行”“拒绝”或“提权”。

数据流:输入是文件、参数和工作目录,但它故意不看 → 克隆自己保存的 decision → 输出这个固定决定。

调用关系:很多测试用它控制 handle_escalate_session_with_policy 的分支。例如想测试提权执行,就把 decision 设成 Escalate;想测试正常运行,就设成 Run。

调用图:外部调用 2 个(pin, clone)。

tests::AssertingEscalationPolicy::determine_action426–437 ↗
fn determine_action(
            &'a self,
            file: &'a AbsolutePathBuf,
            _argv: &'a [String],
            workdir: &'a AbsolutePathBuf,
        ) -> EscalationPolicyFuture<'a>

作用:测试用的规则检查器。它确认服务器传给规则的程序路径和工作目录,确实是测试预期的值。

数据流:输入是 file、argv、workdir → 它比较 file 和 expected_file、workdir 和 expected_workdir → 如果不一致测试失败;如果一致,输出 Run 决定。

调用关系:它主要服务于 handle_escalate_session_resolves_relative_file_against_request_workdir,验证相对路径会先按工作目录解析,再交给策略判断。

调用图:调用 1 个内部函数(run);外部调用 2 个(pin, assert_eq!)。

tests::ForwardingShellCommandExecutor::run462–473 ↗
fn run(
            &self,
            _command: Vec<String>,
            _cwd: PathBuf,
            _env_overlay: HashMap<String, String>,
            _cancel_rx: CancellationToken,
            _afte

作用:这是测试执行器里一个不会被用到的 run。它存在只是为了满足 ShellCommandExecutor 这个接口。

数据流:输入的命令、目录、环境和取消信号都被忽略 → 函数直接进入 unreachable,意思是如果真的走到这里,测试就说明写错了 → 不会正常输出 ExecResult。

调用关系:handle_escalate_session_with_policy 的相关测试只需要 prepare_escalated_exec,不需要真正跑顶层 shell。所以这个 run 是占位保险,用来防止测试误走到错误流程。

调用图:外部调用 2 个(pin, unreachable!)。

tests::ForwardingShellCommandExecutor::prepare_escalated_exec475–486 ↗
fn prepare_escalated_exec(
            &'a self,
            program: &'a AbsolutePathBuf,
            argv: &'a [String],
            workdir: &'a AbsolutePathBuf,
            env: HashMap<String, St

作用:测试用的“照原样转发”提权命令准备器。它把请求里的程序和参数拼成一个可以直接执行的 PreparedExec。

数据流:输入是 program、argv、workdir、env 和 execution → 它忽略 execution,把 program 放到命令第一项,再接上 argv 中除第一个以外的参数 → cwd 设成 workdir,env 原样带上,arg0 设成 argv 的第一项 → 输出 PreparedExec。

调用关系:提权分支测试会通过它让服务器实际启动 /bin/sh 等命令。它被 ShellCommandExecutor trait 的实现方法包装调用,然后交给 handle_escalate_session_with_policy 用来 spawn 子进程。

调用图:调用 2 个内部函数(to_path_buf, to_string_lossy);外部调用 3 个(pin, prepare_escalated_exec, once)。

tests::PermissionAssertingShellCommandExecutor::run518–529 ↗
fn run(
            &self,
            _command: Vec<String>,
            _cwd: PathBuf,
            _env_overlay: HashMap<String, String>,
            _cancel_rx: CancellationToken,
            _afte

作用:这是权限检查测试执行器里不会被用到的 run,占位用。若测试误调用它,就直接失败。

数据流:输入的命令、目录、环境和钩子都被忽略 → 进入 unreachable → 不产生正常结果。

调用关系:权限传递测试只关心 prepare_escalated_exec 是否收到正确的 EscalationExecution。这个 run 的存在只是为了让测试结构实现 ShellCommandExecutor 接口。

调用图:外部调用 2 个(pin, unreachable!)。

tests::PermissionAssertingShellCommandExecutor::prepare_escalated_exec531–544 ↗
fn prepare_escalated_exec(
            &'a self,
            program: &'a AbsolutePathBuf,
            argv: &'a [String],
            workdir: &'a AbsolutePathBuf,
            env: HashMap<String, St

作用:测试提权权限参数有没有被原样传到执行器。它先断言收到的 execution 和预期权限一致,再准备一个普通可执行命令。

数据流:输入是 program、argv、workdir、env、execution → 它把 execution 和 expected_permissions 包成的值比较 → 一致就像 ForwardingShellCommandExecutor 一样拼出 PreparedExec;不一致测试失败。

调用关系:它被 handle_escalate_session_passes_permissions_to_executor 使用。该测试先让策略返回带权限的 Escalate 决定,再确认 handle_escalate_session_with_policy 没有把这份权限信息弄丢。

调用图:调用 2 个内部函数(to_path_buf, to_string_lossy);外部调用 4 个(pin, assert_eq!, prepare_escalated_exec, once)。

tests::wait_for_pid_file547–561 ↗
async fn wait_for_pid_file(pid_file: &std::path::Path) -> anyhow::Result<i32>

作用:测试辅助函数:等待某个文件出现,并从里面读出进程号。它用来确认被启动的子进程真的已经跑起来。

数据流:输入是 pid_file 路径 → 它最多等 5 秒,每 20 毫秒尝试读一次文件 → 读到后把内容 trim 并解析成 i32 进程号 → 如果超时,返回错误。

调用关系:dropping_session_aborts_intercept_workers_and_kills_spawned_child 用它等 shell 写出自己的 pid,然后才能测试丢弃会话后这个进程是否被杀掉。

调用图:外部调用 6 个(from_millis, from_secs, now, anyhow!, read_to_string, sleep)。

tests::process_exists563–569 ↗
fn process_exists(pid: i32) -> bool

作用:检查某个进程号对应的进程是否还存在。它用 Unix 的 kill(pid, 0) 做探测,这不会真的杀进程,只是问系统“这个进程在吗”。

数据流:输入是 pid → 调用 libc::kill(pid, 0) → 如果返回 0,说明进程存在;如果失败但错误不是 ESRCH(没有这个进程),也按存在处理;只有 ESRCH 才表示不存在。

调用关系:wait_for_process_exit 会反复调用它。相关测试用它确认长时间 sleep 的子进程先存在,再在会话 drop 后消失。

调用图:外部调用 2 个(last_os_error, kill)。

tests::AfterSpawnAssertingShellCommandExecutor::run600–613 ↗
fn run(
            &self,
            _command: Vec<String>,
            _cwd: PathBuf,
            env_overlay: HashMap<String, String>,
            _cancel_rx: CancellationToken,
            after_

作用:测试 exec 是否在 shell 启动后调用 after_spawn 钩子,并由钩子关闭父进程的 socket。它不真正执行外部命令,只模拟成功结果。

数据流:输入是 env_overlay 和 after_spawn → 它从环境里取出 ESCALATE_SOCKET_ENV_VAR,解析成文件描述符号,确认这个 fd 当前有效 → 调用 after_spawn → 把 after_spawn_invoked 标记设为 true → 输出一个退出码为 0、无输出、未超时的 ExecResult。

调用关系:exec_closes_parent_socket_after_shell_spawn 使用这个执行器。EscalateServer::exec 把 after_spawn 钩子传给它,测试据此确认 socket 生命周期处理正确。

调用图:外部调用 4 个(pin, new, assert_ne!, run)。

tests::AfterSpawnAssertingShellCommandExecutor::prepare_escalated_exec615–624 ↗
fn prepare_escalated_exec(
            &'a self,
            _program: &'a AbsolutePathBuf,
            _argv: &'a [String],
            _workdir: &'a AbsolutePathBuf,
            _env: HashMap<String

作用:这是 after_spawn 测试执行器里不会用到的提权准备函数。若被调用,就说明测试走错了流程。

数据流:输入的程序、参数、目录、环境和执行方式都被忽略 → 直接 unreachable → 不返回正常 PreparedExec。

调用关系:exec_closes_parent_socket_after_shell_spawn 只测试 EscalateServer::exec 启动顶层 shell 的路径,不测试提权子命令,所以这里是接口占位。

调用图:外部调用 2 个(pin, unreachable!)。

tests::wait_for_process_exit627–638 ↗
async fn wait_for_process_exit(pid: i32) -> anyhow::Result<()>

作用:测试辅助函数:等待指定进程退出。它避免测试只发出取消信号,却没确认子进程真的结束。

数据流:输入是 pid → 设置 5 秒截止时间 → 循环调用 process_exists 检查进程是否还在,每 20 毫秒睡一次 → 如果进程消失,返回成功;如果超时还在,返回错误。

调用关系:dropping_session_aborts_intercept_workers_and_kills_spawned_child 在 drop(session) 后调用它,验证会话清理确实杀掉了已启动的提权子进程。

调用图:外部调用 6 个(from_millis, from_secs, now, anyhow!, process_exists, sleep)。

tests::start_session_exposes_wrapper_env_overlay649–688 ↗
async fn start_session_exposes_wrapper_env_overlay() -> anyhow::Result<()>

作用:测试 start_session 返回的环境变量只包含 wrapper 和 socket 所需信息,并且 socket 在手动关闭前是有效的。

数据流:输入是测试框架提供的异步运行环境 → 它创建服务器和会话 → 读取 session.env,检查 EXEC_WRAPPER_ENV_VAR 等于设定路径,ESCALATE_SOCKET_ENV_VAR 是有效 fd → 再调用 close_client_socket,确认内部 socket 变成 none → 测试成功返回。

调用关系:它直接覆盖 EscalateServer::start_session 和 EscalationSession::env、close_client_socket 的基本契约。用假的 shell 和 wrapper 路径,是为了证明 start_session 只是传环境,不会真的执行这些路径。

调用图:调用 2 个内部函数(run, new);外部调用 6 个(new, new, from, assert!, assert_eq!, assert_ne!)。

tests::exec_closes_parent_socket_after_shell_spawn691–722 ↗
async fn exec_closes_parent_socket_after_shell_spawn() -> anyhow::Result<()>

作用:测试 EscalateServer::exec 在 shell 被创建后,会关闭父进程保留的客户端 socket。这样 socket 不会因为父进程多拿一份而泄漏。

数据流:输入是测试运行环境 → 它创建带固定 Run 策略的服务器和 AfterSpawnAssertingShellCommandExecutor → 调用 server.exec 执行 true → 执行器检查 fd 有效并触发 after_spawn → 测试检查退出码为 0 且 after_spawn 标记已被设置。

调用关系:它把 EscalateServer::exec、start_session、session 环境变量和 after_spawn 钩子连起来测。重点不是命令 true 本身,而是 shell 启动后资源清理是否发生。

调用图:调用 3 个内部函数(run, new, current_dir);外部调用 7 个(clone, new, new, new, from, assert!, assert_eq!)。

tests::handle_escalate_session_respects_run_in_sandbox_decision725–761 ↗
async fn handle_escalate_session_respects_run_in_sandbox_decision() -> anyhow::Result<()>

作用:测试当策略说“正常运行”时,服务器会只回 Run,不会尝试提权执行。

数据流:输入是测试环境 → 它创建一对 AsyncSocket,后台启动 handle_escalate_session_with_policy,策略固定返回 Run → 客户端发送 EscalateRequest,里面还塞了较大的环境变量 → 客户端收到 EscalateResponse::Run → 等服务器任务结束。

调用关系:它直接验证 handle_escalate_session_with_policy 的 Run 分支。ForwardingShellCommandExecutor 在这里不会真正用到,只是满足函数参数。

调用图:调用 4 个内部函数(run, handle_escalate_session_with_policy, pair, try_from);外部调用 8 个(new, new, new, from, assert_eq!, format!, spawn, vec!)。

tests::handle_escalate_session_resolves_relative_file_against_request_workdir764–801 ↗
async fn handle_escalate_session_resolves_relative_file_against_request_workdir() -> anyhow::Result<()>

作用:测试请求里的相对程序路径会按请求提供的工作目录解析成绝对路径,再交给策略判断。

数据流:输入是测试环境 → 它创建临时 workspace,并预期 ./bin/tool 应该变成 workspace/bin/tool → 用 AssertingEscalationPolicy 检查 file 和 workdir → 客户端发送相对路径请求 → 收到 Run 响应表示检查通过。

调用关系:它专门覆盖 handle_escalate_session_with_policy 里的 resolve_path_against_base 这一步,防止策略看到含糊的相对路径而做错判断。

调用图:调用 3 个内部函数(handle_escalate_session_with_policy, pair, try_from);外部调用 9 个(new, new, new, from, assert_eq!, create_dir, new, spawn, vec!)。

tests::handle_escalate_session_executes_escalated_command804–846 ↗
async fn handle_escalate_session_executes_escalated_command() -> anyhow::Result<()>

作用:测试当策略决定提权时,服务器会真的准备并启动命令,并把退出码传回客户端。

数据流:输入是测试环境 → 策略固定返回 Escalate Unsandboxed → 客户端先发执行 /bin/sh 的请求和环境变量 KEY=VALUE → 收到 Escalate 响应后,再发 SuperExecMessage → 服务器启动 shell,shell 检查环境变量后 exit 42 → 客户端收到 SuperExecResult,退出码应为 42。

调用关系:它覆盖 handle_escalate_session_with_policy 的 Escalate 主流程:先回复提权、再收文件描述符消息、再调用 ForwardingShellCommandExecutor 准备命令、最后启动子进程并回传结果。

调用图:调用 4 个内部函数(escalate, handle_escalate_session_with_policy, pair, current_dir);外部调用 8 个(new, new, from, from, new, assert_eq!, spawn, vec!)。

tests::RestoredFd::close_temporarily864–880 ↗
fn close_temporarily(target_fd: i32) -> anyhow::Result<Self>

作用:测试辅助函数:临时关闭某个文件描述符,但先复制一份以便之后恢复。这里主要用来制造“收到的 fd 正好占用了目标 fd 号”的特殊情况。

数据流:输入是 target_fd → 它先 dup 复制 target_fd,保存副本 → 再 close 原来的 target_fd 号码 → 输出 RestoredFd,里面记着目标号码和副本;如果 dup 或 close 失败,就返回系统错误。

调用关系:handle_escalate_session_accepts_received_fds_that_overlap_destinations 用它临时关闭 stdin。这样通过 SCM_RIGHTS 收到的 fd 可能被系统分配到 0,正好测试 dup2 源和目标相同的情况。

调用图:外部调用 4 个(last_os_error, close, dup, from_raw_fd)。

tests::RestoredFd::drop888–892 ↗
fn drop(&mut self)

作用:当 RestoredFd 离开作用域时,把之前临时关闭的文件描述符恢复回原来的号码。这样测试不会把当前测试进程的标准输入弄坏。

数据流:输入是即将销毁的 RestoredFd → 它取出保存的 original_fd → 调用 dup2,把副本复制回 target_fd 这个编号 → 不返回结果,也不主动报错。

调用关系:它是 close_temporarily 的配套清理动作。重叠 fd 测试结束或提前退出时,它都会尽量恢复 stdin,保证测试自包含。

调用图:外部调用 2 个(as_raw_fd, dup2)。

tests::handle_escalate_session_accepts_received_fds_that_overlap_destinations896–966 ↗
async fn handle_escalate_session_accepts_received_fds_that_overlap_destinations() -> anyhow::Result<()>

作用:测试服务器能正确处理一种很刁钻的 fd 情况:收到的文件描述符编号,刚好和要映射到的目标编号一样。

数据流:输入是测试环境 → 它创建管道,把 stdin 临时关掉,让接收端 fd 可能落到 0 → 启动提权会话处理 → 客户端请求运行 shell,并在 SuperExecMessage 里要求把收到的管道读端映射到 stdin → 向管道写入 overlap-ok → 子进程从 stdin 读到内容并成功退出 → 客户端收到退出码 0。

调用关系:它覆盖 handle_escalate_session_with_policy 中 pre_exec 的 dup2 循环。这个测试防止实现者假设源 fd 和目标 fd 永远不同,因为 Unix fd 分配有时会让它们重叠。

调用图:调用 4 个内部函数(escalate, handle_escalate_session_with_policy, pair, current_dir);外部调用 12 个(new, new, new, from, assert_eq!, last_os_error, pipe, close_temporarily, from_raw_fd, from_raw_fd (+2 more))。

tests::handle_escalate_session_passes_permissions_to_executor969–1023 ↗
async fn handle_escalate_session_passes_permissions_to_executor() -> anyhow::Result<()>

作用:测试策略给出的具体权限要求,会被一路传给命令执行器,而不是只传一个笼统的“要提权”。

数据流:输入是测试环境 → 策略返回 EscalationExecution::Permissions,里面包含额外权限配置,例如启用网络 → PermissionAssertingShellCommandExecutor 在 prepare_escalated_exec 中检查收到的 execution 是否完全一致 → 命令执行并返回退出码 0 → 测试确认结果成功。

调用关系:它覆盖 handle_escalate_session_with_policy 从 policy.determine_action 到 command_executor.prepare_escalated_exec 的参数传递链路。这样后续真正执行器才能按权限配置启动子进程。

调用图:调用 4 个内部函数(escalate, handle_escalate_session_with_policy, pair, current_dir);外部调用 11 个(new, new, default, new, from, new, assert_eq!, AdditionalPermissionProfile, Permissions, spawn (+1 more))。

tests::dropping_session_aborts_intercept_workers_and_kills_spawned_child1026–1114 ↗
async fn dropping_session_aborts_intercept_workers_and_kills_spawned_child() -> anyhow::Result<()>

作用:测试丢弃 EscalationSession 时,后台拦截任务会停止,已经由提权流程启动的长时间子进程也会被杀掉。

数据流:输入是测试环境 → 它创建服务器和会话,复制会话 socket fd,模拟 wrapper 握手并发送一个会 sleep 100 秒的 shell 请求 → 服务器返回 Escalate 后,客户端发送 SuperExecMessage → 子进程写出 pid 文件并开始 sleep → 测试确认进程存在 → drop(session) → 等待进程退出,若 5 秒内不退出则失败。

调用关系:这是对 EscalationSession::drop、escalate_task 和 handle_escalate_session_with_policy 清理行为的端到端测试。它证明会话生命周期结束时,不会留下孤儿后台任务或卡住的提权子进程。

调用图:调用 5 个内部函数(escalate, new, from_raw_fd, pair, current_dir);外部调用 13 个(new, new, new, from, new, new, assert!, assert_eq!, dup, wait_for_pid_file (+3 more))。

运行时集成

这些文件将共享的运行时命令准备与 Unix 提权后端连接起来,使工具启动能在策略要求时被改写、沙盒化和提权。

core/src/tools/runtimes/mod.rs源码 ↗
orchestrationrequest handling / command preparation

可以把这个文件理解成工具运行前的“后台化妆间”。真正执行命令前,它先把命令包装成沙箱认识的格式;再根据权限决定哪些环境变量能带进去;还会整理 PATH,也就是系统找程序时看的目录清单。它特别小心两类情况:一是提权运行时,不让 Codex 管理的网络代理和证书配置误带到更高权限环境里;二是恢复 shell 快照时,把用户明确指定的环境变量、代理变量和 Codex 自己加过的 PATH 再补回来,避免快照把它们冲掉。Windows 上还有一个保护:在特定提权沙箱里运行 PowerShell 时自动加上 -NoProfile,防止读取真实用户的启动脚本。

函数细节24
build_sandbox_command38–55 ↗
fn build_sandbox_command(
    command: &[String],
    cwd: &AbsolutePathBuf,
    env: &HashMap<String, String>,
    additional_permissions: Option<AdditionalPermissionProfile>,
) -> Result<SandboxComm

作用:把一串命令参数整理成沙箱能理解的命令对象。沙箱就是一个受限制的运行盒子,用来控制命令能访问什么。

数据流:进去的是命令数组、当前工作目录、环境变量和额外权限说明;它先确认命令里至少有程序名,再把目录转成沙箱使用的路径格式,把程序名、参数、环境变量一起装进 SandboxCommand;出来的是可交给沙箱执行的命令对象,若命令为空则返回拒绝错误。

调用关系:它在真正运行工具前被 run、try_run_zsh_fork 等流程调用。它自己只做打包工作,其中路径转换交给 from_abs_path,后面的审批、沙箱和重试流程会拿它的结果继续执行。

调用图:调用 1 个内部函数(from_abs_path);被 3 处调用(run, try_run_zsh_fork, run)。

exec_env_for_sandbox_permissions57–68 ↗
fn exec_env_for_sandbox_permissions(
    env: &HashMap<String, String>,
    sandbox_permissions: SandboxPermissions,
) -> HashMap<String, String>

作用:根据这次命令需要的沙箱权限,决定环境变量要不要清理。重点是提权时不要把 Codex 托管的代理设置带进去。

数据流:进去的是一份环境变量表和沙箱权限;它复制一份环境变量,如果权限要求更高且发现代理处于启用状态,就调用 strip_managed_proxy_env 删除托管代理相关内容;出来的是清理后的新环境变量表,原表不被改动。

调用关系:run、prepare_escalated_exec、try_run_zsh_fork 等在准备执行环境时会用它。它先询问权限对象 requires_escalated_permissions,再把具体清理工作交给 strip_managed_proxy_env。

调用图:调用 2 个内部函数(strip_managed_proxy_env, requires_escalated_permissions);被 4 处调用(run, prepare_escalated_exec, try_run_zsh_fork, run)。

strip_managed_proxy_env70–90 ↗
fn strip_managed_proxy_env(env: &mut HashMap<String, String>)

作用:从环境变量里移除 Codex 自己管理的网络代理和证书设置。这样能避免这些设置在不合适的权限或用户上下文里继续生效。

数据流:进去的是可修改的环境变量表;它删除代理变量,删除指向 Codex 管理证书包的证书变量;在 macOS 上,如果 Git SSH 命令是 Codex 注入的代理包装器,也会删掉;出来没有返回值,但传入的环境变量表会被直接改干净。

调用关系:它既会被 exec_env_for_sandbox_permissions 在提权沙箱准备阶段调用,也会被 execute_user_shell_command 在用户 shell 命令执行前调用。它是环境清理的具体执行者。

调用图:被 2 处调用(execute_user_shell_command, exec_env_for_sandbox_permissions)。

prepend_path_entry99–116 ↗
fn prepend_path_entry(env: &mut HashMap<String, String>, path_entry: &str) -> Option<String>

作用:把一个目录加到 PATH 最前面。PATH 是系统找可执行程序时按顺序查找的目录列表。

数据流:进去的是环境变量表和一个目录字符串;如果目录为空,它什么都不做,避免空 PATH 项意外表示“当前目录”;如果不为空,它把这个目录放到 PATH 开头,同时去掉旧 PATH 里的空项和重复项;出来是新的 PATH 字符串,环境变量表也被更新。

调用关系:它是 Unix 系统上的底层小工具,被 RuntimePathPrepends::prepend 和 prepend_zsh_fork_bin_to_path 调用。上层负责决定加哪个目录,它负责安全地改 PATH。

调用图:被 2 处调用(prepend, prepend_zsh_fork_bin_to_path);外部调用 1 个(once)。

RuntimePathPrepends::prepend129–135 ↗
fn prepend(&mut self, env: &mut HashMap<String, String>, path_entry: &Path)

作用:记录并应用 Codex 运行时自己加到 PATH 前面的目录。记录下来是为了以后恢复 shell 快照后还能重新加回来。

数据流:进去的是自身记录、环境变量表和一个路径;它把路径转成字符串,调用 prepend_path_entry 改 PATH;如果确实改了,就先从记录里删掉重复项,再把该目录记到末尾;出来没有返回值,但环境变量和内部记录都会更新。

调用关系:apply_package_path_prepend 和 apply_zsh_fork_path_prepend 会调用它。它把实际改 PATH 的活交给 prepend_path_entry,同时维护一份“Codex 加过哪些 PATH”的账本。

调用图:调用 1 个内部函数(prepend_path_entry);被 2 处调用(apply_package_path_prepend, apply_zsh_fork_path_prepend);外部调用 1 个(to_string_lossy)。

RuntimePathPrepends::shell_exports_after_snapshot137–156 ↗
fn shell_exports_after_snapshot(
        &self,
        explicit_env_overrides: &HashMap<String, String>,
    ) -> String

作用:生成一段 shell 命令,用来在恢复快照后重新补上 Codex 加过的 PATH。shell 命令就是终端能执行的文本脚本。

数据流:进去的是 Codex 记录的 PATH 目录和用户明确覆盖的环境变量;如果用户明确设置了 PATH,它尊重用户选择,返回空字符串;否则为每个非空目录生成 export PATH=... 的脚本片段;出来是一段可插入到 shell 包装脚本里的文本。

调用关系:maybe_wrap_shell_lc_with_snapshot 在包 shell 快照时会调用它。它不执行脚本,只负责把“恢复 PATH”的说明写成 shell 能懂的文本。

调用图:被 1 处调用(maybe_wrap_shell_lc_with_snapshot);外部调用 1 个(new)。

apply_package_path_prepend160–173 ↗
fn apply_package_path_prepend(
    env: &mut HashMap<String, String>,
    runtime_path_prepends: &mut RuntimePathPrepends,
)

作用:把 Codex 安装包自带的命令目录加到 PATH 前面。这样运行时可以优先找到 Codex 随包提供的小工具。

数据流:进去的是环境变量表和 PATH 追加记录器;它读取当前安装上下文,找到安装包布局里的 path_dir;如果找不到就直接返回;如果找到,就调用 RuntimePathPrepends::prepend 应用并记录;出来没有返回值,但可能修改 PATH 和记录器。

调用关系:run 流程在准备执行环境时会调用它。它从 InstallContext::current 取得安装信息,再把具体 PATH 修改交给 RuntimePathPrepends::prepend。

调用图:调用 2 个内部函数(prepend, current);被 2 处调用(run, run)。

prepend_zsh_fork_bin_to_path176–184 ↗
fn prepend_zsh_fork_bin_to_path(
    env: &mut HashMap<String, String>,
    shell_zsh_path: &Path,
) -> Option<String>

作用:把某个 zsh 可执行文件所在的目录临时放到 PATH 前面。zsh 是一种常见 Unix shell,也就是命令解释器。

数据流:进去的是环境变量表和 zsh 程序路径;它先取这个程序的父目录,也就是 bin 目录;如果没有父目录就返回 None;如果有,就调用 prepend_path_entry 更新 PATH;出来是新的 PATH,或表示没有改动的 None。

调用关系:try_run_zsh_fork 在尝试使用 zsh 分支运行方式时会调用它。它只处理路径目录,真正的 PATH 拼接规则交给 prepend_path_entry。

调用图:调用 1 个内部函数(prepend_path_entry);被 1 处调用(try_run_zsh_fork);外部调用 1 个(parent)。

apply_zsh_fork_path_prepend187–196 ↗
fn apply_zsh_fork_path_prepend(
    env: &mut HashMap<String, String>,
    runtime_path_prepends: &mut RuntimePathPrepends,
    shell_zsh_path: &Path,
)

作用:把 zsh fork 使用的 zsh 所在目录加到 PATH,并记录这是 Codex 运行时加的。这样后续恢复快照时也能补回来。

数据流:进去的是环境变量表、PATH 追加记录器和 zsh 路径;它取 zsh 的父目录,如果不存在就不做事;如果存在,就调用 RuntimePathPrepends::prepend;出来没有返回值,但可能更新 PATH 和记录。

调用关系:run 流程在准备 zsh fork 相关运行环境时会调用它。它负责找目录,实际修改和记录交给 RuntimePathPrepends::prepend。

调用图:调用 1 个内部函数(prepend);被 2 处调用(run, run);外部调用 1 个(parent)。

disable_powershell_profile_for_elevated_windows_sandbox198–225 ↗
fn disable_powershell_profile_for_elevated_windows_sandbox(
    command: &[String],
    shell_type: Option<&ShellType>,
    sandbox: SandboxType,
    windows_sandbox_level: WindowsSandboxLevel,
) -> V

作用:在特定 Windows 提权沙箱里运行 PowerShell 时,自动加上 -NoProfile。这个选项会禁止 PowerShell 读取用户启动配置,避免混用沙箱账号和真实用户配置造成怪问题。

数据流:进去的是命令数组、shell 类型、沙箱类型和 Windows 沙箱级别;它只在“PowerShell + WindowsRestrictedToken + Elevated + 命令非空”时处理;如果命令里已经有 -NoProfile,就原样返回;否则在程序名后插入 -NoProfile;出来是一份新的命令数组。

调用关系:run 在准备 Windows PowerShell 命令时会调用它。文件里的多个测试函数也调用它,确认该加时会加、不该加时完全不碰原命令。

调用图:被 8 处调用(inserts_no_profile_before_encoded_command, inserts_no_profile_for_elevated_windows_sandbox, leaves_legacy_restricted_token_backend_alone, leaves_non_powershell_alone, leaves_unsandboxed_attempts_alone, preserves_existing_no_profile, run, run)。

maybe_wrap_shell_lc_with_snapshot250–313 ↗
fn maybe_wrap_shell_lc_with_snapshot(
    command: &[String],
    session_shell: &Shell,
    shell_snapshot: Option<&AbsolutePathBuf>,
    explicit_env_overrides: &HashMap<String, String>,
    env: &H

作用:必要时把形如 shell -lc '脚本' 的命令改写成一个包装命令,让它先恢复 shell 快照,再执行原脚本。快照可以理解为之前保存的一份 shell 状态。

数据流:进去的是原命令、会话 shell、快照路径、用户明确设置的环境变量、当前完整环境变量和 Codex 记录的 PATH;它在 Windows、无快照、快照不存在、命令太短或不是 -lc 时都原样返回;符合条件时,它生成一段新脚本:先保存需要保住的环境变量,再 source 快照,然后恢复用户覆盖、代理变量和 Codex PATH,最后 exec 原 shell 执行原脚本;出来是改写后的命令数组。

调用关系:prepare_user_shell_exec_command、prepare_user_shell_exec_command_with_path_prepend 和 run 会在准备用户 shell 执行时调用它。它把生成环境恢复脚本的细活交给 build_override_exports、build_proxy_env_exports、RuntimePathPrepends::shell_exports_after_snapshot、join_shell_blocks 和 shell_single_quote。

调用图:调用 5 个内部函数(shell_exports_after_snapshot, build_override_exports, build_proxy_env_exports, join_shell_blocks, shell_single_quote);被 4 处调用(prepare_user_shell_exec_command, prepare_user_shell_exec_command_with_path_prepend, run, run);外部调用 3 个(cfg!, format!, vec!)。

build_override_exports315–324 ↗
fn build_override_exports(explicit_env_overrides: &HashMap<String, String>) -> (String, String)

作用:为用户明确指定的环境变量生成“先保存、后恢复”的 shell 脚本。这样 shell 快照不会把用户本次指定的变量覆盖掉。

数据流:进去的是用户明确覆盖的环境变量表;它挑出名字符合 shell 变量规则的键并排序;然后调用 build_override_exports_for_keys 生成两段文本:一段用于保存原值,一段用于恢复原值;出来是这两段脚本文本。

调用关系:maybe_wrap_shell_lc_with_snapshot 在构造快照包装脚本时调用它。它不关心变量值本身,只负责按变量名生成保存和恢复的脚本模板。

调用图:调用 1 个内部函数(build_override_exports_for_keys);被 1 处调用(maybe_wrap_shell_lc_with_snapshot)。

build_proxy_env_exports326–350 ↗
fn build_proxy_env_exports() -> (String, String)

作用:为 Codex 网络代理相关环境变量生成快照前后保护脚本。它保证快照恢复后,代理设置不会被无意覆盖或丢失。

数据流:进去没有普通参数,它使用模块里的代理变量名单和证书变量名单;它过滤出合法 shell 变量名、排序去重,再生成保存和恢复脚本;同时单独处理代理是否启用的标记,并把 macOS 的 Git SSH 代理包装器逻辑合并进去;出来是捕获脚本和恢复脚本两段文本。

调用关系:maybe_wrap_shell_lc_with_snapshot 在需要恢复快照时调用它。它内部调用 build_override_exports_for_keys 生成通用变量保存逻辑,调用 build_codex_proxy_git_ssh_command_exports 处理 Git SSH 的特殊情况,再用 join_shell_blocks 拼成完整脚本。

调用图:调用 3 个内部函数(build_codex_proxy_git_ssh_command_exports, build_override_exports_for_keys, join_shell_blocks);被 1 处调用(maybe_wrap_shell_lc_with_snapshot);外部调用 1 个(format!)。

build_codex_proxy_git_ssh_command_exports367–369 ↗
fn build_codex_proxy_git_ssh_command_exports() -> (String, String)

作用:处理 Codex 在 macOS 上为 Git SSH 注入的代理命令;在非 macOS 上它返回空脚本。Git SSH 命令是 Git 连接 SSH 仓库时调用的程序设置。

数据流:进去没有普通参数;在 macOS 上,它生成脚本来记录当前 Git SSH 命令是否由 Codex 标记过,并在快照恢复后按规则恢复或取消;在非 macOS 上,出来就是两个空字符串。

调用关系:build_proxy_env_exports 会调用它,把 Git SSH 的特殊保护并入整体代理环境保护脚本。它是代理变量处理中平台相关的那一小块。

调用图:被 1 处调用(build_proxy_env_exports);外部调用 2 个(new, format!)。

build_override_exports_for_keys371–400 ↗
fn build_override_exports_for_keys(variable_prefix: &str, keys: &[&str]) -> (String, String)

作用:根据一组环境变量名,生成通用的保存和恢复脚本。它是环境变量“防止被快照冲掉”的模板工厂。

数据流:进去的是一个临时变量名前缀和一组环境变量名;如果列表为空,返回两段空文本;否则为每个变量生成两个临时变量:一个记它原来是否存在,一个记它原来的值;再生成恢复脚本,原来存在就 export 回去,原来不存在就 unset;出来是捕获脚本和恢复脚本。

调用关系:build_override_exports 用它处理用户显式覆盖变量,build_proxy_env_exports 用它处理代理和证书变量。它提供共用机制,上层决定具体保护哪些变量。

调用图:被 2 处调用(build_override_exports, build_proxy_env_exports);外部调用 1 个(new)。

join_shell_blocks402–408 ↗
fn join_shell_blocks(blocks: impl IntoIterator<Item = String>) -> String

作用:把多段 shell 脚本文本拼在一起,并自动跳过空段。这样生成出来的脚本不会有一堆无意义的空内容。

数据流:进去的是若干字符串;它逐个取出,过滤掉空字符串,再用换行符连接;出来是一整段脚本文本。

调用关系:build_proxy_env_exports 和 maybe_wrap_shell_lc_with_snapshot 都会用它拼装最终脚本。它是脚本文本组装时的小工具。

调用图:被 2 处调用(build_proxy_env_exports, maybe_wrap_shell_lc_with_snapshot);外部调用 1 个(into_iter)。

is_valid_shell_variable_name410–419 ↗
fn is_valid_shell_variable_name(name: &str) -> bool

作用:判断一个名字能不能安全地当作 shell 环境变量名。这样可以避免把奇怪名字写进脚本后造成语法错误。

数据流:进去的是一个字符串;它检查第一个字符必须是字母或下划线,后面只能是字母、数字或下划线;空字符串或不符合规则就返回 false,符合就返回 true。

调用关系:它服务于构造环境变量恢复脚本的流程,用来筛掉不能写进 shell export/unset 语句的名字。虽然它不调用别的项目函数,但它保护了后续脚本文本的安全性和可执行性。

shell_single_quote421–423 ↗
fn shell_single_quote(input: &str) -> String

作用:把一段文本转成可以安全放进单引号 shell 字符串里的形式。这样路径或脚本里即使有单引号,也不会把外层命令弄坏。

数据流:进去的是任意字符串;它把里面的单引号替换成 shell 里常用的安全拼接写法;出来是转义后的字符串,可被外层再包进单引号使用。

调用关系:maybe_wrap_shell_lc_with_snapshot 在拼装包装脚本时会调用它,保护原 shell 路径、原脚本、快照路径和后续参数。它是防止脚本文本被特殊字符打断的小保险。

调用图:被 1 处调用(maybe_wrap_shell_lc_with_snapshot)。

disable_powershell_profile_tests::inserts_no_profile_for_elevated_windows_sandbox431–454 ↗
fn inserts_no_profile_for_elevated_windows_sandbox()

作用:测试在提权 Windows 沙箱里运行普通 PowerShell 命令时,会自动插入 -NoProfile。

数据流:进去的是测试里构造的 powershell.exe -Command 命令;它调用 disable_powershell_profile_for_elevated_windows_sandbox;出来的结果应当是在程序名后多了 -NoProfile,测试用断言确认这一点。

调用关系:它是 disable_powershell_profile_for_elevated_windows_sandbox 的正向测试之一,证明真正需要保护的场景会被改写。

调用图:调用 1 个内部函数(disable_powershell_profile_for_elevated_windows_sandbox);外部调用 2 个(assert_eq!, vec!)。

disable_powershell_profile_tests::inserts_no_profile_before_encoded_command457–480 ↗
fn inserts_no_profile_before_encoded_command()

作用:测试 PowerShell 使用 -EncodedCommand 时,-NoProfile 也会插到正确位置。

数据流:进去的是 powershell.exe -EncodedCommand 加编码脚本的测试命令;它调用目标函数;出来应当变成 powershell.exe -NoProfile -EncodedCommand ...,测试断言顺序正确。

调用关系:它补充覆盖另一种常见 PowerShell 调用形式,确保目标函数不会只适配 -Command。

调用图:调用 1 个内部函数(disable_powershell_profile_for_elevated_windows_sandbox);外部调用 2 个(assert_eq!, vec!)。

disable_powershell_profile_tests::preserves_existing_no_profile483–499 ↗
fn preserves_existing_no_profile()

作用:测试命令里已经有 -NoProfile 时,不会重复插入一次。

数据流:进去的是已经带 -NoProfile 的 pwsh.exe 命令;它调用目标函数;出来应当和原命令完全一样,测试用断言确认没有重复改动。

调用关系:它保护 disable_powershell_profile_for_elevated_windows_sandbox 的幂等性。幂等性就是说同样处理多次,结果不会越改越多。

调用图:调用 1 个内部函数(disable_powershell_profile_for_elevated_windows_sandbox);外部调用 2 个(assert_eq!, vec!)。

disable_powershell_profile_tests::leaves_legacy_restricted_token_backend_alone502–517 ↗
fn leaves_legacy_restricted_token_backend_alone()

作用:测试旧的受限令牌沙箱级别不会被加 -NoProfile。也就是说,只有新的提权级别才触发这条规则。

数据流:进去的是普通 PowerShell 命令,但 Windows 沙箱级别设为 RestrictedToken;它调用目标函数;出来应当保持原样,测试断言没有插入新参数。

调用关系:它验证目标函数的条件判断不会过宽,避免影响旧后端的行为。

调用图:调用 1 个内部函数(disable_powershell_profile_for_elevated_windows_sandbox);外部调用 2 个(assert_eq!, vec!)。

disable_powershell_profile_tests::leaves_unsandboxed_attempts_alone520–535 ↗
fn leaves_unsandboxed_attempts_alone()

作用:测试没有使用 Windows 受限令牌沙箱时,PowerShell 命令不会被改写。

数据流:进去的是 PowerShell 命令,但沙箱类型是 None;它调用目标函数;出来应当等于原命令,说明非沙箱场景不受影响。

调用关系:它确保 disable_powershell_profile_for_elevated_windows_sandbox 只服务于特定沙箱场景,不会改变普通执行。

调用图:调用 1 个内部函数(disable_powershell_profile_for_elevated_windows_sandbox);外部调用 2 个(assert_eq!, vec!)。

disable_powershell_profile_tests::leaves_non_powershell_alone538–553 ↗
fn leaves_non_powershell_alone()

作用:测试非 PowerShell 的命令不会被加 PowerShell 专用参数。

数据流:进去的是 /bin/bash -lc echo ok 这样的 Bash 命令;它调用目标函数;出来应当完全不变,因为 Bash 不认识 -NoProfile 这个 PowerShell 参数。

调用关系:它防止目标函数误伤其他 shell。和其他测试一起,说明改写只发生在 PowerShell、指定沙箱、指定级别这几个条件同时满足时。

调用图:调用 1 个内部函数(disable_powershell_profile_for_elevated_windows_sandbox);外部调用 2 个(assert_eq!, vec!)。

core/src/tools/runtimes/shell/unix_escalation.rs源码 ↗
domain_logicrequest handling

可以把这个文件想成 shell 命令执行前的“安检口”。普通命令先放进沙盒(一种受限制的运行环境,防止乱读写文件或乱联网)。如果命令在运行中真正要启动某个程序,这里会拦截到这个 execve 动作(系统里“启动另一个程序”的动作),再按执行策略判断:允许、拒绝,还是需要人批准。CoreShellActionProvider 像审批员,负责查规则、触发钩子、找 Guardian 或用户确认;CoreShellCommandExecutor 像执行员,负责把最终命令按决定放进原沙盒、加强权限的沙盒,或在允许时不进沙盒运行。这个文件还专门支持 zsh fork 后端:它能让 zsh 运行时继续被监控,而不是 shell 一启动就失去后续命令的视野。

函数细节17
approval_sandbox_permissions89–103 ↗
fn approval_sandbox_permissions(
    sandbox_permissions: SandboxPermissions,
    additional_permissions_preapproved: bool,
) -> SandboxPermissions

作用:这个函数决定“审批时应该按哪种沙盒权限来描述这次请求”。如果额外权限其实已经提前批准了,它会把请求看成普通默认权限,避免重复要求用户批准同一件事。

数据流:进去的是当前沙盒权限,以及“额外权限是否已预先批准”的布尔值。它检查权限是不是“带额外权限”,如果是并且已经预批,就改成“使用默认权限”;否则原样返回。出来的是一份用于后续审批判断的沙盒权限。

调用关系:try_run_zsh_fork 和 prepare_unified_exec_zsh_fork 在创建审批策略时会调用它。它不执行命令,只是帮后面的 CoreShellActionProvider 用正确口径判断是否还需要问人。

调用图:被 2 处调用(prepare_unified_exec_zsh_fork, try_run_zsh_fork);外部调用 1 个(matches!)。

try_run_zsh_fork105–247 ↗
async fn try_run_zsh_fork(
    req: &ShellRequest,
    attempt: &SandboxAttempt<'_>,
    ctx: &ToolCtx,
    command: &[String],
) -> Result<Option<ExecToolCallOutput>, ToolError>

作用:这个函数尝试用 zsh fork 方式运行一次 shell 请求。它的作用是让 zsh 命令在沙盒中运行时,后续启动的子程序也能被拦截、审批和必要时提权。

数据流:进去的是 shell 请求、当前沙盒尝试、工具上下文和原始命令。它先确认 zsh fork 功能、zsh 路径、用户 shell 类型都合适;然后准备环境变量、沙盒命令、超时和执行策略;再启动 EscalateServer,让它边运行边拦截需要审批的子程序。出来时,如果不适合 zsh fork,就返回 None;如果运行了,就把执行结果转成工具输出,或者把超时、沙盒拒绝等情况变成错误。

调用关系:maybe_run_shell_command 会在处理 shell 命令时尝试走这里。这个函数会把审批工作交给 CoreShellActionProvider,把真正执行工作交给 CoreShellCommandExecutor,最后用 map_exec_result 把底层执行结果翻译成上层工具能懂的结果。

调用图:调用 12 个内部函数(cancel_when_either, build_sandbox_command, exec_env_for_sandbox_permissions, prepend_zsh_fork_bin_to_path, approval_sandbox_permissions, extract_shell_script, map_exec_result, env_for, managed_network_for_sandbox_permissions, sandbox_permissions_preserving_denied_reads (+2 more));被 1 处调用(maybe_run_shell_command);外部调用 7 个(clone, new, from_millis, new, clone, matches!, warn!)。

prepare_unified_exec_zsh_fork249–324 ↗
async fn prepare_unified_exec_zsh_fork(
    req: &crate::tools::runtimes::unified_exec::UnifiedExecRequest,
    _attempt: &SandboxAttempt<'_>,
    ctx: &ToolCtx,
    exec_request: ExecRequest,
    she

作用:这个函数为 unified exec 这种统一执行入口准备 zsh fork 会话。它不马上跑完整命令,而是先搭好能拦截后续 execve 的环境。

数据流:进去的是统一执行请求、上下文、已经构造好的执行请求、zsh 路径和 execve 包装器路径。它解析命令,确认目标确实是配置的 zsh;然后创建命令执行器和审批策略,启动提权会话,并把会话需要的环境变量塞回执行请求。出来的是一个 PreparedUnifiedExecZshFork,里面包含改好的执行请求和会话;如果命令不适合 zsh fork,就返回 None。

调用关系:maybe_prepare_unified_exec 在准备统一执行时会调用它。它和 try_run_zsh_fork 做的是同一类事,但更偏“提前布置现场”,让后续统一执行流程继续接手。

调用图:调用 4 个内部函数(approval_sandbox_permissions, extract_shell_script, new, unlimited);被 1 处调用(maybe_prepare_unified_exec);外部调用 7 个(clone, new, new, to_path_buf, to_string_lossy, new, warn!)。

execve_prompt_is_rejected_by_policy354–372 ↗
fn execve_prompt_is_rejected_by_policy(
    approval_policy: AskForApproval,
    decision_source: &DecisionSource,
) -> Option<&'static str>

作用:这个函数判断“本来需要询问用户”的情况,是否被当前审批设置直接禁止。比如用户配置了永不询问,那就不能弹窗问,只能拒绝。

数据流:进去的是审批策略和这次决定的来源:是命中了明确规则,还是没命中规则后的兜底判断。它按配置检查是否允许规则审批或沙盒审批。出来的是拒绝原因文字;如果允许询问,就返回空。

调用关系:CoreShellActionProvider::process_decision 在遇到 Prompt 决策时会先调用它。这样可以避免系统明明被配置成不能问人,却又弹出审批请求。

调用图:被 1 处调用(process_decision)。

CoreShellActionProvider::decision_driven_by_policy375–380 ↗
fn decision_driven_by_policy(matched_rules: &[RuleMatch], decision: Decision) -> bool

作用:这个函数判断某个允许、拒绝或询问的结论,是不是来自用户或系统写明的执行规则,而不是启发式兜底判断。

数据流:进去的是命中的规则列表和最终决策。它逐条看规则,跳过启发式规则,只要有一条真实规则给出了同样决策,就认为这个结论由策略规则驱动。出来的是 true 或 false。

调用关系:CoreShellActionProvider::determine_action 用它来区分“明确规则要求这样做”和“没有规则时系统自己猜的”。这个区别会影响后面是否需要提权,以及提权到什么级别。

调用图:外部调用 1 个(iter)。

CoreShellActionProvider::shell_request_escalation_execution382–411 ↗
fn shell_request_escalation_execution(
        sandbox_permissions: SandboxPermissions,
        permission_profile: &PermissionProfile,
        file_system_sandbox_policy: &FileSystemSandboxPolicy,

作用:这个函数把 shell 请求里的权限要求,翻译成提权系统能执行的动作。简单说,它决定如果要升级权限,是用默认沙盒、额外权限沙盒,还是完全不进沙盒。

数据流:进去的是请求的沙盒权限、当前权限配置、文件系统沙盒策略,以及可能的额外权限。它根据请求类型判断:默认权限就保持默认;要求提权时,如果策略允许无沙盒运行就选无沙盒,否则退回默认;带额外权限时,如果确实有额外权限,就生成对应的权限执行方案。出来的是 EscalationExecution,也就是后续执行员能照着做的执行方式。

调用关系:CoreShellActionProvider::determine_action 在没命中明确规则、需要按 shell 请求自身权限来处理时会用它。它还会调用 unsandboxed_execution_allowed,确认是否真的允许完全绕开沙盒。

调用图:调用 1 个内部函数(unsandboxed_execution_allowed)。

CoreShellActionProvider::prompt413–508 ↗
async fn prompt(
        &self,
        program: &AbsolutePathBuf,
        argv: &[String],
        workdir: &AbsolutePathBuf,
        stopwatch: &Stopwatch,
        additional_permissions: Option<Add

作用:这个函数负责真正“问一问能不能执行”。它会先给自动钩子机会,再看是否交给 Guardian,最后才退回普通用户确认。

数据流:进去的是要执行的程序、参数、工作目录、计时器和额外权限说明。它先把程序和参数拼成可展示的命令;然后暂停执行计时,先运行权限请求钩子,钩子可以直接允许或拒绝;如果配置了 Guardian,就发给 Guardian 审核;否则向当前会话里的用户发出审批请求。出来的是 PromptDecision,包含批准、拒绝、超时等结果,以及可能的 Guardian 审核编号或拒绝消息。

调用关系:CoreShellActionProvider::process_decision 在策略结果是 Prompt 时调用它。它把审批流程按“自动钩子 → Guardian → 用户”的顺序串起来,是这个文件里人机确认流程的核心入口。

调用图:调用 6 个内部函数(run_permission_request_hooks, join_program_and_argv, bash, shlex_join, pause_for, to_string_lossy);被 1 处调用(process_decision);外部调用 5 个(new_v4, review_approval_request, routes_approval_to_guardian, clone, vec!)。

CoreShellActionProvider::process_decision511–594 ↗
async fn process_decision(
        &self,
        decision: Decision,
        needs_escalation: bool,
        program: &AbsolutePathBuf,
        argv: &[String],
        workdir: &AbsolutePathBuf,

作用:这个函数把策略给出的抽象结论,变成提权系统能执行的具体动作:运行、提权运行,或者拒绝。

数据流:进去的是策略决策、是否需要提权、程序和参数、工作目录、要提示的权限、准备好的提权方式,以及决策来源。它先处理 Forbidden 直接拒绝;遇到 Prompt 时先确认配置是否允许询问,再调用 prompt 拿审批结果;遇到 Allow 时根据是否需要提权选择普通运行或提权运行。出来的是 EscalationDecision,告诉后续流程下一步该干什么。

调用关系:CoreShellActionProvider::determine_action 会把策略评估结果交给它收尾。它会调用 execve_prompt_is_rejected_by_policy 做配置检查,也会在需要时调用 prompt,并把 Guardian 拒绝或超时消息转成用户能看到的错误。

调用图:调用 5 个内部函数(prompt, execve_prompt_is_rejected_by_policy, deny, escalate, run);被 1 处调用(determine_action);外部调用 4 个(guardian_rejection_message, guardian_timeout_message, clone, debug!)。

CoreShellActionProvider::determine_action671–680 ↗
fn determine_action(
        &'a self,
        program: &'a AbsolutePathBuf,
        argv: &'a [String],
        workdir: &'a AbsolutePathBuf,
    ) -> EscalationPolicyFuture<'a>

作用:这个函数是每次拦截到子程序启动时的总裁判。它看命令、查策略、判断沙盒限制,然后决定这次启动能不能继续,以及要不要换一种权限执行。

数据流:进去的是被拦截到的程序路径、参数和工作目录。它读取当前执行策略,调用 evaluate_intercepted_exec_policy 得到允许、拒绝或询问的判断;再看这个判断是不是明确规则导致、当前沙盒是否允许无沙盒执行、请求本身想要什么权限;最后调用 process_decision 输出最终动作。出来的是一个 EscalationDecision。

调用关系:EscalateServer 通过 EscalationPolicy 接口在每次 execve 拦截时会走到这里。它把规则评估、权限判断和用户审批串成一条线,是 CoreShellActionProvider 的主流程。

调用图:调用 3 个内部函数(process_decision, evaluate_intercepted_exec_policy, unsandboxed_execution_allowed);外部调用 5 个(pin, decision_driven_by_policy, shell_request_escalation_execution, clone, debug!)。

evaluate_intercepted_exec_policy683–734 ↗
fn evaluate_intercepted_exec_policy(
    policy: &Policy,
    program: &AbsolutePathBuf,
    argv: &[String],
    context: InterceptedExecPolicyContext,
) -> Evaluation

作用:这个函数把一次被拦截的程序启动,拿去和执行策略做匹配。策略可以理解成一张“哪些命令允许、哪些要问、哪些禁止”的清单。

数据流:进去的是策略对象、程序路径、参数,以及审批策略、权限配置、Windows 沙盒级别等上下文。它根据配置决定是只用真实程序路径评估,还是尝试从 shell 脚本文本里拆出候选命令;然后给没命中规则的命令准备兜底判断;最后调用策略检查多个候选命令。出来的是 Evaluation,包含最终决策和命中的规则。

调用关系:CoreShellActionProvider::determine_action 调用它来获得最初的政策判断。它可能会调用 commands_for_intercepted_exec_policy 生成候选命令,再把这些候选命令交给策略引擎检查。

调用图:调用 1 个内部函数(commands_for_intercepted_exec_policy);被 1 处调用(determine_action);外部调用 2 个(check_multiple_with_options, vec!)。

commands_for_intercepted_exec_policy750–778 ↗
fn commands_for_intercepted_exec_policy(
    program: &AbsolutePathBuf,
    argv: &[String],
) -> CandidateCommands

作用:这个函数尝试从 shell 的 -c-lc 脚本文本里拆出真正要执行的命令,方便策略检查得更细。

数据流:进去的是 shell 程序路径和参数列表。它如果看到类似 shell -c '命令' 的形式,就尝试解析里面的简单命令;如果能解析出多个普通命令,就返回这些命令;如果只能识别单个命令前缀,就标记为用了复杂解析;解析不了时,就退回到“程序加参数”的原始形式。出来的是候选命令列表和是否用了复杂解析的标记。

调用关系:evaluate_intercepted_exec_policy 在启用 shell 包装解析时会调用它。当前文件默认关闭这个能力,因为直接拦截 execve 看到的真实可执行文件路径更可靠。

调用图:调用 3 个内部函数(parse_shell_lc_plain_commands, parse_shell_lc_single_command_prefix, to_string_lossy);被 1 处调用(evaluate_intercepted_exec_policy);外部调用 1 个(vec!)。

CoreShellCommandExecutor::run837–885 ↗
async fn run(
        &self,
        env_overlay: HashMap<String, String>,
        cancel_rx: CancellationToken,
        after_spawn: Option<Box<dyn FnOnce() + Send>>,
    ) -> anyhow::Result<ExecResu

作用:这个函数真正启动最外层 shell 命令,并把它放进预先选好的沙盒环境中。它还会把提权会话需要的少量环境变量加进去。

数据流:进去的是来自提权会话的环境变量覆盖、取消信号和启动后回调。它复制基础环境,只挑出 CODEX_ESCALATE_SOCKETEXEC_WRAPPER 这两个变量合进去;然后构造 ExecRequest,调用底层沙盒执行函数运行命令。出来的是 ExecResult,里面有退出码、标准输出、标准错误、合并输出、耗时和是否超时。

调用关系:EscalateServer 通过 ShellCommandExecutor 接口调用它来跑 shell 主进程。它不负责审批,审批由 CoreShellActionProvider 做;它只负责按已有配置把命令实际送去执行。

调用图:调用 1 个内部函数(execute_exec_request_with_after_spawn);外部调用 5 个(pin, Cancellation, clone, clone, clone)。

CoreShellCommandExecutor::prepare_escalated_exec887–945 ↗
async fn prepare_escalated_exec(
        &self,
        program: &AbsolutePathBuf,
        argv: &[String],
        workdir: &AbsolutePathBuf,
        env: HashMap<String, String>,
        execution:

作用:这个函数为一次被拦截到、需要特殊处理的子程序启动,准备新的执行方式。它会根据提权决定,生成可以直接执行的命令、目录、环境和 argv0。

数据流:进去的是真实程序路径、参数、工作目录、环境变量和提权执行类型。它先把程序和参数拼成命令;如果是无沙盒执行,就准备一个按提权权限整理过的环境;如果是默认沙盒或带权限沙盒,就调用 prepare_sandboxed_exec 重新生成沙盒包装后的命令。出来的是 PreparedExec,告诉提权系统实际应该启动什么。

调用关系:EscalateServer 在某个子程序被批准提权或需要改用沙盒权限时,会通过 ShellCommandExecutor 调用它。它把高层的 EscalationExecution 翻译成低层能运行的 PreparedExec。

调用图:调用 4 个内部函数(exec_env_for_sandbox_permissions, prepare_sandboxed_exec, join_program_and_argv, to_path_buf);外部调用 2 个(pin, anyhow!)。

CoreShellCommandExecutor::prepare_sandboxed_exec948–1012 ↗
fn prepare_sandboxed_exec(
        &self,
        params: PrepareSandboxedExecParams<'_>,
    ) -> anyhow::Result<PreparedExec>

作用:这个函数把一个普通命令包装成“可在沙盒里执行”的命令。它负责选择合适的沙盒后端,并把文件、网络、工作目录等限制写进执行请求里。

数据流:进去的是命令、工作目录、环境、权限配置和可选额外权限。它从权限配置生成文件系统和网络策略,选择一个合适沙盒,把命令转成 SandboxCommand,再让 SandboxManager 做转换;如果有受管网络代理,还会把网络代理需要的变量写进环境。出来的是 PreparedExec,包含沙盒包装后的命令、目录、环境和 arg0。

调用关系:CoreShellCommandExecutor::prepare_escalated_exec 在需要默认沙盒、额外权限沙盒或完整权限配置沙盒时调用它。它是“把权限决定落地成沙盒启动命令”的地方。

调用图:调用 3 个内部函数(from_sandbox_exec_request, new, from_abs_path);被 1 处调用(prepare_escalated_exec)。

extract_shell_script1022–1045 ↗
fn extract_shell_script(command: &[String]) -> Result<ParsedShellCommand, ToolError>

作用:这个函数从一串可能被包装过的命令参数里,找出真正传给 shell 的脚本文本。zsh fork 必须知道脚本内容,才能让 zsh 以正确方式执行。

数据流:进去的是命令参数数组。它不是只看开头,而是在整个数组里寻找连续三项:程序、-c-lc、脚本;找到 -c 就标记为非登录 shell,找到 -lc 就标记为登录 shell。出来的是 ParsedShellCommand;如果找不到这种结构,就返回拒绝错误。

调用关系:try_run_zsh_fork 和 prepare_unified_exec_zsh_fork 在启动 zsh fork 前都会调用它。它确保后面的 EscalateServer 拿到的是 shell 脚本,而不是一堆外层沙盒包装参数。

调用图:被 2 处调用(prepare_unified_exec_zsh_fork, try_run_zsh_fork);外部调用 1 个(Rejected)。

map_exec_result1047–1074 ↗
fn map_exec_result(
    sandbox: SandboxType,
    result: ExecResult,
) -> Result<ExecToolCallOutput, ToolError>

作用:这个函数把 zsh fork 执行器返回的结果,翻译成 shell 工具统一使用的输出格式。它还会把超时和疑似沙盒拒绝识别成明确错误。

数据流:进去的是沙盒类型和 ExecResult。它先把退出码、stdout、stderr、合并输出、耗时和超时状态装进 ExecToolCallOutput;如果结果显示超时,就返回沙盒超时错误;如果输出看起来像被沙盒拦住了,就返回沙盒拒绝错误;否则返回正常输出。

调用关系:try_run_zsh_fork 在 EscalateServer 执行完后调用它。它是底层执行结果和上层工具协议之间的翻译层。

调用图:调用 2 个内部函数(is_likely_sandbox_denied, new);被 1 处调用(try_run_zsh_fork);外部调用 3 个(new, Codex, Sandbox)。

join_program_and_argv1082–1086 ↗
fn join_program_and_argv(program: &AbsolutePathBuf, argv: &[String]) -> Vec<String>

作用:这个函数把被拦截到的程序路径和 argv 参数列表,合成一条适合展示和策略检查的命令数组。

数据流:进去的是标准化后的程序绝对路径和 argv。因为 argv 里通常已经有 argv[0],它会用标准化程序路径替换原来的 argv[0],再接上后面的参数。出来的是一个新的命令数组,不会把程序名重复放两次。

调用关系:CoreShellActionProvider::prompt 用它生成展示给用户或钩子的命令;CoreShellCommandExecutor::prepare_escalated_exec 用它生成实际准备执行的命令。它保证审批看到的命令和执行准备用的命令格式一致。

调用图:调用 1 个内部函数(to_string_lossy);被 2 处调用(prompt, prepare_escalated_exec);外部调用 1 个(once)。