Codex 系统手册

审批策略和请求决策引擎

stage-14.1.114 个文件

这一阶段是系统的“安全门卫”,主要在工具真正执行命令、改文件、联网或放宽沙箱限制前做决定:能直接放行、必须拒绝,还是先问用户。新版 execpolicy 负责读入人写的策略、整理成规则,并按命令或网络请求逐条匹配。core 里的几个文件把这些判断接到实际工具上:命令看 exec_policy,改补丁看 safety,联网请求由 network_policy_decision 翻译成用户能懂的审批。sandboxing 则统一决定要不要审批、要不要关进“隔离间”运行。legacy 部分是旧版同类门卫,继续解析旧策略、检查程序名和参数,保证老规则还能用。

本阶段的文件14

执行策略模型和解析

这些文件定义现代执行策略 API、规则模型、解析器和运行时评估,用于决定命令和网络访问。

execpolicy/src/lib.rs源码 ↗
othercross-cutting

可以把这个文件理解成一本书的目录加前台接待。真正干活的代码放在 amend、parser、policy、rule 等模块里,而这里负责告诉 Rust 编译器:这些模块属于这个库;同时挑出外面最常用的类型和函数重新公开出来。这样,别的代码想用执行策略功能时,可以直接从 execpolicy 拿到 Policy、Rule、PolicyParser、Decision 这些名字,而不用知道它们具体藏在哪个子文件里。它本身不解析规则、不做判断,也不读写文件;它的重要性在于稳定“对外接口”。如果没有它,库里的零件虽然还在,但外部使用者会更难引用,项目内部结构一改,调用方也更容易跟着坏掉。

execpolicy/src/rule.rs源码 ↗
domain_logicconfig load, request handling, policy validation

可以把这个文件理解成一套“门禁规则说明书”。命令进来时,系统会把命令拆成一个个词,比如程序名和参数,然后用这里的前缀规则去比对:前几个词对上了,就算命中。规则命中后会产生一个 RuleMatch,里面带着决定结果,比如允许、拒绝或询问。这里还支持“一个位置可以有多个可选词”,像检查口令时允许几种写法。文件里也定义了网络规则的协议和主机名清洗方法,避免把带路径、通配符或乱写端口的网络地址当成有效规则。最后,它还提供了正例和反例校验:写策略的人可以给几个“应该匹配”和“不应该匹配”的例子,系统会检查规则是不是写歪了。

函数细节13
PatternToken::matches22–27 ↗
fn matches(&self, token: &str) -> bool

作用:判断命令里的某一个词,是否符合规则里这一格的要求。规则可以要求一个固定词,也可以允许几个备选词中的任意一个。

数据流:输入是规则中的一个词要求和命令里的实际词。它先看规则是“只能等于这个词”,还是“可以等于这些词之一”,再逐个比较。输出是 true 或 false,表示这一格有没有对上,不改动任何数据。

调用关系:它是更大匹配流程里的小齿轮。PrefixPattern::matches_prefix 在检查整段命令前缀时,会对每个后续词调用它;只要有一个词没对上,整条前缀规则就不算命中。

PatternToken::alternatives29–34 ↗
fn alternatives(&self) -> &[String]

作用:把这个规则词能接受的所有写法拿出来。哪怕它本来只有一个固定词,也会用“列表”的形式交出去,方便外面统一处理。

数据流:输入是一个 PatternToken。它如果是单个固定词,就把这个词临时看成一个只有一项的列表;如果本来就是多个备选词,就直接返回这些备选词。输出是字符串切片列表,不复制原始内容。

调用关系:它主要服务于需要展示、检查或展开规则内容的代码。这里会用到 from_ref 这个标准工具,把单个值安全地包装成一个只读的一项列表。

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

PrefixPattern::matches_prefix46–59 ↗
fn matches_prefix(&self, cmd: &[String]) -> Option<Vec<String>>

作用:判断一整条命令的开头,是否符合某个“前缀模式”。比如规则写的是 git commit,那么命令 git commit -m hi 的开头就能匹配。

数据流:输入是一条已经拆成词的命令。它先确认命令够长,并且第一个词等于规则规定的程序名;然后逐个检查后面的规则词。全部对上时,输出实际命中的那一段命令前缀;任何一步不对,就输出 None,表示没命中。

调用关系:PrefixRule::matches 会把真正的命令交给它做细节比对。它内部又依靠 PatternToken::matches 检查每一个位置上的词是不是允许的写法。

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

RuleMatch::decision85–90 ↗
fn decision(&self) -> Decision

作用:从一次规则命中结果里取出最终决定。调用者不需要关心这是前缀规则命中,还是启发式规则命中,只想知道结论是什么。

数据流:输入是一个 RuleMatch。它查看命中结果的种类,并把里面保存的 Decision 取出来返回。它不改变命中结果,也不做重新判断。

调用关系:它是读取命中结果的便捷入口。策略匹配完成后,后续执行流程可以用它统一拿到允许、拒绝或询问之类的决定,而不用分别处理不同的命中类型。

RuleMatch::with_resolved_program92–107 ↗
fn with_resolved_program(self, resolved_program: &AbsolutePathBuf) -> Self

作用:给前缀规则的命中结果补上“程序最终解析到的绝对路径”。这能让后续提示或记录更准确,比如知道运行的不是随便一个 git,而是 /usr/bin/git。

数据流:输入是一条已有的命中结果,以及一个已经解析好的程序路径。它如果看到这是 PrefixRuleMatch,就复制这个路径并放进 resolved_program 字段,同时保留原来的命中前缀、决定和理由;如果不是这种命中,就原样返回。输出是更新后的 RuleMatch。

调用关系:它通常出现在命令已经匹配规则、并且外层策略代码解析出了真实可执行文件位置之后。它会调用 clone 复制路径,避免外部路径对象被借走或提前失效。

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

NetworkRuleProtocol::parse126–136 ↗
fn parse(raw: &str) -> Result<Self>

作用:把配置文件里写的网络协议文字,转换成程序内部能安全使用的协议枚举。这样可以早早发现拼错的协议名。

数据流:输入是一个字符串,比如 http、https、socks5_tcp。它和允许的协议名逐一比较,也接受少量兼容写法,比如 https_connect。匹配成功就输出对应的 NetworkRuleProtocol;不认识就输出 InvalidRule 错误,并说明允许哪些值。

调用关系:它在读取或追加网络规则时使用,负责把外部文本变成内部标准值。遇到坏输入时,它通过 Error::InvalidRule 把问题交给上层配置流程处理。

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

NetworkRuleProtocol::as_policy_string138–145 ↗
fn as_policy_string(self) -> &'static str

作用:把内部的网络协议值转回策略文件里使用的标准文字。这样写回配置时不会出现同一个协议多种写法混在一起。

数据流:输入是一个 NetworkRuleProtocol。它根据具体枚举值返回固定字符串,比如 Http 返回 http,Socks5Udp 返回 socks5_udp。输出是静态字符串,不分配新内存,也不改动对象。

调用关系:blocking_append_network_rule 在把网络规则追加回策略时会调用它。它和 NetworkRuleProtocol::parse 正好一进一出:parse 负责读进来,as_policy_string 负责用标准格式写出去。

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

normalize_network_rule_host156–212 ↗
fn normalize_network_rule_host(raw: &str) -> Result<String>

作用:清洗并检查网络规则里的主机名,确保它只是一个明确的主机或 IP,而不是一整段网址、路径,或带通配符的模糊规则。

数据流:输入是用户写的主机字符串。它先去掉首尾空白,拒绝空值、带 http:// 这类 scheme、路径、查询参数和片段;然后处理 IPv6 方括号和可选端口,也会去掉普通 host:port 里的端口;最后去掉末尾点、转小写,并拒绝通配符和空白字符。输出是规范化后的主机名,或一个 InvalidRule 错误。

调用关系:blocking_append_network_rule 和 add_network_rule 在新增网络规则前会调用它。它像门口的安检员,先把不合格的地址挡住,避免后面的匹配逻辑面对含糊或危险的规则。

调用图:被 2 处调用(blocking_append_network_rule, add_network_rule);外部调用 2 个(InvalidRule, format!)。

PrefixRule::program225–227 ↗
fn program(&self) -> &str

作用:取出这条前缀规则针对的程序名,也就是命令的第一个词。策略系统可以用它快速把规则按程序分类。

数据流:输入是一个 PrefixRule。它直接读取 pattern.first,也就是规则固定的第一个命令词,并以字符串形式返回。它不复制数据,也不修改规则。

调用关系:它实现了 Rule 这个统一接口。外层 Policy 可以把不同规则都当作 Rule 使用,并通过 program 先找到可能相关的规则,减少无关比对。

PrefixRule::matches229–238 ↗
fn matches(&self, cmd: &[String]) -> Option<RuleMatch>

作用:判断一条具体命令是否命中这条前缀规则;如果命中,就生成一份带决定结果的命中记录。

数据流:输入是一条拆成词的命令。它把命令交给 PrefixPattern::matches_prefix;如果没有匹配,输出 None。匹配成功时,它把命中的前缀、规则决定、空的 resolved_program,以及规则理由一起包成 RuleMatch::PrefixRuleMatch 返回。

调用关系:这是 PrefixRule 作为 Rule 接口的核心动作。Policy 在检查命令时会调用各条规则的 matches;这里再把细节交给 PrefixPattern::matches_prefix,自己负责把“匹配成功”变成可供后续使用的 RuleMatch。

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

PrefixRule::as_any240–242 ↗
fn as_any(&self) -> &dyn Any

作用:把这条规则暴露成 Any,方便外层在必要时把通用 Rule 再识别回具体的 PrefixRule 类型。

数据流:输入是一个 PrefixRule 的引用。它把自身作为 dyn Any 返回,数据本身不移动、不复制、不修改。输出是一个可用于类型识别的通用引用。

调用关系:它是 Rule 接口的一部分。平时系统只需要把规则当成统一的 Rule;只有调试、校验或特殊处理时,外层才可能通过 as_any 做类型判断。

validate_match_examples246–279 ↗
fn validate_match_examples(
    policy: &Policy,
    rules: &[RuleRef],
    matches: &[Vec<String>],
) -> Result<()>

作用:检查策略作者写的“这些命令应该匹配”的例子是否真的能匹配上。它能提前发现规则写错、写漏,避免策略看起来有用但实际不起作用。

数据流:输入是完整策略、这批规则,以及若干正例命令。它为每个例子开启“解析宿主机可执行文件”的匹配选项,然后调用 policy.matches_for_command_with_options 看有没有任何命中。没命中的例子会被用 try_join 渲染成可读命令行并收集起来。全部命中则返回 Ok;只要有未命中,就返回 ExampleDidNotMatch 错误,里面带上规则和失败例子。

调用关系:validate_pending_examples_from 会在策略校验阶段调用它。它不直接逐条手写匹配逻辑,而是走 Policy 的正式匹配入口,这样校验结果和真实运行时保持一致。

调用图:被 1 处调用(validate_pending_examples_from);外部调用 4 个(iter, new, matches_for_command_with_options, try_join)。

validate_not_match_examples282–306 ↗
fn validate_not_match_examples(
    policy: &Policy,
    _rules: &[RuleRef],
    not_matches: &[Vec<String>],
) -> Result<()>

作用:检查策略作者写的“这些命令不应该匹配”的反例是否真的不会匹配。它用来防止规则写得太宽,把不该放进来的命令也放进来了。

数据流:输入是完整策略、规则列表和若干反例命令。它对每个反例调用 policy.matches_for_command_with_options 做正式匹配;如果没有命中,就继续检查下一个。只要发现某个反例命中了第一条规则,它就把规则和命令行格式化成人能读懂的文字,并返回 ExampleDidMatch 错误。全部安全则返回 Ok。

调用关系:validate_pending_examples_from 会在策略校验阶段调用它,和 validate_match_examples 一正一反配合。它依赖 Policy 的真实匹配流程,而不是另写一套判断,避免测试通过但运行时表现不同。

调用图:被 1 处调用(validate_pending_examples_from);外部调用 3 个(matches_for_command_with_options, format!, try_join)。

execpolicy/src/parser.rs源码 ↗
domain_logicconfig load / startup

可以把这个文件想成“策略说明书的翻译员”。用户在策略文件里写 prefix_rule、network_rule、host_executable 这些命令,它先用 Starlark(一种像 Python 的配置脚本语言)解析并执行这些命令;执行时不是真的去运行系统命令,而是把规则收集到 PolicyBuilder 里。收集完以后,它会检查示例:哪些命令应该匹配,哪些不应该匹配,避免规则写错还没发现。它还会把错误位置补上文件名、行号、列号,让报错能指到策略文件里的具体地方。最后 PolicyParser::build 会把临时收集器变成正式的 Policy,供后面的执行拦截和网络判断使用。

函数细节24
PolicyParser::default43–45 ↗
fn default() -> Self

作用:提供一个默认的策略解析器。别人只想要“新建一个能用的解析器”时,可以直接用默认值,不必关心里面怎么初始化。

数据流:进去没有额外输入 → 它调用 PolicyParser::new 创建内部带空规则收集器的解析器 → 出来一个全新的 PolicyParser。

调用关系:它是标准的默认构造入口,实际工作交给 PolicyParser::new。这样外部代码用 Default 机制创建解析器时,也会走同一套初始化流程。

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

PolicyParser::new49–53 ↗
fn new() -> Self

作用:创建一个空的策略解析器,里面准备好一个 PolicyBuilder 用来暂存后续读到的规则。

数据流:进去没有策略内容 → 它新建 PolicyBuilder,并放进 RefCell(一种运行时借用检查盒子,方便解析脚本时临时修改里面的数据)→ 出来一个可以开始 parse 的 PolicyParser。

调用关系:这是解析流程的起点。load_exec_policy 和大量测试都会先调用它,之后再调用 PolicyParser::parse 读策略文本,最后调用 PolicyParser::build 得到正式策略。

调用图:调用 1 个内部函数(new);被 40 处调用(load_exec_policy, heuristics_apply_when_other_commands_match_policy, mixed_rule_and_sandbox_prompt_prioritizes_rule_for_rejection_decision, mixed_rule_and_sandbox_prompt_rejects_when_granular_rules_are_disabled, policy_from_src, denied_reads_keep_granular_sandbox_rejection_for_escalation, denied_reads_keep_prefix_rule_allow_inside_sandbox, evaluate_intercepted_exec_policy_matches_inner_shell_commands_when_enabled, evaluate_intercepted_exec_policy_uses_wrapper_command_when_shell_wrapper_parsing_disabled, intercepted_exec_policy_rejects_disallowed_host_executable_mapping (+15 more));外部调用 1 个(new)。

PolicyParser::parse57–79 ↗
fn parse(&mut self, policy_identifier: &str, policy_file_contents: &str) -> Result<()>

作用:读取并执行一段策略文件内容,把里面声明的规则加入当前解析器。它还会给解析错误和示例校验错误带上策略文件标识,方便定位问题。

数据流:进去是策略文件名或标识、策略文本 → 它把文本解析成 Starlark 语法树,注册本文件定义的 prefix_rule、network_rule、host_executable 等内置命令,然后执行这段策略;执行时规则会写入内部 PolicyBuilder;最后只校验这次新增的示例 → 成功时不返回新对象,只改变解析器内部状态;失败时返回带原因的错误。

调用关系:它是 PolicyParser 的核心步骤。它会调用 Starlark 的解析和执行能力,并通过 policy_builtins 暴露给策略文件使用;策略文件里的内置命令再反过来写入 PolicyBuilder。

调用图:外部调用 3 个(parse, standard, with_temp_heap)。

PolicyParser::build81–83 ↗
fn build(self) -> crate::policy::Policy

作用:把已经解析和校验过的临时规则,打包成运行时使用的正式 Policy。

数据流:进去是整个 PolicyParser 自身 → 它取出内部的 PolicyBuilder,并调用它的 build → 出来一个 crate::policy::Policy;解析器本身被消耗掉,不能再继续追加规则。

调用关系:它通常在所有 parse 调用结束后使用,是“读配置”到“运行时策略”的交接点。真正组装 Policy 的细节交给 PolicyBuilder::build。

PolicyBuilder::new95–102 ↗
fn new() -> Self

作用:创建一个空的规则收集器。它负责先把策略文件里读到的各种规则分门别类放好。

数据流:进去没有输入 → 它创建空的程序规则表、空的网络规则列表、空的宿主机可执行文件表、空的待校验示例列表 → 出来一个干净的 PolicyBuilder。

调用关系:PolicyParser::new 会用它准备内部状态。后续 prefix_rule、network_rule、host_executable 这些策略命令都会往这个收集器里加东西。

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

PolicyBuilder::add_rule104–107 ↗
fn add_rule(&mut self, rule: RuleRef)

作用:加入一条“某个程序如何匹配命令”的前缀规则。这样运行时看到某个程序启动时,就能快速找到相关规则。

数据流:进去是一条 RuleRef(共享引用形式的规则)→ 它读取规则对应的 program,也就是规则适用的程序名,并把规则插入按程序名分组的表里 → 之后内部 rules_by_program 多了一条记录。

调用关系:策略文件调用 prefix_rule 后,会生成一条或多条 PrefixRule,然后通过这个函数加入收集器。最终 PolicyBuilder::build 会把这些规则交给正式 Policy。

调用图:外部调用 2 个(insert, program)。

PolicyBuilder::add_network_rule109–111 ↗
fn add_network_rule(&mut self, rule: NetworkRule)

作用:加入一条网络访问规则,比如某个主机、协议应该允许还是拒绝。

数据流:进去是一条 NetworkRule → 它把这条规则追加到内部 network_rules 列表末尾 → 之后构建出的 Policy 会包含这条网络判断规则。

调用关系:策略文件里的 network_rule 内置命令解析好主机、协议和决定后,会调用它保存结果。PolicyBuilder::build 会把这些网络规则一起交给 Policy。

PolicyBuilder::add_host_executable113–115 ↗
fn add_host_executable(&mut self, name: String, paths: Vec<AbsolutePathBuf>)

作用:登记一个宿主机上的可执行程序名字,以及这个名字允许对应到哪些绝对路径。

数据流:进去是程序名和一组绝对路径 → 它把路径列表转成共享数组,并按程序名放进 host_executables_by_name 表 → 后续策略匹配时可以用这张表识别宿主机可执行文件。

调用关系:策略文件里的 host_executable 命令会先检查名字和路径是否合法,再调用这个函数保存。示例校验和最终 Policy 都会用到这份映射。

PolicyBuilder::add_pending_example_validation117–131 ↗
fn add_pending_example_validation(
        &mut self,
        rules: Vec<RuleRef>,
        matches: Vec<Vec<String>>,
        not_matches: Vec<Vec<String>>,
        location: Option<ErrorLocation>,

作用:把规则附带的示例先存起来,等策略文件执行完再统一检查。这样可以在规则全部收集好后,再判断示例是否真的匹配。

数据流:进去是一组规则、应该匹配的命令示例、不应该匹配的命令示例,以及可选的源码位置 → 它把这些信息包装成 PendingExampleValidation 放入列表 → 之后 validate_pending_examples_from 会逐条检查。

调用关系:prefix_rule 解析完 match 和 not_match 参数后会用它登记校验任务。它本身不立刻判断对错,而是把任务排队,等 PolicyParser::parse 执行完本次策略后再处理。

PolicyBuilder::validate_pending_examples_from133–152 ↗
fn validate_pending_examples_from(&self, start: usize) -> Result<()>

作用:检查从某个位置之后新增的示例,确认策略作者写的“应匹配”和“不应匹配”例子真的符合规则。

数据流:进去是一个起始下标 → 它只看 pending_example_validations 中从这个下标开始的任务;每个任务都会临时组装一个只含相关规则的 Policy,再先检查 not_match 示例不会匹配,后检查 match 示例会匹配;如果失败,就把记录的源码位置贴到错误上 → 成功返回空结果,失败返回带位置的错误。

调用关系:PolicyParser::parse 在执行完一段策略后调用它,用来只校验本次 parse 新增的示例。它会调用 validate_not_match_examples、validate_match_examples,并在出错时通过 attach_validation_location 补充位置。

调用图:调用 2 个内部函数(validate_match_examples, validate_not_match_examples);外部调用 3 个(new, new, from_parts)。

PolicyBuilder::build154–160 ↗
fn build(self) -> crate::policy::Policy

作用:把收集器里的所有执行规则、网络规则和宿主机可执行文件映射,变成正式 Policy。

数据流:进去是 PolicyBuilder 自身 → 它把 rules_by_program、network_rules、host_executables_by_name 原样交给 Policy::from_parts → 出来一个可供运行时查询的 Policy。

调用关系:PolicyParser::build 会调用它。它是解析阶段的收尾,把临时收集的数据交给真正负责判断的策略对象。

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

parse_pattern171–182 ↗
fn parse_pattern(pattern: UnpackList<Value<'v>>) -> Result<Vec<PatternToken>>

作用:把策略文件里 prefix_rule 的 pattern 参数,转换成内部能理解的匹配片段列表。它会拒绝空 pattern,因为空规则没有明确要匹配什么。

数据流:进去是 Starlark 传来的列表值 → 它逐个调用 parse_pattern_token,把字符串或字符串列表变成 PatternToken;如果结果为空就报 InvalidPattern → 出来是一组 PatternToken。

调用关系:policy_builtins 里的 prefix_rule 会用它解析用户写的命令前缀模式。它把每个元素的细节交给 parse_pattern_token。

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

parse_pattern_token184–217 ↗
fn parse_pattern_token(value: Value<'v>) -> Result<PatternToken>

作用:解析 pattern 里的单个元素。这个元素可以是一个固定字符串,也可以是一组可选字符串,表示这里有多个可能写法。

数据流:进去是一个 Starlark 值 → 如果它是字符串,就变成 PatternToken::Single;如果它是列表,就要求列表里全是字符串,空列表报错,单元素列表简化成 Single,多元素列表变成 Alts;如果既不是字符串也不是列表,就报 InvalidPattern → 出来是一个内部匹配片段。

调用关系:parse_pattern 会对 pattern 的每一项调用它。它负责把用户友好的写法翻译成 PrefixRule 后面可以使用的结构。

调用图:外部调用 6 个(from_value, unpack_str, InvalidPattern, Alts, Single, format!)。

parse_examples219–221 ↗
fn parse_examples(examples: UnpackList<Value<'v>>) -> Result<Vec<Vec<String>>>

作用:解析规则里的示例列表,也就是 match 或 not_match 参数里的多条命令例子。

数据流:进去是一组 Starlark 示例值 → 它逐个调用 parse_example,把每个例子变成字符串 token 列表 → 出来是二维字符串列表,每一行代表一条命令及其参数。

调用关系:prefix_rule 会用它处理 match 和 not_match。单条示例的字符串写法或列表写法,由 parse_example 再分发给更具体的函数。

parse_literal_absolute_path223–232 ↗
fn parse_literal_absolute_path(raw: &str) -> Result<AbsolutePathBuf>

作用:检查并解析 host_executable 里的路径,确保它真的是绝对路径。绝对路径就是从根目录开始写的完整位置,比如 /usr/bin/git。

数据流:进去是一段路径文字 → 它先用 Path 判断是不是绝对路径;不是就报 InvalidRule;是的话再转成 AbsolutePathBuf,并把格式错误包装成清楚的规则错误 → 出来是经过确认的绝对路径对象。

调用关系:host_executable 内置命令会对每个 paths 项调用它。后续还会继续检查这个路径的文件名是否和声明的可执行文件名一致。

调用图:调用 1 个内部函数(try_from);外部调用 3 个(new, InvalidRule, format!)。

validate_host_executable_name234–251 ↗
fn validate_host_executable_name(name: &str) -> Result<()>

作用:检查 host_executable 的名字是不是单纯的程序名,而不是路径或空字符串。

数据流:进去是一个名字 → 它先拒绝空名字;再检查这个名字不能包含目录层级,必须像 git 这样只是文件名本身 → 合法则返回成功,不合法则返回 InvalidRule。

调用关系:host_executable 内置命令一开始就调用它。这样后面保存的映射不会混进 /usr/bin/git 或 ../git 这类含路径含义的名字。

调用图:外部调用 3 个(new, InvalidRule, format!)。

parse_network_rule_decision253–258 ↗
fn parse_network_rule_decision(raw: &str) -> Result<Decision>

作用:解析网络规则里的决定字段,也就是这条网络访问该允许还是拒绝。它特别把字符串 deny 当作 Forbidden 处理。

数据流:进去是一段决定文字 → 如果正好是 deny,就直接转成 Decision::Forbidden;否则交给 Decision::parse 按通用规则解析 → 出来是内部 Decision,或解析失败的错误。

调用关系:network_rule 内置命令会用它处理 decision 参数。这样网络规则可以使用 deny 这个更贴近网络语境的写法,同时仍复用通用 Decision 解析。

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

error_location_from_file_span260–275 ↗
fn error_location_from_file_span(span: FileSpan) -> ErrorLocation

作用:把 Starlark 给出的源码范围,转换成本项目统一使用的错误位置格式。

数据流:进去是 FileSpan,也就是脚本语言记录的文件和字符范围 → 它解析出文件名、起止行列,并把从 0 开始的行列号改成普通人习惯的从 1 开始 → 出来是 ErrorLocation。

调用关系:prefix_rule 在登记示例校验时会读取当前调用位置,并用这个函数转换。之后如果示例校验失败,错误就能指向策略文件里的具体 rule。

调用图:外部调用 2 个(filename, resolve_span)。

attach_validation_location277–282 ↗
fn attach_validation_location(error: Error, location: Option<ErrorLocation>) -> Error

作用:在示例校验失败时,把之前记录的源码位置贴到错误上。没有位置时,就保持原错误不变。

数据流:进去是一个 Error 和一个可选 ErrorLocation → 如果有位置,就调用 error.with_location 生成带位置的错误;如果没有,就直接返回原错误 → 出来仍然是 Error,但可能多了文件行列信息。

调用关系:PolicyBuilder::validate_pending_examples_from 在 validate_match_examples 或 validate_not_match_examples 失败时使用它。它让“规则示例不符合预期”的报错更容易被策略作者修正。

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

parse_example284–295 ↗
fn parse_example(value: Value<'v>) -> Result<Vec<String>>

作用:解析一条命令示例。示例可以写成一整段 shell 风格字符串,也可以写成字符串列表。

数据流:进去是一个 Starlark 值 → 如果是字符串,就交给 parse_string_example 按 shell 规则拆词;如果是列表,就交给 parse_list_example 逐项检查;如果是别的类型,就报 InvalidExample → 出来是一条命令的 token 列表。

调用关系:parse_examples 会对每条示例调用它。它是示例解析的分岔口,负责支持两种用户写法。

调用图:调用 2 个内部函数(parse_list_example, parse_string_example);外部调用 4 个(from_value, unpack_str, InvalidExample, format!)。

parse_string_example297–309 ↗
fn parse_string_example(raw: &str) -> Result<Vec<String>>

作用:把一条字符串形式的命令示例拆成命令和参数。它按 shell 的引号和空格规则来拆,避免简单按空格切导致引号内容出错。

数据流:进去是一段命令字符串 → 它用 shlex::split 按 shell 语法拆词;语法不合法就报错;拆完为空也报错 → 出来是非空的字符串列表。

调用关系:parse_example 在遇到字符串示例时调用它。它专门处理像 "git commit -m 'hello world'" 这种一行命令写法。

调用图:被 1 处调用(parse_example);外部调用 2 个(InvalidExample, split)。

parse_list_example311–335 ↗
fn parse_list_example(list: &ListRef) -> Result<Vec<String>>

作用:把列表形式的命令示例检查并转换成字符串列表。列表里每一项应该已经是一个命令 token。

数据流:进去是 Starlark 列表引用 → 它读取列表内容,要求每项都是字符串;如果有非字符串或列表为空,就报 InvalidExample → 出来是非空的 token 列表。

调用关系:parse_example 在遇到列表示例时调用它。它适合用户已经手动把命令和参数分开的写法,比如 ["git", "status"]。

调用图:被 1 处调用(parse_example);外部调用 2 个(content, InvalidExample)。

policy_builder337–345 ↗
fn policy_builder(eval: &Evaluator<'v, 'a, '_>) -> RefMut<'a, PolicyBuilder>

作用:从 Starlark 执行器里取回当前正在使用的 PolicyBuilder,并拿到可修改的访问权。

数据流:进去是 Evaluator,也就是 Starlark 脚本正在运行时的执行器 → 它从 eval.extra 里取出之前塞进去的 RefCell<PolicyBuilder>,确认类型正确,然后 borrow_mut 得到可修改引用 → 出来是 RefMut<PolicyBuilder>,调用方可以往里面加规则。

调用关系:policy_builtins 里定义的 prefix_rule、network_rule、host_executable 都需要用它把脚本调用转成对 PolicyBuilder 的修改。PolicyParser::parse 负责在执行脚本前把 builder 放进 eval.extra。

policy_builtins348–473 ↗
fn policy_builtins(builder: &mut GlobalsBuilder)

作用:向 Starlark 策略文件注册本项目自己的三个命令:prefix_rule、network_rule、host_executable。没有它,策略文件就只是普通脚本,无法声明本系统认识的规则。

数据流:进去是 GlobalsBuilder,也就是要暴露给脚本的全局函数表 → 它登记 prefix_rule、network_rule、host_executable:prefix_rule 解析命令前缀、决定、理由和示例并加入执行规则;network_rule 解析主机、协议、决定并加入网络规则;host_executable 检查程序名和绝对路径并登记映射 → 出来是更新后的全局函数表,供 Starlark 执行时调用。

调用关系:PolicyParser::parse 会把它交给 GlobalsBuilder::standard().with(...)。随后策略文件执行到这些名字时,实际运行的就是这里定义的 Rust 函数;这些函数再调用 parse_pattern、parse_examples、policy_builder 等辅助函数,把用户写法变成 PolicyBuilder 里的数据。

execpolicy/src/policy.rs源码 ↗
domain_logicrequest handling

这个文件像一个门卫手册加门卫本人。Policy 保存三类信息:按程序名分组的命令规则、网络访问规则、以及主机上某些可执行文件的真实路径。外部传来一条命令时,它先看命令开头的程序名,找有没有精确匹配的规则;如果需要,也能把“/usr/bin/git”这种完整路径转换成“git”再匹配;如果还是没有规则,就可以交给一个兜底判断函数,用经验规则给出结论。Evaluation 是最后的判定单,里面有最终决定和命中的规则。这里还有一些辅助能力,比如新增命令前缀规则、新增网络规则、合并两份策略、整理允许或禁止联网的域名。整体上,它保证“哪些命令能跑、哪些网络能访问”有一个统一、可解释的判断入口。

函数细节24
Policy::new35–37 ↗
fn new(rules_by_program: MultiMap<String, RuleRef>) -> Self

作用:用一组按程序名整理好的命令规则,创建一份新的策略。适合只有命令规则、还没有网络规则和主机路径信息的时候使用。

数据流:输入是一张“程序名 → 多条规则”的表 → 它把网络规则设为空列表,把主机可执行文件路径设为空表 → 输出一份完整的 Policy。

调用关系:它把真正组装 Policy 的工作交给 Policy::from_parts,自己只是提供一个更省事的入口。Policy::empty 也会借用这种创建方式。

调用图:外部调用 3 个(new, from_parts, new)。

Policy::from_parts39–49 ↗
fn from_parts(
        rules_by_program: MultiMap<String, RuleRef>,
        network_rules: Vec<NetworkRule>,
        host_executables_by_name: HashMap<String, Arc<[AbsolutePathBuf]>>,
    ) -> Self

作用:用三块现成材料直接拼出一份策略:命令规则、网络规则、主机可执行文件路径。适合加载配置或合并策略后,已经准备好全部数据的场景。

数据流:输入三份数据 → 它原样放进 Policy 的对应字段里 → 输出一份可以用来判断命令和网络访问的 Policy。

调用关系:这是 Policy 最底层的构造函数。Policy::new 和 Policy::merge_overlay 都会调用它,避免到处重复写组装字段的代码。

Policy::empty51–53 ↗
fn empty() -> Self

作用:创建一份完全空的策略。它常用于测试、默认初始化,或者先建空壳再逐条添加规则。

数据流:不需要输入 → 它新建一张空的规则表,并通过 Policy::new 包装成策略 → 输出没有任何命令规则、网络规则和路径记录的 Policy。

调用关系:它是 Policy::new 的快捷用法。调用者如果还没有任何规则,可以先拿到一个安全的空对象。

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

Policy::rules55–57 ↗
fn rules(&self) -> &MultiMap<String, RuleRef>

作用:把策略里保存的命令规则表拿出来给别人查看。它只给只读引用,不让外部直接乱改。

数据流:输入是当前 Policy → 它读取内部的 rules_by_program 字段 → 输出这张按程序名分组的规则表的只读视图。

调用关系:它是一个查看窗口。其他代码想展示、检查或导出命令规则时会用它,而真正改规则要走 add_prefix_rule 等专门函数。

Policy::network_rules59–61 ↗
fn network_rules(&self) -> &[NetworkRule]

作用:返回策略里的网络规则列表,比如哪些域名允许访问、哪些禁止访问。它同样只提供查看,不直接开放修改。

数据流:输入是当前 Policy → 它读取内部的 network_rules → 输出一个只读的网络规则切片,也就是列表视图。

调用关系:它让外部能查看网络规则。需要得到最终允许和禁止域名列表时,则由 Policy::compiled_network_domains 做进一步整理。

Policy::host_executables63–65 ↗
fn host_executables(&self) -> &HashMap<String, Arc<[AbsolutePathBuf]>>

作用:返回“程序名对应哪些真实路径”的记录。这个记录用于把完整路径形式的命令和按名字写的规则对上号。

数据流:输入是当前 Policy → 它读取 host_executables_by_name → 输出一张只读表,例如 git 对应 /usr/bin/git 这类路径。

调用关系:它主要是查看接口。真正利用这张表做匹配的是 Policy::match_host_executable_rules。

Policy::get_allowed_prefixes67–89 ↗
fn get_allowed_prefixes(&self) -> Vec<Vec<String>>

作用:找出所有明确允许的命令前缀。简单说,就是把“允许执行的命令开头长什么样”整理成列表。

数据流:输入是当前 Policy 的全部命令规则 → 它逐条挑出 PrefixRule,也就是“命令从这些词开始就匹配”的规则,再只保留决定为 Allow 的规则 → 输出去重并排序后的命令前缀列表。

调用关系:它遍历 Policy 内部的规则表,不参与单次命令判定。它更像报表功能,方便外部展示“目前哪些命令前缀被允许”。其中每个规则里的模式词会交给 render_pattern_token 转成能读的字符串。

调用图:外部调用 3 个(iter_all, new, with_capacity)。

Policy::add_prefix_rule91–111 ↗
fn add_prefix_rule(&mut self, prefix: &[String], decision: Decision) -> Result<()>

作用:往策略里添加一条“命令前缀规则”。比如规定以 git status 开头的命令允许,或以 rm 开头的命令禁止。

数据流:输入是一串命令词和一个决定 → 它检查前缀不能为空,把第一个词作为程序名,把后面的词做成逐个匹配的模式 → 成功时把新规则插入规则表;如果前缀为空,就返回错误。

调用关系:这是修改 Policy 的入口之一。它构造 PrefixRule 后放进 rules_by_program,之后 Policy::match_exact_rules 就能在检查命令时找到这条规则。

调用图:外部调用 3 个(from, new, insert)。

Policy::add_network_rule113–135 ↗
fn add_network_rule(
        &mut self,
        host: &str,
        protocol: NetworkRuleProtocol,
        decision: Decision,
        justification: Option<String>,
    ) -> Result<()>

作用:往策略里添加一条网络访问规则。它说明某个主机名或域名在某种协议下应该放行、禁止或询问。

数据流:输入是主机名、协议、决定和可选理由 → 它先把主机名标准化,避免同一个地址写法不同;再检查理由如果提供了就不能是空白 → 成功时把 NetworkRule 加到列表里,失败时返回错误。

调用关系:它依赖 normalize_network_rule_host 做主机名清理。添加后的规则会被 Policy::compiled_network_domains 汇总成最终允许和禁止的域名列表。

调用图:调用 1 个内部函数(normalize_network_rule_host);外部调用 1 个(InvalidRule)。

Policy::set_host_executable_paths137–139 ↗
fn set_host_executable_paths(&mut self, name: String, paths: Vec<AbsolutePathBuf>)

作用:记录某个程序名在主机上的真实可执行文件路径。这样规则写的是“git”,命令实际是“/usr/bin/git”时,也有机会匹配上。

数据流:输入是程序名和一组绝对路径 → 它把路径列表存进 host_executables_by_name → 之后策略就知道这个名字可以对应哪些真实文件。

调用关系:它为 Policy::match_host_executable_rules 准备资料。只有开启解析主机可执行文件选项时,这些路径记录才会参与命令匹配。

Policy::merge_overlay141–165 ↗
fn merge_overlay(&self, overlay: &Policy) -> Policy

作用:把另一份策略叠加到当前策略上,生成一份合并后的新策略。可以把基础规则和临时补充规则合在一起用。

数据流:输入是当前 Policy 和 overlay Policy → 它复制当前规则,再追加 overlay 的命令规则;复制当前网络规则,再追加 overlay 的网络规则;复制路径表,再用 overlay 的路径表补充或覆盖 → 输出一份新的 Policy,不直接改原来的两份。

调用关系:它最后调用 Policy::from_parts 组装结果。这个函数常用于“默认策略 + 用户临时策略”这种分层配置场景。

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

Policy::compiled_network_domains167–186 ↗
fn compiled_network_domains(&self) -> (Vec<String>, Vec<String>)

作用:把一串网络规则整理成两个最终名单:允许访问的域名和禁止访问的域名。后出现的相反规则会覆盖前面的结果。

数据流:输入是 Policy 里的网络规则列表 → 它按顺序扫描,遇到 Allow 就从禁止名单移除并放入允许名单,遇到 Forbidden 就反过来,Prompt 不进名单 → 输出“允许名单”和“禁止名单”两个列表。

调用关系:它在整理域名时调用 upsert_domain,保证同一个域名在同一名单里只出现一次,并且位置按最新规则更新。

调用图:调用 1 个内部函数(upsert_domain);外部调用 1 个(new)。

Policy::check188–198 ↗
fn check(&self, cmd: &[String], heuristics_fallback: &F) -> Evaluation

作用:检查一条命令,给出最终判定。它是最常用的简洁入口,使用默认匹配选项。

数据流:输入是一条命令和一个兜底判断函数 → 它先找匹配规则,如果没有规则就让兜底函数判断 → 最后把命中的结果汇总成 Evaluation 输出。

调用关系:它调用 Policy::matches_for_command_with_options 做实际匹配,再调用 Evaluation::from_matches 算出最终决定。需要自定义匹配选项时,用 Policy::check_with_options。

调用图:调用 2 个内部函数(from_matches, matches_for_command_with_options);外部调用 1 个(default)。

Policy::check_with_options200–212 ↗
fn check_with_options(
        &self,
        cmd: &[String],
        heuristics_fallback: &F,
        options: &MatchOptions,
    ) -> Evaluation

作用:检查一条命令,但允许调用者指定额外匹配选项。比如是否把完整可执行文件路径解析成程序名来匹配。

数据流:输入是一条命令、兜底判断函数和 MatchOptions → 它按这些选项查找匹配规则,必要时使用兜底判断 → 输出带最终决定和命中详情的 Evaluation。

调用关系:它和 Policy::check 很像,只是不使用默认选项。它同样依赖 Policy::matches_for_command_with_options 和 Evaluation::from_matches。

调用图:调用 2 个内部函数(from_matches, matches_for_command_with_options)。

Policy::check_multiple215–226 ↗
fn check_multiple(
        &self,
        commands: Commands,
        heuristics_fallback: &F,
    ) -> Evaluation

作用:一次检查多条命令,并把结果合成一个总判定。适合一批命令要作为一个整体审核的情况。

数据流:输入是一组命令和兜底判断函数 → 它使用默认匹配选项,把工作交给多命令版本 → 输出一个合并后的 Evaluation。

调用关系:它是 Policy::check_multiple_with_options 的便捷入口。需要额外匹配选项时,调用者可以直接用后者。

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

Policy::check_multiple_with_options228–251 ↗
fn check_multiple_with_options(
        &self,
        commands: Commands,
        heuristics_fallback: &F,
        options: &MatchOptions,
    ) -> Evaluation

作用:一次检查多条命令,并允许指定匹配选项。最终结果会反映这批命令中命中的所有规则。

数据流:输入是一组命令、兜底判断函数和 MatchOptions → 它逐条命令调用匹配流程,把所有 RuleMatch 收集到一个列表 → 输出由 Evaluation::from_matches 汇总出的总判定。

调用关系:Policy::check_multiple 会调用它。它负责把多条命令摊平成一堆匹配结果,再交给 Evaluation::from_matches 选出最终决定。

调用图:调用 1 个内部函数(from_matches);被 1 处调用(check_multiple);外部调用 1 个(into_iter)。

Policy::matches_for_command260–266 ↗
fn matches_for_command(
        &self,
        cmd: &[String],
        heuristics_fallback: HeuristicsFallback<'_>,
    ) -> Vec<RuleMatch>

作用:返回一条命令具体命中了哪些规则,而不只是给最终允许或禁止。它适合需要解释“为什么这样判”的场景。

数据流:输入是一条命令和可选的兜底判断函数 → 它使用默认匹配选项查规则 → 输出 RuleMatch 列表;如果没规则但提供了兜底函数,就输出一条兜底匹配结果。

调用关系:它是 Policy::matches_for_command_with_options 的简化版。Policy::check 这类最终判定函数也基于同一套匹配流程。

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

Policy::matches_for_command_with_options268–295 ↗
fn matches_for_command_with_options(
        &self,
        cmd: &[String],
        heuristics_fallback: HeuristicsFallback<'_>,
        options: &MatchOptions,
    ) -> Vec<RuleMatch>

作用:这是单条命令匹配规则的核心流程。它决定先查什么、查不到时是否尝试路径解析、最后是否使用兜底判断。

数据流:输入是一条命令、可选兜底判断函数和 MatchOptions → 它先按命令第一个词做精确规则匹配;如果开启了路径解析且没匹配到,就尝试把完整路径转成程序名再匹配;如果仍为空且有兜底函数,就生成一条兜底结果 → 输出匹配到的 RuleMatch 列表。

调用关系:Policy::check、Policy::check_with_options 和 Policy::matches_for_command 都会走到这里。它把精确匹配交给 Policy::match_exact_rules,把路径形式命令的匹配交给 Policy::match_host_executable_rules。

调用图:调用 1 个内部函数(match_exact_rules);被 3 处调用(check, check_with_options, matches_for_command);外部调用 1 个(vec!)。

Policy::match_exact_rules297–305 ↗
fn match_exact_rules(&self, cmd: &[String]) -> Option<Vec<RuleMatch>>

作用:按命令第一个词做最直接的规则匹配。比如命令开头是 git,就只查 git 这一组规则。

数据流:输入是一条命令 → 如果命令为空就没有结果;否则取第一个词,到规则表里找对应规则,并逐条询问是否匹配整条命令 → 输出匹配成功的 RuleMatch 列表,外面包在 Option 里表示有没有命令开头可查。

调用关系:它由 Policy::matches_for_command_with_options 首先调用。只有这里没有命中时,流程才可能继续尝试主机可执行文件路径匹配或兜底判断。

调用图:被 1 处调用(matches_for_command_with_options);外部调用 1 个(get_vec)。

Policy::match_host_executable_rules307–334 ↗
fn match_host_executable_rules(&self, cmd: &[String]) -> Vec<RuleMatch>

作用:处理“命令用完整路径写,但规则只按程序名写”的情况。比如把 /usr/bin/python 对应到 python 的规则。

数据流:输入是一条命令 → 它确认第一个词是绝对路径,再取出可用于查规则的文件名;如果策略里记录了这个名字允许的真实路径,还会检查当前路径是否在名单里;然后把命令开头替换成程序名去匹配规则 → 输出匹配结果,并把结果标记为已解析到原始完整路径。

调用关系:它服务于 Policy::matches_for_command_with_options 中的可选路径解析分支。它会用 executable_path_lookup_key 从路径中找查询用的名字,并从规则表里取对应规则。

调用图:调用 2 个内部函数(executable_path_lookup_key, try_from);外部调用 3 个(get_vec, new, once)。

upsert_domain337–340 ↗
fn upsert_domain(entries: &mut Vec<String>, host: &str)

作用:把一个域名放进列表,同时保证列表里不会有重复项。可以理解成“先擦掉旧记录,再写上新记录”。

数据流:输入是一个可修改的域名列表和一个域名 → 它先删除列表里已有的相同域名,再把这个域名追加到末尾 → 输出体现在原列表被修改。

调用关系:它由 Policy::compiled_network_domains 调用,用来维护允许名单或禁止名单,保证最新规则的位置和内容生效。

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

render_pattern_token342–347 ↗
fn render_pattern_token(token: &PatternToken) -> String

作用:把规则里的一个模式词变成人能读的字符串。普通词原样显示,多选词会显示成类似 [a|b] 的形式。

数据流:输入是 PatternToken → 如果是 Single,就输出里面的文字;如果是 Alts,就把多个可选项用竖线连接并加上方括号 → 输出一个字符串。

调用关系:它被 Policy::get_allowed_prefixes 用来把内部规则模式整理成可展示的命令前缀。

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

Evaluation::is_match358–362 ↗
fn is_match(&self) -> bool

作用:判断这次结果是不是命中了真正写在策略里的规则。它会把兜底经验判断排除掉。

数据流:输入是一个 Evaluation → 它查看 matched_rules 里有没有非 HeuristicsRuleMatch 的项目;HeuristicsRuleMatch 指没有规则时由兜底函数临时给出的判断 → 输出 true 或 false。

调用关系:它用于外部区分“确实有规则命中”和“只是兜底猜的”。Evaluation::from_matches 负责创建 Evaluation,而这个函数负责解释其中的匹配性质。

Evaluation::from_matches365–374 ↗
fn from_matches(matched_rules: Vec<RuleMatch>) -> Self

作用:把一组匹配结果汇总成最终判定单。它会从所有命中的决定里选出优先级最高的那个作为总决定。

数据流:输入是非空的 RuleMatch 列表 → 它逐条取出决定,找出最大也就是最强的决定 → 输出 Evaluation,包含最终 decision 和原始 matched_rules;如果输入为空,会触发内部错误,因为调用者必须保证不为空。

调用关系:Policy::check、Policy::check_with_options 和 Policy::check_multiple_with_options 都调用它。它位于匹配流程的最后一步,把“命中了哪些规则”变成“最终怎么判”。

调用图:被 3 处调用(check, check_multiple_with_options, check_with_options)。

核心审批决策引擎

这些核心模块在执行策略结果之上应用审批、沙箱、补丁和网络决策逻辑,用于面向用户的强制执行。

core/src/tools/sandboxing.rs源码 ↗
domain_logiccross-cutting / request handling

可以把这个文件看成工具执行前的“门卫和安检规则”。工具想运行命令、改文件、联网之前,先要判断:这件事是否已经被用户本轮会话批准过?是否必须弹出确认?是否能在沙箱里跑?沙箱就是一个受限制的小房间,用来防止命令随便读写文件或访问网络。文件里有 ApprovalStore,用来记住“本次会话别再问了”的批准结果;有 ExecApprovalRequirement,用来表达“不用问、必须问、禁止做”;还有 Approvable、Sandboxable、ToolRuntime 这些特征,像接口说明书一样,要求每种工具说清楚自己如何申请审批、如何选择沙箱、如何真正运行。SandboxAttempt 则把一次沙箱执行需要的环境打包好,最后转换成实际可执行的请求。一个重要细节是:如果沙箱里有“禁止读取某些路径”的限制,就不能简单绕过沙箱,否则这些禁止读取会失效。

函数细节19
ApprovalStore::get46–52 ↗
fn get(&self, key: &K) -> Option<ReviewDecision>

作用:从会话里的审批缓存中查一件事以前是否已经被批准过。这样用户选择“本次会话不再询问”后,后面相同的请求就不用反复打扰用户。

数据流:进去的是一个可序列化的键,也就是能转成文本的审批标识;它先把这个键转成 JSON 字符串,再拿这个字符串去内部表里查;出来的是找到的审批决定,找不到或转换失败就返回空。

调用关系:它被 with_cached_approval 间接使用:审批流程开始前会先查缓存,如果所有相关键都已经是“本次会话批准”,就直接放行,不再走真正的询问流程。

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

ApprovalStore::put54–61 ↗
fn put(&mut self, key: K, value: ReviewDecision)

作用:把某个审批结果存进会话缓存里。主要用于记住用户选择的“本次会话批准”,方便后续类似请求自动通过。

数据流:进去的是一个审批键和一个审批决定;它把键转成 JSON 字符串;转换成功后,把字符串和决定放入内部表,之后同样的键就能查到这个决定。

调用关系:它配合 with_cached_approval 使用:当用户批准“本次会话有效”时,with_cached_approval 会把每个相关目标都交给它保存起来。

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

with_cached_approval70–116 ↗
async fn with_cached_approval(
    services: &SessionServices,
    // Name of the tool, used for metrics collection.
    tool_name: &str,
    keys: Vec<K>,
    fetch: F,
) -> ReviewDecision

作用:这是统一的“先查缓存,不行再问用户”的审批流程。它避免用户对同一个命令或同一批文件反复点确认。

数据流:进去的是会话服务、工具名、一组审批键,以及一个真正发起询问的函数;它先检查这些键是否都已经被本次会话批准;如果是,就直接返回批准。否则它调用传入的询问函数拿到用户决定,记录一次审批统计;如果用户选择本次会话批准,它还会把每个键写入缓存。出来的是最终审批决定。

调用关系:多个工具的 start_approval_async 会调用它。它本身不负责弹窗或问用户,只负责审批缓存这层通用逻辑;真正怎么问,由传进来的 fetch 函数完成。

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

PermissionRequestPayload::bash141–155 ↗
fn bash(command: String, description: Option<String>) -> Self

作用:把一个 bash 命令包装成审批钩子能看懂的请求内容。bash 是常见的命令行外壳,这里用于表示“我要执行这条命令”。

数据流:进去的是命令字符串和可选说明;它创建一个 JSON 对象,把 command 放进去,如果有 description 也放进去;出来的是一个 PermissionRequestPayload,标明工具名是 bash,输入内容是这份 JSON。

调用关系:当审批前需要把命令交给策略钩子、提示界面或内联策略检查时,会用它生成统一格式的请求材料。调用方包括 handle_inline_policy_request、permission_request_payload 和 prompt 等流程。

调用图:调用 1 个内部函数(bash);被 4 处调用(handle_inline_policy_request, permission_request_payload, prompt, permission_request_payload);外部调用 3 个(new, Object, String)。

ExecApprovalRequirement::proposed_execpolicy_amendment182–194 ↗
fn proposed_execpolicy_amendment(&self) -> Option<&ExecPolicyAmendment>

作用:从一次执行审批要求里取出“以后类似命令可不可以少问”的建议规则。它让后续策略有机会变聪明,而不是每次都重新问。

数据流:进去的是一个 ExecApprovalRequirement;它查看这个要求是“需要审批”还是“跳过审批”,并检查里面有没有附带的策略修改建议;出来的是这个建议的引用,如果没有就返回空。

调用关系:它是审批要求对象上的小帮手。别的流程拿到审批要求后,可以通过它统一读取建议,而不用分别判断每种枚举情况。

default_exec_approval_requirement202–238 ↗
fn default_exec_approval_requirement(
    policy: AskForApproval,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> ExecApprovalRequirement

作用:根据全局审批策略和文件系统沙箱策略,给一次命令执行算出默认的审批要求。简单说,它回答“这次要不要先问用户”。

数据流:进去的是 AskForApproval 策略和文件系统沙箱策略;它先判断当前策略是否要求审批,比如受限沙箱下的按需审批通常要问;如果细粒度策略明确不允许弹沙箱审批,就返回禁止;如果需要审批就返回 NeedsApproval;否则返回 Skip,表示不用问但仍按默认沙箱执行。

调用关系:这是各工具没有自定义审批规则时的兜底判断。Approvable::exec_approval_requirement 默认返回空,调用方就会落到这个函数来决定。

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

sandbox_override_for_first_attempt246–275 ↗
fn sandbox_override_for_first_attempt(
    sandbox_permissions: SandboxPermissions,
    exec_approval_requirement: &ExecApprovalRequirement,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
)

作用:决定第一次执行时要不要直接绕过沙箱。它处理的是“用户或策略已经信任这次执行,那还要不要关进小房间”的问题。

数据流:进去的是工具请求的沙箱权限、执行审批要求、文件系统沙箱策略;它先检查能不能安全地无沙箱运行,如果有禁止读取规则就不能绕过;然后看审批要求是否明确要求绕过沙箱,或权限是否要求升级;出来的是“不覆盖”或“第一次绕过沙箱”。

调用关系:运行流程 run 会在真正执行前调用它。它还会调用 unsandboxed_execution_allowed 来确认绕过沙箱不会偷偷取消重要的读文件限制。

调用图:调用 2 个内部函数(unsandboxed_execution_allowed, requires_escalated_permissions);被 1 处调用(run);外部调用 1 个(matches!)。

unsandboxed_execution_allowed283–287 ↗
fn unsandboxed_execution_allowed(
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> bool

作用:判断当前文件系统策略能不能用“无沙箱执行”来等价表示。关键是保护那些“禁止读取某些路径”的规则不被绕过。

数据流:进去的是文件系统沙箱策略;它检查策略里有没有禁止读取的限制;如果没有,返回 true,表示可以无沙箱运行;如果有,返回 false,因为离开沙箱后这些限制就没地方执行了。

调用关系:很多执行决策都会先问它,包括 run、determine_action、shell_request_escalation_execution、sandbox_override_for_first_attempt 和 sandbox_permissions_preserving_denied_reads。它是防止权限升级时误放大的安全刹车。

调用图:调用 1 个内部函数(has_denied_read_restrictions);被 5 处调用(run, determine_action, shell_request_escalation_execution, sandbox_override_for_first_attempt, sandbox_permissions_preserving_denied_reads)。

sandbox_permissions_preserving_denied_reads289–303 ↗
fn sandbox_permissions_preserving_denied_reads(
    sandbox_permissions: SandboxPermissions,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> SandboxPermissions

作用:在需要提升权限时,尽量保住“禁止读取某些文件”的限制。它避免因为想放宽某些权限,就把读文件禁令也一起弄丢。

数据流:进去的是请求的沙箱权限和文件系统沙箱策略;如果请求要求升级权限,而且当前策略不能无沙箱执行,它就把权限降回默认沙箱权限;否则保持原样返回。结果是:该沙箱的还是沙箱,该升级的才升级。

调用关系:网络审批和运行流程都会用它,比如 network_approval_spec、run、try_run_zsh_fork。它依赖 unsandboxed_execution_allowed 判断是否存在不能丢的 denied reads,也就是禁止读取限制。

调用图:调用 2 个内部函数(unsandboxed_execution_allowed, requires_escalated_permissions);被 5 处调用(network_approval_spec, run, try_run_zsh_fork, network_approval_spec, run)。

managed_network_for_sandbox_permissions305–314 ↗
fn managed_network_for_sandbox_permissions(
    network: Option<&NetworkProxy>,
    sandbox_permissions: SandboxPermissions,
) -> Option<&NetworkProxy>

作用:决定这次执行是否还要使用受管理的网络代理。网络代理可以理解成一扇受控的网关,用来限制或审计联网行为。

数据流:进去的是可选的 NetworkProxy 和沙箱权限;如果权限要求升级,说明这次可能要脱离受控环境,它返回空,不使用受管理网络;否则把原来的网络代理原样返回。

调用关系:执行和网络审批相关流程会调用它,包括 network_approval_spec、run、try_run_zsh_fork 等。它把“提升权限”和“是否强制走托管网络”这两个安全选择连在一起。

调用图:调用 1 个内部函数(requires_escalated_permissions);被 6 处调用(explicit_escalation_prepares_exec_without_managed_network, network_approval_spec, run, try_run_zsh_fork, network_approval_spec, run)。

Approvable::sandbox_permissions330–332 ↗
fn sandbox_permissions(&self, _req: &Req) -> SandboxPermissions

作用:给某个待执行请求提供它想要的沙箱权限。默认情况下,它表示“使用系统默认沙箱规则”。

数据流:进去的是请求本身;默认实现不读取请求内容,直接返回 SandboxPermissions::UseDefault;它不改动任何状态。

调用关系:实现 Approvable 的具体工具可以覆盖它,表达自己需要更高权限或特殊沙箱设置。运行编排流程会读取这个结果,继续决定沙箱和审批方式。

Approvable::should_bypass_approval334–340 ↗
fn should_bypass_approval(&self, policy: AskForApproval, already_approved: bool) -> bool

作用:判断这次是否可以跳过用户审批。默认规则很保守:已经批准过就跳过,或者全局策略明确说永远不问才跳过。

数据流:进去的是审批策略和一个“是否已经批准过”的布尔值;如果已经批准过,直接返回 true;否则只有策略是 Never,也就是从不询问时,才返回 true;其他情况返回 false。

调用关系:这是 Approvable 的默认行为,具体工具可以直接用。审批流程在决定是否弹出确认前会参考它,避免重复询问或违背全局不询问策略。

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

Approvable::exec_approval_requirement344–346 ↗
fn exec_approval_requirement(&self, _req: &Req) -> Option<ExecApprovalRequirement>

作用:允许具体工具提供自己的执行审批要求。默认返回空,意思是“我没有特殊规则,请用系统默认判断”。

数据流:进去的是工具请求;默认实现不看请求,返回 None;它不产生副作用。

调用关系:审批编排流程会先问工具有没有自定义要求;如果这里返回 None,就会退回 default_exec_approval_requirement 来按通用策略判断。

Approvable::permission_request_payload350–352 ↗
fn permission_request_payload(&self, _req: &Req) -> Option<PermissionRequestPayload>

作用:让工具在审批前提供一份给策略钩子看的请求材料。默认没有材料,表示这个工具不需要额外的审批前检查输入。

数据流:进去的是请求;默认实现不读取内容,返回 None;没有修改任何状态。

调用关系:如果某个工具覆盖它,就可以像 PermissionRequestPayload::bash 那样,把命令或参数整理成统一格式,交给审批钩子、守护审查或用户审批流程使用。

Approvable::wants_no_sandbox_approval355–363 ↗
fn wants_no_sandbox_approval(&self, policy: AskForApproval) -> bool

作用:判断在某些情况下,工具是否想申请“无沙箱执行”的批准。也就是问:如果沙箱跑不了,能不能向用户请求放到沙箱外跑。

数据流:进去的是全局审批策略;它按策略返回布尔值:OnFailure 和 UnlessTrusted 会允许申请,Never 和 OnRequest 不允许,Granular 则看细粒度配置里的 sandbox_approval 开关。

调用关系:这是工具审批逻辑的一部分。执行失败或需要升级权限时,运行流程会参考它,决定能不能发起“离开沙箱运行”的审批。

Sandboxable::escalate_on_failure374–376 ↗
fn escalate_on_failure(&self) -> bool

作用:说明工具在沙箱里失败后,默认是否可以尝试升级权限再跑一次。默认是可以。

数据流:没有输入参数;默认直接返回 true;它不读取也不修改状态。

调用关系:实现 Sandboxable 的工具会继承这个默认行为,除非它覆盖成 false。运行流程遇到沙箱失败时,会用这个选择决定是否进入升级权限或再次审批的路径。

ToolRuntime::network_approval_spec393–395 ↗
fn network_approval_spec(&self, _req: &Req, _ctx: &ToolCtx) -> Option<NetworkApprovalSpec>

作用:让工具声明这次请求是否需要网络审批。默认返回空,表示这类工具不额外申请联网许可。

数据流:进去的是请求和工具上下文;默认实现不使用这些信息,返回 None;没有副作用。

调用关系:具体工具如果可能联网,可以覆盖它,给审批系统一份网络申请说明。运行编排会在执行前调用它,判断是否需要网络审批。

ToolRuntime::sandbox_cwd397–399 ↗
fn sandbox_cwd(&self, _req: &'a Req) -> Option<&'a AbsolutePathBuf>

作用:让工具指定沙箱里的工作目录。工作目录可以理解成命令开始运行时所在的文件夹。

数据流:进去的是请求;默认实现不读取请求,返回 None,表示使用外部流程给定的默认目录;不修改任何状态。

调用关系:具体工具如果需要在某个目录下执行,可以覆盖它。SandboxAttempt 最终会使用选定的目录来生成实际执行环境。

SandboxAttempt::env_for424–452 ↗
fn env_for(
        &self,
        command: SandboxCommand,
        options: ExecOptions,
        network: Option<&NetworkProxy>,
    ) -> Result<crate::sandboxing::ExecRequest, CodexErr>

作用:把“一次沙箱尝试”的各种设置,转换成真正可以交给执行器运行的请求。它是沙箱规则落地成命令执行环境的最后一段。

数据流:进去的是要运行的 SandboxCommand、执行选项和可选网络代理;它把沙箱类型、权限、网络控制、工作目录、平台相关沙箱参数等打包成 SandboxTransformRequest,交给 SandboxManager 转换;转换成功后,再变成项目内部的 ExecRequest,并带上工作区根目录。出来的是可执行请求;如果转换失败,返回 CodexErr 错误。

调用关系:run 和 try_run_zsh_fork 等真正执行工具的流程会调用它。它自己不运行命令,而是把活交给 SandboxManager::transform 做沙箱转换,再用 from_sandbox_exec_request 包装成统一的执行请求。

调用图:调用 2 个内部函数(from_sandbox_exec_request, transform);被 3 处调用(run, try_run_zsh_fork, run);外部调用 1 个(to_vec)。

core/src/exec_policy.rs源码 ↗
domain_logicconfig load, request handling, cross-cutting

这份文件像一个门卫。系统准备执行命令时,它会先读规则文件,也会看当前的审批设置、沙箱权限和命令本身的危险程度,然后给出三种结果:允许、需要用户批准、禁止。它还支持用户批准后把“以后类似命令可放行”的规则写回规则文件,并马上更新内存里的规则,不用重启。这里的“沙箱”可以理解成给命令划出的安全活动范围;“策略规则”就是写在配置里的通行证或禁令。文件里最核心的是 ExecPolicyManager,它保存当前策略,负责加载、检查和更新。它还会把 bash、PowerShell 这类外壳包装过的命令拆开看,避免只看到“bash”而漏掉里面真正要跑的危险内容。

函数细节28
child_uses_parent_exec_policy137–159 ↗
fn child_uses_parent_exec_policy(parent_config: &Config, child_config: &Config) -> bool

作用:判断一个子任务能不能沿用父任务的执行策略。这样可以避免子任务误用不该继承的安全规则。

数据流:输入父配置和子配置 → 分别取出它们参与执行策略的配置目录、忽略规则开关、强制要求里的策略 → 如果这些关键内容完全一样,就返回 true,否则返回 false。

调用关系:它会被 inherited_exec_policy_for_source 使用,用来决定创建子会话或子代理时,是否可以直接复用父级已经加载好的执行策略。

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

is_policy_match161–166 ↗
fn is_policy_match(rule_match: &RuleMatch) -> bool

作用:区分一次命令命中的是用户写的策略规则,还是系统临时推断出来的经验判断。这个区别会影响是否能自动建议新增规则。

数据流:输入一个规则匹配结果 → 如果它是前缀规则匹配,就认为是真正的策略命中并返回 true;如果只是启发式判断,就返回 false。

调用关系:它是本文件里多个决策步骤的小筛子,用来判断“这事是规则说了算,还是系统猜的”。

prompt_is_rejected_by_policy174–197 ↗
fn prompt_is_rejected_by_policy(
    approval_policy: AskForApproval,
    prompt_is_rule: bool,
) -> Option<&'static str>

作用:判断当前审批设置是否禁止弹出确认提示。比如用户设置了“永不询问”,那本来该询问的命令就只能被拒绝。

数据流:输入审批策略和这次提示是否来自规则 → 检查 Never、Granular 等设置 → 输出一个拒绝原因,或输出 None 表示可以继续提示用户。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 在发现命令需要批准后会调用它,确认这次“问用户”是不是被总体设置允许。

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

ExecPolicyManager::new250–255 ↗
fn new(policy: Arc<Policy>) -> Self

作用:创建一个新的执行策略管理器。它把策略放进可以安全替换的容器里,并准备一把更新锁,防止两次规则写入互相打架。

数据流:输入一份 Policy → 包装成 ArcSwap 方便运行中读取和整体替换 → 创建只允许一个更新者进入的 Semaphore → 输出 ExecPolicyManager。

调用关系:它被 ExecPolicyManager::load、默认构造和多处测试使用,是拿到策略管理器的最基础入口。

调用图:被 4 处调用(exec_approval_requirement_for_command, mixed_rule_and_sandbox_prompt_prioritizes_rule_for_rejection_decision, mixed_rule_and_sandbox_prompt_rejects_when_granular_rules_are_disabled, verify_approval_requirement_for_unsafe_powershell_command);外部调用 2 个(from, new)。

ExecPolicyManager::load258–264 ↗
async fn load(config_stack: &ConfigLayerStack) -> Result<Self, ExecPolicyError>

作用:从配置层加载执行策略,并生成管理器。即使规则解析有警告,它也会记录出来,让系统能安全地继续用空策略启动。

数据流:输入配置层栈 → 调用 load_exec_policy_with_warning 读取并解析规则 → 有解析警告就写日志 → 用解析出的策略创建 ExecPolicyManager 并返回。

调用关系:它会在 spawn_internal 等启动流程中被调用,也被测试用来验证没有规则文件或子代理策略继承时的行为。

调用图:调用 1 个内部函数(load_exec_policy_with_warning);被 3 处调用(returns_empty_policy_when_no_policy_files_exist, spawn_internal, guardian_subagent_does_not_inherit_parent_exec_policy_rules);外部调用 3 个(new, new, warn!)。

ExecPolicyManager::current266–268 ↗
fn current(&self) -> Arc<Policy>

作用:取出当前正在使用的执行策略。它让检查命令和更新规则时都能读到最新版本。

数据流:输入管理器自身 → 从 ArcSwap 里完整加载当前 Policy → 输出一个可共享的 Arc<Policy>,不改动原状态。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command、append_amendment_and_update、append_network_rule_and_update 都会先通过它拿当前规则。

调用图:被 3 处调用(append_amendment_and_update, append_network_rule_and_update, create_exec_approval_requirement_for_command);外部调用 1 个(load_full)。

ExecPolicyManager::create_exec_approval_requirement_for_command270–375 ↗
async fn create_exec_approval_requirement_for_command(
        &self,
        req: ExecApprovalRequest<'_>,
    ) -> ExecApprovalRequirement

作用:这是命令执行前的核心判定函数:决定跳过审批、请求审批,还是直接禁止。它把规则、沙箱、危险命令判断和用户审批设置合在一起看。

数据流:输入待执行命令、审批策略、权限档位、沙箱信息和可选的建议前缀规则 → 先把 shell 包装命令拆成真实命令 → 用当前策略逐条检查,不匹配时用安全/危险启发式兜底 → 输出 ExecApprovalRequirement,并可能带上建议写入的新规则。

调用关系:它是运行命令前的主要入口;它会调用 current、commands_for_exec_policy、prompt_is_rejected_by_policy、derive_prompt_reason、derive_forbidden_reason 等 helper,把判断结果整理成上层能直接执行的审批要求。

调用图:调用 7 个内部函数(current, commands_for_exec_policy, derive_forbidden_reason, derive_prompt_reason, derive_requested_execpolicy_amendment_from_prefix_rule, prompt_is_rejected_by_policy, try_derive_execpolicy_amendment_for_allow_rules)。

ExecPolicyManager::append_amendment_and_update377–425 ↗
async fn append_amendment_and_update(
        &self,
        codex_home: &Path,
        amendment: &ExecPolicyAmendment,
    ) -> Result<(), ExecPolicyUpdateError>

作用:把用户同意的“允许某个命令前缀”写进默认规则文件,并同步更新内存策略。这样下次类似命令不用再重复询问。

数据流:输入 codex_home 和一条执行策略修订 → 先拿更新锁 → 找到默认规则文件路径 → 在线程池里做阻塞式文件追加 → 再检查当前策略是否已允许 → 如未允许,就把新 allow 前缀加到内存策略并替换当前策略。

调用关系:它在用户批准并选择记住规则后使用;它依赖 current、default_policy_path 和 spawn_blocking,避免慢文件写入堵住异步运行流程。

调用图:调用 2 个内部函数(current, default_policy_path);外部调用 4 个(new, store, acquire, spawn_blocking)。

ExecPolicyManager::append_network_rule_and_update427–471 ↗
async fn append_network_rule_and_update(
        &self,
        codex_home: &Path,
        host: &str,
        protocol: NetworkRuleProtocol,
        decision: Decision,
        justification: Option<

作用:把网络访问规则写进规则文件,并马上更新内存策略。它用于记住某个主机和协议以后该允许、提示还是禁止。

数据流:输入 codex_home、主机名、协议、决定和可选理由 → 拿更新锁 → 计算默认规则文件 → 在线程池追加网络规则 → 克隆当前策略,加入网络规则,再整体替换内存中的策略。

调用关系:它和 append_amendment_and_update 是同一类“写文件并热更新”的流程,只是处理的是网络规则而不是命令前缀规则。

调用图:调用 2 个内部函数(current, default_policy_path);外部调用 4 个(new, store, acquire, spawn_blocking)。

ExecPolicyManager::default475–477 ↗
fn default() -> Self

作用:创建一个没有任何规则的默认策略管理器。测试或没有配置时可以用它作为安全起点。

数据流:没有外部输入 → 创建空 Policy → 调用 ExecPolicyManager::new 包装它 → 输出默认管理器。

调用关系:它被多处测试和 spawn_internal 使用,用在还没有加载到具体规则、或需要空策略起步的场景。

调用图:被 13 处调用(append_execpolicy_amendment_rejects_empty_prefix, append_execpolicy_amendment_updates_policy_and_file, empty_bash_lc_script_falls_back_to_original_command, exec_approval_requirement_falls_back_to_heuristics, request_rule_falls_back_when_prefix_rule_does_not_approve_all_commands, request_rule_uses_prefix_rule, whitespace_bash_lc_script_falls_back_to_original_command, spawn_internal, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx (+3 more));外部调用 3 个(new, new, empty)。

check_execpolicy_for_warnings480–485 ↗
async fn check_execpolicy_for_warnings(
    config_stack: &ConfigLayerStack,
) -> Result<Option<ExecPolicyError>, ExecPolicyError>

作用:只检查执行策略有没有解析警告,而不是直接拿策略来用。它适合配置检查或启动前提醒用户。

数据流:输入配置层栈 → 调用 load_exec_policy_with_warning → 丢掉成功加载的 Policy,只返回可能存在的警告错误。

调用关系:它把实际加载工作交给 load_exec_policy_with_warning,自己只负责把结果整理成“有没有警告”。

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

exec_policy_message_for_display487–507 ↗
fn exec_policy_message_for_display(source: &codex_execpolicy::Error) -> String

作用:把底层解析错误整理成人能看懂的一行消息。它会尽量去掉冗长的 Starlark 错误包装。

数据流:输入 codex_execpolicy 的错误对象 → 转成字符串 → 优先找以 error: 开头的行,或提取 starlark error 后面的细节 → 输出适合展示的短消息。

调用关系:format_exec_policy_error_with_source 会调用它,把错误消息和文件行号拼成最终提示。

调用图:被 1 处调用(format_exec_policy_error_with_source);外部调用 1 个(to_string)。

parse_starlark_line_from_message509–523 ↗
fn parse_starlark_line_from_message(message: &str) -> Option<(PathBuf, usize)>

作用:从 Starlark 错误文本里抠出文件路径和行号。Starlark 可以理解成规则文件使用的一种小脚本语言。

数据流:输入一段错误文本 → 读取第一行 → 按“路径:行:列: starlark error”这种格式拆开 → 如果行号有效,就输出路径和行号,否则输出 None。

调用关系:format_exec_policy_error_with_source 会在结构化位置信息不够准确时调用它,补出更有用的报错位置。

调用图:被 1 处调用(format_exec_policy_error_with_source);外部调用 1 个(from)。

format_exec_policy_error_with_source525–556 ↗
fn format_exec_policy_error_with_source(error: &ExecPolicyError) -> String

作用:把执行策略错误格式化成用户能直接定位问题的文字,尤其是规则文件解析失败时。它会尽量告诉用户“哪个文件第几行附近有问题”。

数据流:输入 ExecPolicyError → 如果是解析错误,就同时尝试结构化位置和从文本解析出的位置 → 选出更可信的路径和行号 → 加上简化后的错误消息 → 输出完整字符串;其他错误直接转字符串。

调用关系:它调用 exec_policy_message_for_display 和 parse_starlark_line_from_message,是面向用户展示规则错误的最后整理步骤。

调用图:调用 2 个内部函数(exec_policy_message_for_display, parse_starlark_line_from_message);外部调用 2 个(to_string, format!)。

load_exec_policy_with_warning558–566 ↗
async fn load_exec_policy_with_warning(
    config_stack: &ConfigLayerStack,
) -> Result<(Policy, Option<ExecPolicyError>), ExecPolicyError>

作用:加载执行策略,但把解析失败降级成“警告加空策略”。这样坏规则不会让整个系统直接起不来。

数据流:输入配置层栈 → 调用 load_exec_policy → 如果成功,返回策略和 None;如果是规则解析错误,返回空策略和 warning;如果是读目录、读文件等硬错误,就继续返回错误。

调用关系:ExecPolicyManager::load 和 check_execpolicy_for_warnings 都通过它加载规则,一个用策略继续运行,一个只关心警告。

调用图:调用 1 个内部函数(load_exec_policy);被 2 处调用(load, check_execpolicy_for_warnings);外部调用 1 个(empty)。

load_exec_policy568–625 ↗
async fn load_exec_policy(config_stack: &ConfigLayerStack) -> Result<Policy, ExecPolicyError>

作用:真正从各层配置目录读取 .rules 文件并构建执行策略。它保证低优先级规则先读,高优先级规则后读,后者可以覆盖前者。

数据流:输入配置层栈 → 遍历启用的配置层 → 按需要跳过用户/项目规则 → 收集每层 rules 目录下的 .rules 文件 → 逐个读文件并交给 PolicyParser 解析 → 构建 Policy → 如果配置里还有强制策略,就叠加合并后返回。

调用关系:它被 load_exec_policy_with_warning、测试和 build_config_state_with_mtimes 调用;文件收集工作交给 collect_policy_files,规则文本解析交给 PolicyParser。

调用图:调用 5 个内部函数(get_layers, ignore_user_and_project_exec_policy_rules, requirements, collect_policy_files, new);被 4 处调用(loads_requirements_exec_policy_without_rules_files, merges_requirements_exec_policy_with_file_rules, load_exec_policy_with_warning, build_config_state_with_mtimes);外部调用 5 个(new, read_to_string, matches!, debug!, trace!)。

render_decision_for_unmatched_command628–745 ↗
fn render_decision_for_unmatched_command(
    command: &[String],
    context: UnmatchedCommandContext<'_>,
) -> Decision

作用:当命令没有命中任何明确规则时,用一套保守的默认判断决定允许、询问还是禁止。它是“规则没写到时怎么办”的兜底逻辑。

数据流:输入命令和上下文,包括审批策略、权限档位、Windows 沙箱级别、沙箱请求和命令来源 → 先判断是否已知安全,再判断是否危险或缺少有效沙箱保护 → 结合审批设置和沙箱类型 → 输出 Decision。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 会把它作为策略检查的 fallback,也就是规则没匹配时交给它判断。它会调用 profile_has_managed_filesystem_restrictions 和命令安全/危险分类函数。

调用图:调用 5 个内部函数(profile_has_managed_filesystem_restrictions, command_might_be_dangerous, is_dangerous_powershell_words, is_known_safe_command, is_safe_powershell_words);外部调用 2 个(cfg!, matches!)。

profile_has_managed_filesystem_restrictions747–755 ↗
fn profile_has_managed_filesystem_restrictions(permission_profile: &PermissionProfile) -> bool

作用:判断当前权限档位是否有系统管理的文件系统限制。这个结果会影响在 Windows 沙箱不可用时是否需要更保守。

数据流:输入 PermissionProfile → 取出文件系统沙箱策略 → 检查是否是 Managed、是否受限、是否没有全盘写权限 → 返回 true 或 false。

调用关系:render_decision_for_unmatched_command 会调用它,用来识别“看起来有限制,但平台沙箱实际没启用”的风险场景。

调用图:调用 1 个内部函数(file_system_sandbox_policy);被 1 处调用(render_decision_for_unmatched_command);外部调用 1 个(matches!)。

default_policy_path757–759 ↗
fn default_policy_path(codex_home: &Path) -> PathBuf

作用:算出默认规则文件的位置。所有自动追加的命令规则和网络规则都会写到这里。

数据流:输入 codex_home 路径 → 拼上 rules/default.rules → 输出完整 PathBuf。

调用关系:append_amendment_and_update 和 append_network_rule_and_update 都用它找到要追加规则的文件。

调用图:被 2 处调用(append_amendment_and_update, append_network_rule_and_update);外部调用 1 个(join)。

commands_for_exec_policy761–799 ↗
fn commands_for_exec_policy(command: &[String]) -> ExecPolicyCommands

作用:把传进来的命令整理成适合策略检查的命令列表。它会尽量拆开 bash -lc 或 PowerShell -Command 里真正执行的内容。

数据流:输入原始命令参数数组 → 先尝试解析简单 shell 脚本为多条普通命令 → Windows 下再尝试解析 PowerShell → 如果只能粗略识别单个前缀,就标记用了复杂解析 → 都不行就使用原始命令 → 输出命令列表、是否复杂解析、命令来源。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 会先调用它,再把拆出来的每条命令交给执行策略检查。

调用图:调用 3 个内部函数(parse_shell_lc_plain_commands, parse_shell_lc_single_command_prefix, parse_powershell_command_into_plain_commands);被 1 处调用(create_exec_approval_requirement_for_command);外部调用 1 个(vec!)。

try_derive_execpolicy_amendment_for_prompt_rules811–830 ↗
fn try_derive_execpolicy_amendment_for_prompt_rules(
    matched_rules: &[RuleMatch],
) -> Option<ExecPolicyAmendment>

作用:在命令需要提示用户时,尝试生成一条“以后允许这个命令”的建议规则。但如果提示是明确策略规则造成的,就不会建议绕过它。

数据流:输入所有匹配结果 → 如果有任何策略规则要求 Prompt,直接返回 None → 否则找到第一个启发式判断为 Prompt 的命令 → 把它包装成 ExecPolicyAmendment 返回。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 在准备 NeedsApproval 结果时会用到它,作为没有用户指定前缀规则时的自动建议来源。

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

try_derive_execpolicy_amendment_for_allow_rules835–851 ↗
fn try_derive_execpolicy_amendment_for_allow_rules(
    matched_rules: &[RuleMatch],
) -> Option<ExecPolicyAmendment>

作用:在命令本来被允许但可能因沙箱失败而需要升级时,尝试建议一条以后绕过沙箱的允许规则。它只在没有明确策略规则命中时才这么做。

数据流:输入匹配结果 → 如果有任何真正策略规则命中,返回 None → 否则找第一个启发式 Allow 命令 → 生成 ExecPolicyAmendment 返回。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 在返回 Skip 时调用它,用来给后续沙箱失败后的提示准备可记住的规则。

调用图:被 1 处调用(create_exec_approval_requirement_for_command);外部调用 1 个(iter)。

derive_requested_execpolicy_amendment_from_prefix_rule853–892 ↗
fn derive_requested_execpolicy_amendment_from_prefix_rule(
    prefix_rule: Option<&Vec<String>>,
    matched_rules: &[RuleMatch],
    exec_policy: &Policy,
    commands: &[Vec<String>],
    exec_poli

作用:根据调用方请求的命令前缀,判断是否应该建议新增一条允许规则。它会避免建议太宽泛、太危险或没有实际效果的规则。

数据流:输入可选前缀、已匹配规则、当前策略、拆分后的命令、兜底判断和匹配选项 → 没有前缀、空前缀、禁用前缀或已有策略命中时返回 None → 临时加入这条前缀规则测试是否能批准所有相关命令 → 能才返回 amendment。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 会优先调用它处理用户或上层传来的 prefix_rule;具体“加了是否有效”的测试交给 prefix_rule_would_approve_all_commands。

调用图:调用 1 个内部函数(prefix_rule_would_approve_all_commands);被 1 处调用(create_exec_approval_requirement_for_command);外部调用 2 个(new, iter)。

prefix_rule_would_approve_all_commands894–915 ↗
fn prefix_rule_would_approve_all_commands(
    exec_policy: &Policy,
    prefix_rule: &[String],
    commands: &[Vec<String>],
    exec_policy_fallback: &impl Fn(&[String]) -> Decision,
    match_opti

作用:试算一条前缀允许规则是否真的能让这次拆出来的所有命令都通过。它只是模拟,不会改动真实策略。

数据流:输入当前策略、候选前缀、命令列表、兜底判断和匹配选项 → 克隆策略 → 尝试加入 allow 前缀规则 → 对每条命令重新检查 → 全部都是 Allow 才返回 true。

调用关系:它被 derive_requested_execpolicy_amendment_from_prefix_rule 调用,是防止系统建议“看似有用但其实不能解决问题”的规则的验证步骤。

调用图:被 1 处调用(derive_requested_execpolicy_amendment_from_prefix_rule);外部调用 1 个(clone)。

derive_prompt_reason918–944 ↗
fn derive_prompt_reason(command_args: &[String], evaluation: &Evaluation) -> Option<String>

作用:为“需要审批”的结果生成一段解释。只有当明确策略规则导致提示时,它才返回原因。

数据流:输入原始命令参数和评估结果 → 把命令渲染成适合展示的一行文字 → 找到最具体的 Prompt 前缀规则 → 如果有用户写的理由就带上理由,否则说明是策略要求 → 没有策略 Prompt 就返回 None。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 在生成 NeedsApproval 时调用它,让用户知道为什么被要求确认。

调用图:调用 1 个内部函数(render_shlex_command);被 1 处调用(create_exec_approval_requirement_for_command);外部调用 1 个(format!)。

render_shlex_command946–948 ↗
fn render_shlex_command(args: &[String]) -> String

作用:把命令参数数组变成一行比较像终端里的命令文本。这样错误和提示消息更容易读。

数据流:输入参数列表 → 尝试用 shlex 规则加引号并拼接,避免空格等字符造成误解 → 如果失败就简单用空格连接 → 输出字符串。

调用关系:derive_prompt_reason 和 derive_forbidden_reason 都调用它,把内部的参数数组变成人能看懂的命令。

调用图:被 2 处调用(derive_forbidden_reason, derive_prompt_reason);外部调用 1 个(try_join)。

derive_forbidden_reason953–980 ↗
fn derive_forbidden_reason(command_args: &[String], evaluation: &Evaluation) -> String

作用:为“禁止执行”的结果生成清楚的拒绝原因。它会优先展示最具体的禁止规则和用户写的说明。

数据流:输入原始命令参数和评估结果 → 渲染命令文本 → 找出最长、最具体的 Forbidden 前缀规则 → 有 justification 就用它解释;没有就说明哪个前缀被策略禁止;没有具体规则就给通用禁止原因。

调用关系:ExecPolicyManager::create_exec_approval_requirement_for_command 在得到 Decision::Forbidden 时调用它,把底层评估结果变成上层可展示的文字。

调用图:调用 1 个内部函数(render_shlex_command);被 1 处调用(create_exec_approval_requirement_for_command);外部调用 1 个(format!)。

collect_policy_files982–1032 ↗
async fn collect_policy_files(dir: impl AsRef<Path>) -> Result<Vec<PathBuf>, ExecPolicyError>

作用:收集某个 rules 目录下所有规则文件。它只接受普通文件且扩展名是 .rules,并按路径排序,保证加载顺序稳定。

数据流:输入目录路径 → 异步读取目录 → 如果目录不存在就返回空列表 → 遇到读取错误就包装成 ExecPolicyError → 检查每个条目的扩展名和文件类型 → 排序后返回文件路径列表。

调用关系:load_exec_policy 会对每个配置层的 rules 目录调用它,拿到要读入 PolicyParser 的规则文件清单。

调用图:被 1 处调用(load_exec_policy);外部调用 5 个(as_ref, to_path_buf, new, read_dir, debug!)。

core/src/network_policy_decision.rs源码 ↗
domain_logicrequest handling

项目里网络访问不是想连就连,要先经过策略判断。这个文件就像一个“翻译员”:一边听网络代理说“这个请求被拦了,原因是什么”,一边把这些信息整理成给用户看的说明,或者整理成后续可保存的策略规则。比如某个域名被禁止访问时,它会把冷冰冰的原因代码,翻译成“这个域名不在当前沙箱允许列表里”这类人能看懂的话。它还会检查一次请求是否真的属于“需要询问用户”的情况,避免把缺信息、空域名、或本来不该问的问题拿去打扰用户。最后,当用户决定允许或拒绝某个网络访问时,它会把这个决定转换成执行策略系统认识的协议、动作和理由,方便持久保存。没有它,不同模块会各说各话,用户提示会含糊,策略保存也容易写错。

函数细节4
parse_network_policy_decision18–24 ↗
fn parse_network_policy_decision(value: &str) -> Option<NetworkPolicyDecision>

作用:这个函数把文本形式的网络决定翻译成程序内部能理解的决定。它只认“deny”和“ask”,其他内容都当作无效。

数据流:进去的是一段字符串,比如“deny”。函数检查它是不是已知的决定词;如果是,就变成对应的网络策略决定;如果不是,就返回空,表示无法识别。它不改动任何外部数据。

调用关系:它是一个小翻译工具,主要被 denied_network_policy_message 用来判断一次被拦截的请求是不是明确的“拒绝”。这样后面的提示逻辑不用直接处理原始字符串。

network_approval_context_from_payload26–44 ↗
fn network_approval_context_from_payload(
    payload: &NetworkPolicyDecisionPayload,
) -> Option<NetworkApprovalContext>

作用:这个函数从一份网络策略决定的载荷里,提取“可以拿去问用户批准吗”的关键信息。只有当请求确实是由决策器要求询问,并且协议和主机名都有效时,它才会给出上下文。

数据流:进去的是 NetworkPolicyDecisionPayload,也就是一包网络决策信息。函数先调用 is_ask_from_decider 确认这是不是“需要询问用户”的情况;再取出协议;再取出主机名并去掉前后空格;如果任何一步缺失或主机名为空,就返回空。成功时,它输出 NetworkApprovalContext,里面有主机名和协议。

调用关系:它处在“准备弹出批准问题”之前,负责把原始决策信息筛干净。它把是否能问用户这件事判断好,后续流程就可以放心使用 NetworkApprovalContext,而不是每一步都重复检查。

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

denied_network_policy_message46–72 ↗
fn denied_network_policy_message(blocked: &BlockedRequest) -> Option<String>

作用:这个函数把一次被网络策略拒绝的请求,变成一段用户能看懂的提示文字。它避免只显示“denied”这类机器代码,让人知道大概为什么被拦。

数据流:进去的是 BlockedRequest,也就是一次被拦住的网络请求记录。函数先读取里面的 decision 字段,并借助 parse_network_policy_decision 判断它是不是明确的拒绝;如果不是拒绝,就不生成消息。然后它读取 host;如果主机名为空,就返回一个笼统的“网络访问被策略拦截”提示。否则,它根据 reason 字段把原因代码翻译成具体说明,再用 format! 拼成最终句子返回。

调用关系:它会被 record_blocked_request 调用,通常发生在系统记录一次被拦截的网络请求时。它不负责决定要不要拦截,只负责把已经发生的拒绝包装成清楚的提示,供日志、界面或事件记录使用。

调用图:被 1 处调用(record_blocked_request);外部调用 1 个(format!)。

execpolicy_network_rule_amendment74–102 ↗
fn execpolicy_network_rule_amendment(
    amendment: &NetworkPolicyAmendment,
    network_approval_context: &NetworkApprovalContext,
    host: &str,
) -> ExecPolicyNetworkRuleAmendment

作用:这个函数把用户对网络规则的修改,转换成执行策略系统能保存的格式。简单说,就是把“允许这个协议访问这个主机”或“拒绝它”写成底层规则需要的样子。

数据流:进去的是用户选择的 NetworkPolicyAmendment、当前的 NetworkApprovalContext,以及目标 host。函数先把批准上下文里的协议,比如 HTTP、HTTPS 或 SOCKS5,转换成执行策略模块认识的协议枚举;再把用户动作 Allow 或 Deny 转成执行策略里的 Allow 或 Forbidden;最后用 format! 生成一段理由文字,比如允许某协议访问某主机。输出的是 ExecPolicyNetworkRuleAmendment,里面包含协议、决定和说明。

调用关系:它会被 persist_network_policy_amendment 调用,也就是在系统准备把用户的网络批准或拒绝长期保存时使用。它连接了“用户批准流程”和“执行策略存储流程”,确保保存下来的规则和用户刚才点的决定一致。

调用图:被 1 处调用(persist_network_policy_amendment);外部调用 1 个(format!)。

core/src/safety.rs源码 ↗
domain_logicrequest handling,执行 apply_patch 改文件之前

这份代码解决的是一个很实际的问题:AI 或工具准备修改文件时,不能只看它“想改什么”,还要确认它有没有权限改。否则一个补丁可能写到项目外面,甚至绕过只读限制。文件里的核心类型是 SafetyCheck,它把结果分成三类:可以自动批准、需要问用户、直接拒绝。主要函数 assess_patch_safety 会先拒绝空补丁,再看用户的审批设置、当前权限档位、文件系统沙箱策略。沙箱可以理解成“安全围栏”,让程序即使执行了,也只能在规定范围内写文件。代码还会检查补丁里的新增、删除、更新和移动路径是否都落在允许写的目录里。一个重要细节是:即使路径看起来安全,也可能有硬链接这种绕路方式,所以能自动批准时仍尽量放进沙箱里执行。整体目标很简单:既不无谓打扰用户,又不让危险写入悄悄发生。

函数细节3
assess_patch_safety33–116 ↗
fn assess_patch_safety(
    action: &ApplyPatchAction,
    policy: AskForApproval,
    permission_profile: &PermissionProfile,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    cwd: &Absol

作用:这是本文件的总判断入口,用来决定一个补丁能不能安全执行。它会给出三种结果之一:自动批准、询问用户、或者直接拒绝,并说明拒绝原因。

数据流:进去的是补丁内容、用户审批策略、权限配置、文件系统沙箱规则、当前工作目录和 Windows 沙箱级别。它先检查补丁是不是空的,再判断用户是否允许自动批准,然后检查补丁要写的路径是不是都在可写范围内,还会确认当前平台有没有可用沙箱。出来的是一个 SafetyCheck:要么带着可用沙箱类型自动通过,要么要求用户确认,要么给出拒绝理由;它本身不改文件,只做安全裁决。

调用关系:它会在 apply_patch 真正应用补丁前被调用,相当于开工前的门卫。它把路径范围检查交给 is_write_patch_constrained_to_writable_paths,把拒绝理由整理交给 patch_rejection_reason,还会调用平台沙箱探测函数 get_platform_sandbox 来确认能不能用安全围栏兜底。

调用图:调用 3 个内部函数(is_empty, is_write_patch_constrained_to_writable_paths, patch_rejection_reason);被 1 处调用(apply_patch);外部调用 2 个(get_platform_sandbox, matches!)。

patch_rejection_reason118–136 ↗
fn patch_rejection_reason(
    permission_profile: &PermissionProfile,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    cwd: &AbsolutePathBuf,
) -> &'static str

作用:这个函数专门把“为什么拒绝补丁”说清楚。它区分是因为整个环境基本只读,还是因为补丁想写到项目允许范围之外。

数据流:进去的是权限档位、文件系统沙箱规则和当前目录。它查看当前是否有全盘写权限,以及以当前目录为基准时有没有任何允许写的根目录。出来的是一段固定的英文拒绝原因;它不改变任何状态,只负责选择最准确的解释。

调用关系:它只在 assess_patch_safety 决定不能批准时被用到。这样主判断函数不用塞满错误文案细节,而是把“怎么解释拒绝”交给这个小助手。

调用图:调用 3 个内部函数(get_writable_roots_with_cwd, has_full_disk_write_access, as_path);被 1 处调用(assess_patch_safety)。

is_write_patch_constrained_to_writable_paths138–193 ↗
fn is_write_patch_constrained_to_writable_paths(
    action: &ApplyPatchAction,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    cwd: &AbsolutePathBuf,
) -> bool

作用:这个函数检查补丁里所有会被写到的路径,是否都在沙箱规则允许写入的地方。它防止补丁借助 ../ 这类路径写到项目外面。

数据流:进去的是补丁、文件系统沙箱规则和当前目录。它逐个看补丁里的文件变化:新增、删除要检查目标路径;更新要检查原路径;如果更新还伴随移动文件,也要检查移动后的路径。检查前会把相对路径转成绝对路径,并把 . 和 .. 这种路径片段整理掉。出来的是 true 或 false:true 表示所有写入位置都在允许范围内,false 表示至少有一个路径不安全;它不实际读写文件。

调用关系:它被 assess_patch_safety 调用,是自动批准前的重要检查步骤。它从补丁对象里读取 changes 列表,然后用沙箱策略逐个判断路径能不能写,结果会直接影响后面是自动执行、询问用户,还是拒绝。

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

旧版策略编译和匹配

这些旧版策略文件公开旧 API,解析 Starlark 策略,并根据已编译的规范实现参数和程序匹配。

execpolicy-legacy/src/lib.rs源码 ↗
configstartup / config load

这个库看起来是用来检查“某个程序能不能按某种参数方式执行”的。为了不让外部使用者到处翻内部文件,这个 lib.rs 像一个前台接待员:把参数匹配、策略解析、执行检查、错误类型等常用零件统一摆出来,别人只要从这个库入口拿就行。它还把 default.policy 文件直接编进程序里,也就是发布后的程序不需要再额外带一份默认策略文件。get_default_policy 会用 PolicyParser(策略解析器,把文本规则读成程序能理解的结构)解析这份内置文本,得到 Policy(策略对象)。如果没有这个文件,外部代码就很难方便地使用这些能力,也少了一个稳定的默认策略来源。

函数细节1
get_default_policy42–45 ↗
fn get_default_policy() -> starlark::Result<Policy>

作用:读取并解析内置的默认策略文件,返回一个程序能直接使用的 Policy。别人想用系统自带规则,而不是自己提供规则文件时,就会调用它。

数据流:进去时不需要调用者传东西;它读取已经编进程序里的 DEFAULT_POLICY 文本,把这个文本和名字“#default”交给 PolicyParser::new 创建解析器;然后解析器把纯文本规则转换成 Policy。出来的是解析成功后的策略对象,或者解析失败时的错误。

调用关系:它是默认策略加载流程的入口。main 会在启动时调用它来拿到系统自带的策略;它自己不手写解析细节,而是把真正读懂策略文本的工作交给 PolicyParser::new 创建出的解析器,再调用解析器完成解析。

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

execpolicy-legacy/src/policy_parser.rs源码 ↗
configconfig load

这个文件像一个“策略翻译员”。外部人员写的策略不是 Rust 代码,而是 Starlark,一种类似 Python 的配置脚本语言。PolicyParser 先把这份脚本文本解析成 Starlark 能执行的结构,然后准备一批给策略脚本使用的内置名字,比如 ARG_RFILE 表示“这个参数应该是可读文件”。脚本执行时,如果调用 define_program、forbid_substrings、forbid_program_regex 这些函数,实际不是马上生成最终策略,而是把内容暂存在 PolicyBuilder 里。PolicyBuilder 就像一张草稿表:一边收集允许的程序规则,一边收集禁止的字符串和禁止的程序名正则表达式(正则表达式就是按模式匹配文字的规则)。脚本跑完后,PolicyBuilder 再把草稿合成真正的 Policy。这里还有一个重要检查:同一个程序选项不能重复定义,否则会报错,避免策略里出现自相矛盾的写法。

函数细节8
PolicyParser::new29–34 ↗
fn new(policy_source: &str, unparsed_policy: &str) -> Self

作用:创建一个策略解析器,把“这份策略从哪里来”和“策略正文是什么”先保存起来,等之后真正解析时使用。

数据流:输入是策略来源名称和未解析的策略文本;它把这两段字符串复制到 PolicyParser 里;输出是一个可以继续调用 parse 的解析器对象,不会立刻检查策略内容。

调用关系:这是解析流程的起点之一。外部代码先用它造出 PolicyParser,随后通常会调用 PolicyParser::parse 来把文本变成真正的 Policy。

PolicyParser::parse36–67 ↗
fn parse(&self) -> starlark::Result<Policy>

作用:真正读取并执行策略脚本,把人写的 Starlark 策略转换成系统内部使用的 Policy。它是这个文件最核心的入口。

数据流:输入是 PolicyParser 里保存的策略来源和策略文本;它先设置 Starlark 语法规则,解析文本,再准备策略脚本能调用的内置函数和参数类型常量;执行脚本时把收集到的程序规则、禁用字符串、禁用正则都放进 PolicyBuilder;最后调用构建步骤,输出一个 Policy,或者在脚本语法、正则表达式、规则冲突等地方出错时返回错误。

调用关系:它会创建 PolicyBuilder::new 作为临时收集器,并通过 Starlark 的临时模块运行策略脚本。脚本里调用的 define_program、forbid_substrings、forbid_program_regex 来自 policy_builtins,这些调用会反过来把数据塞进同一个 PolicyBuilder。脚本结束后,它调用 PolicyBuilder::build 生成最终策略。

调用图:调用 1 个内部函数(new);外部调用 3 个(parse, extended_by, with_temp_heap)。

PolicyBuilder::new84–90 ↗
fn new() -> Self

作用:准备一张空的“策略草稿表”,用来暂存脚本执行过程中定义的所有规则。

数据流:它不需要外部输入;内部新建三个空容器:程序规则表、禁止程序名的正则列表、禁止出现的字符串列表;输出是一个空的 PolicyBuilder。

调用关系:PolicyParser::parse 在执行策略脚本前会调用它。之后 policy_builtins 里的内置函数会不断往这个草稿表里添加内容,最后交给 PolicyBuilder::build 收尾。

调用图:外部调用 3 个(new, new, new)。

PolicyBuilder::build92–97 ↗
fn build(self) -> Result<Policy, regex_lite::Error>

作用:把前面收集好的草稿规则打包成正式的 Policy,让后续命令检查代码可以直接使用。

数据流:输入是已经填好内容的 PolicyBuilder;它取出里面的程序规则、禁止正则和禁止字符串;然后调用 Policy::new 做最终组装;输出是 Policy,或者返回正则等策略内容导致的错误。

调用关系:它通常在 PolicyParser::parse 的最后被调用。也就是说,脚本执行阶段只是收集材料,PolicyBuilder::build 才是把材料装订成正式规则书的步骤。

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

PolicyBuilder::add_program_spec99–104 ↗
fn add_program_spec(&self, program_spec: ProgramSpec)

作用:把一个程序的允许执行规则加入草稿表,比如这个程序叫什么、允许哪些选项、参数应该是什么类型。

数据流:输入是一个 ProgramSpec,也就是某个程序的完整规则说明;它先记录一条日志,方便排查“哪些规则被加入了”;然后按程序名把这条规则插入到程序规则表里;输出为空,但 PolicyBuilder 的内部内容变多了。

调用关系:policy_builtins 中的 define_program 会在策略脚本声明一个程序时调用它。它是策略脚本里的“定义程序”动作和最终 Policy 之间的桥梁。

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

PolicyBuilder::add_forbidden_substrings106–109 ↗
fn add_forbidden_substrings(&self, substrings: &[String])

作用:把一批禁止出现在命令里的字符串加入草稿表,用来拦住明显危险或不允许的文字片段。

数据流:输入是一组字符串;它把这些字符串追加到 PolicyBuilder 内部的禁止字符串列表;输出为空,但之后构建出的 Policy 会记住这些禁止项。

调用关系:policy_builtins 中的 forbid_substrings 会调用它。策略脚本作者写下要禁止的文字后,这个函数负责把它们收进最终策略会使用的清单。

PolicyBuilder::add_forbidden_program_regex111–114 ↗
fn add_forbidden_program_regex(&self, regex: Regex, reason: String)

作用:加入一条“按模式禁止程序名”的规则,并保存禁止原因,方便之后判断和解释为什么不允许。

数据流:输入是已经编译好的正则表达式和一段原因说明;它把二者打包成 ForbiddenProgramRegex,追加到内部列表;输出为空,但 PolicyBuilder 会多一条禁止规则。

调用关系:policy_builtins 中的 forbid_program_regex 会先把脚本里的正则文本编译好,再调用它保存规则。它负责保存结果,不负责解析正则文本。

policy_builtins118–222 ↗
fn policy_builtins(builder: &mut GlobalsBuilder)

作用:给 Starlark 策略脚本注册一组“专用命令”,让脚本能用 define_program、opt、flag 等简单写法来描述执行策略。

数据流:输入是 Starlark 的全局函数注册器;它往里面登记几个可被策略脚本调用的函数:define_program 用来定义程序规则,forbid_substrings 用来加入禁止字符串,forbid_program_regex 用来加入禁止程序名模式,opt 和 flag 用来描述命令行选项;输出不是普通返回值,而是改变注册器,让之后执行脚本时这些名字可用。脚本调用这些函数时,会读取传入的程序名、选项、参数匹配器等信息,检查重复选项,并把结果写入 PolicyBuilder。

调用关系:PolicyParser::parse 在准备 Starlark 执行环境时会把它挂进去。之后策略脚本运行到相应函数调用时,这些内置函数会拿到 Evaluator 里保存的 PolicyBuilder,并把具体规则交给 PolicyBuilder::add_program_spec、PolicyBuilder::add_forbidden_substrings 或 PolicyBuilder::add_forbidden_program_regex。

execpolicy-legacy/src/arg_resolver.rs源码 ↗
domain_logic策略检查时

可以把这里想成一个检票员:用户实际带来的参数是一排票,策略里的规则是一排座位要求。这个文件要做的事,就是确认每张票该坐哪个座位,有没有多带、少带,或者规则本身写得矛盾。它支持普通固定数量的参数,也支持一种“可变长参数”(一个规则可以吃掉中间任意多个参数)。为了避免乱配,它先把规则切成三段:可变长参数前面的固定规则、可变长规则本身、后面的固定规则。然后先匹配前面,再给后面预留足够参数,中间剩下的交给可变长规则。最后它会生成 MatchedArg,也就是“已确认的参数记录”。如果参数不够、多了、范围算错,或者出现两个可变长规则,就会返回明确错误。

函数细节3
resolve_observed_args_with_patterns15–145 ↗
fn resolve_observed_args_with_patterns(
    program: &str,
    args: Vec<PositionalArg>,
    arg_patterns: &Vec<ArgMatcher>,
) -> Result<Vec<MatchedArg>>

作用:这是这个文件的主函数,用来把实际命令行参数和策略参数规则配对。别人会用它来判断一次程序执行的参数是否符合预期,并得到每个参数对应的类型。

数据流:进去的是程序名、实际参数列表,以及策略里的参数规则列表。它先让 partition_args 把规则分成前段、可变长段、后段;再用 get_range_checked 安全地切出对应的实际参数;每匹配到一个参数,就用 MatchedArg::new 做成一条“已匹配参数”。出来的是一组 MatchedArg;如果发现参数太少、太多、可变长规则没吃到必须的参数,或者内部范围不合理,就出来一个错误。

调用关系:它是在更外层的 check 流程里被调用的,也就是做执行策略检查时用到。它自己不直接理解所有规则细节,而是先把拆规则的活交给 partition_args,把安全取片段的活交给 get_range_checked,最后把确认好的参数交给 MatchedArg::new 包装成统一结果。

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

partition_args156–188 ↗
fn partition_args(program: &str, arg_patterns: &Vec<ArgMatcher>) -> Result<ParitionedArgs>

作用:这个函数负责把一串参数规则切成好处理的几段。它尤其要找出有没有“可变长参数规则”,并保证这种规则最多只能有一个。

数据流:进去的是程序名和参数规则列表。它从头扫描规则:遇到固定数量的规则,就放到前段或后段;第一次遇到可变长规则,就把它记下来,并从此开始把后面的固定规则当成后段;如果又遇到第二个可变长规则,就返回错误。出来的是一个 ParitionedArgs,里面记录前段规则、后段规则、可变长规则,以及前后两段各需要多少实际参数。

调用关系:它只服务于 resolve_observed_args_with_patterns。主函数在真正匹配参数前,先请它把复杂规则拆开,就像做菜前先把食材分堆。它内部用默认值创建一个空的分组结果,然后边扫描边填进去。

调用图:被 1 处调用(resolve_observed_args_with_patterns);外部调用 1 个(default)。

get_range_checked190–204 ↗
fn get_range_checked(vec: &[T], range: std::ops::Range<usize>) -> Result<&[T]>

作用:这是一个安全切片小工具,用来从列表里取一段内容,但会先检查范围是否合法。它防止程序因为取错范围而直接崩掉。

数据流:进去的是一个列表和一个起止范围。它先检查起点有没有超过终点,再检查终点有没有超过列表长度;这些都没问题,才返回这段列表的引用。出来的是安全取得的片段;如果范围反了或越界,就出来一个清楚的错误。

调用关系:resolve_observed_args_with_patterns 在切出前段参数、可变长参数、后段参数,以及报告多余参数时都会用它。它是流程里的安全护栏,专门防止主匹配逻辑因为算错下标而访问不存在的位置。

调用图:被 1 处调用(resolve_observed_args_with_patterns);外部调用 1 个(len)。

execpolicy-legacy/src/program.rs源码 ↗
domain_logicrequest handling 和规则自检时活跃

这个文件的核心是 ProgramSpec,也就是“程序执行说明书”。里面写着:这个程序叫什么、允许出现在系统哪些路径里、哪些选项可以用、哪些选项必须带值、普通参数要符合什么样子,以及一些正反例。检查时,它会把命令行参数从左到右扫一遍:遇到像 -x 这样的选项,就确认它是不是被允许;如果这个选项需要值,就要求下一个参数必须是值,不能又是另一个选项;普通参数会交给参数匹配器去核对。最后它会确认必填选项都出现了,并组装出 ValidExec,也就是“已经验证过的执行请求”。如果这份规则被标成 forbidden,它不会直接放行,而是返回“匹配到了,但禁止执行”的结果。一个值得注意的点是,代码里目前还不支持单独的 -- 分隔符,也没有真正处理 --option=value 这类写法。

函数细节4
ProgramSpec::new33–66 ↗
fn new(
        program: String,
        system_path: Vec<String>,
        option_bundling: bool,
        combined_format: bool,
        allowed_options: HashMap<String, Opt>,
        arg_patterns: Ve

作用:创建一份新的程序执行规则。调用者把程序名、允许的选项、参数匹配规则、正反例等交进来,它会顺手算出哪些选项是必填的,方便以后检查。

数据流:输入是一堆规则材料:程序名、系统路径、选项表、参数模式、是否禁止、应该通过和不应该通过的例子等。函数会查看 allowed_options,把标记为 required 的选项名收集成一个集合。输出是一个完整的 ProgramSpec,里面既保留原始规则,也带着预先整理好的必填选项列表。

调用关系:这是 ProgramSpec 的组装入口。其他地方先用它把配置变成可检查的规则对象,之后真正检查命令时再调用 ProgramSpec::check。它本身不做命令验证,只负责把规则准备好。

ProgramSpec::check94–195 ↗
fn check(&self, exec_call: &ExecCall) -> Result<MatchedExec>

作用:检查一次具体的程序执行请求是否符合这份规则。它会判断选项是否合法、选项值有没有缺、普通参数是否匹配规则、必填选项是否齐全,最后返回“允许的执行”或“虽然匹配但被禁止”的结果。

数据流:输入是一条 ExecCall,也就是一次想执行的程序和参数列表。函数从左到右读取参数:如果正在等某个选项的值,就把当前参数当作这个值;如果看到已允许的 flag(旗标,只有开关没有值),就记录下来;如果看到需要值的选项,就记住下一项必须是它的值;如果是普通参数,就暂存起来。扫完后,它把普通参数交给 resolve_observed_args_with_patterns 按规则匹配,再检查必填选项是否都出现。成功时输出 MatchedExec::Match,里面装着 ValidExec;如果整条规则被标为禁止,则输出 MatchedExec::Forbidden;中途发现问题就输出具体 Error。

调用关系:这是这个文件的核心判断器。ProgramSpec::verify_should_match_list 和 ProgramSpec::verify_should_not_match_list 都靠它来验证规则样例。它自己会把普通参数匹配的工作交给 resolve_observed_args_with_patterns,并在识别带值选项时创建 MatchedOpt。

调用图:调用 2 个内部函数(resolve_observed_args_with_patterns, new);被 2 处调用(verify_should_match_list, verify_should_not_match_list);外部调用 3 个(new, new, new)。

ProgramSpec::verify_should_match_list197–216 ↗
fn verify_should_match_list(&self) -> Vec<PositiveExampleFailedCheck>

作用:检查规则里那些“应该能通过”的例子是否真的能通过。它用来发现规则写得太严、导致合法命令被误拦的情况。

数据流:输入来自 ProgramSpec 里保存的 should_match 列表。函数把每组参数包装成一条 ExecCall,然后逐条调用 ProgramSpec::check。能通过的例子不做记录;不能通过的例子会被收集成 PositiveExampleFailedCheck,里面包含程序名、参数和失败错误。输出是一组失败记录,空列表表示所有正例都通过了。

调用关系:它通常用于加载或测试规则时的自检。它不重新实现检查逻辑,而是把每个正例交给 ProgramSpec::check,因此它的结果和真实执行检查保持一致。

调用图:调用 1 个内部函数(check);外部调用 1 个(new)。

ProgramSpec::verify_should_not_match_list218–233 ↗
fn verify_should_not_match_list(&self) -> Vec<NegativeExamplePassedCheck>

作用:检查规则里那些“不应该通过”的例子是否真的会被拦住。它用来发现规则写得太松、让危险或不合规命令漏过去的情况。

数据流:输入来自 ProgramSpec 里保存的 should_not_match 列表。函数把每组参数变成 ExecCall,然后调用 ProgramSpec::check。只要某个反例竟然检查成功,就说明规则有漏洞,函数会把它记录成 NegativeExamplePassedCheck。输出是一组漏网记录,空列表表示所有反例都被正确拦下了。

调用关系:它和 ProgramSpec::verify_should_match_list 是一对规则自检工具。两者都依赖 ProgramSpec::check;区别是这个函数关注“坏例子有没有误放行”。

调用图:调用 1 个内部函数(check);外部调用 1 个(new)。

execpolicy-legacy/src/policy.rs源码 ↗
domain_logicrequest handling / policy validation

这个文件里的 Policy 可以理解成门卫手里的检查手册。有人要执行一个命令时,它先看程序名是不是命中“绝对禁止”的正则表达式(正则表达式就是按模式匹配文字的工具),再看每个参数里有没有禁止出现的敏感片段。只要碰到这些红线,就直接判为禁止。没碰红线时,它再按程序名找到对应的 ProgramSpec,也就是这个程序允许怎样被调用的详细说明,然后逐条尝试匹配。这里用 MultiMap,是因为同一个程序名可能有多套允许规则。文件还提供两个自检方法:分别检查“应该通过的例子”有没有失败,以及“应该失败的例子”有没有意外通过,帮助发现策略文件写错。

函数细节4
Policy::new22–42 ↗
fn new(
        programs: MultiMap<String, ProgramSpec>,
        forbidden_program_regexes: Vec<ForbiddenProgramRegex>,
        forbidden_substrings: Vec<String>,
    ) -> std::result::Result<Self, Re

作用:创建一份新的策略对象,把允许规则、禁止的程序名模式、禁止的参数片段装到一起。它还会提前把禁止片段合成一个可快速匹配的正则表达式,方便后面检查命令参数。

数据流:输入是按程序名分组的允许规则、禁止程序名的正则列表、以及一串禁止出现在参数里的文字片段。它先把这些禁止片段逐个转义,避免把普通文字误当成正则语法,再用“或”的方式拼成一个匹配模式;如果列表为空,就不建这个模式。最后输出一个 Policy;如果正则表达式创建失败,就返回错误。

调用关系:这是 Policy 被使用前的组装步骤,通常在策略文件解析完成后调用。它把外部传进来的规则整理成后续 Policy::check 可以直接使用的形态;过程中会调用正则库的创建函数,并用格式化字符串拼出正则文本。

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

Policy::check44–86 ↗
fn check(&self, exec_call: &ExecCall) -> Result<MatchedExec>

作用:检查一次具体的执行请求能不能放行。它会先拦截明显禁止的程序和参数,再尝试用允许规则证明这次调用是合法的。

数据流:输入是一条 ExecCall,里面有要运行的程序名和参数。它先拿程序名去匹配禁止程序名规则;命中就返回 Forbidden。然后逐个检查参数,看是否包含禁止片段;命中也返回 Forbidden。都没命中时,它按程序名取出所有 ProgramSpec,逐个让这些规则检查这次调用;只要有一条通过,就返回匹配结果。若没有对应规则,或所有规则都不接受,就返回最后遇到的错误。

调用关系:这是运行时最核心的判断入口,外部在准备执行命令前会调用它。它自己负责先做全局禁令检查,然后把更细的“这个程序允许哪些参数”交给 ProgramSpec::check;查找同名规则时使用 MultiMap 的 get_vec。

调用图:外部调用 3 个(get_vec, clone, format!)。

Policy::check_each_good_list_individually88–94 ↗
fn check_each_good_list_individually(&self) -> Vec<PositiveExampleFailedCheck>

作用:检查策略里写的“好例子”是否真的能通过。它用于自测规则,防止本来应该允许的命令被规则误伤。

数据流:它不接收额外输入,而是读取 Policy 内部保存的所有 ProgramSpec。它创建一个空的违规列表,然后遍历每条程序规则,把每条规则里“应该匹配”的例子拿去验证;失败的例子都会加入列表。最后输出所有 PositiveExampleFailedCheck,表示哪些好例子没有通过。

调用关系:这个函数通常在加载策略后、正式使用前做健康检查。它通过 flat_iter 走遍 MultiMap 里每个程序的每条规则,再把具体检查交给 ProgramSpec::verify_should_match_list。

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

Policy::check_each_bad_list_individually96–102 ↗
fn check_each_bad_list_individually(&self) -> Vec<NegativeExamplePassedCheck>

作用:检查策略里写的“坏例子”是否真的会被拒绝。它用于发现规则太宽松的问题,避免危险调用被意外放行。

数据流:它读取 Policy 内部所有 ProgramSpec,先准备一个空的违规列表。然后逐条遍历规则,把每条规则里“应该不匹配”的例子拿去验证;如果某个坏例子居然通过了,就记录下来。最后输出所有 NegativeExamplePassedCheck,说明哪些坏例子被错误放行。

调用关系:这个函数和好例子检查是一对,常用于策略自检阶段。它遍历规则集合的方式相同,具体的坏例子验证交给 ProgramSpec::verify_should_not_match_list 完成。

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

旧版执行验证

这个最终的旧版检查器会在执行继续之前,对文件系统访问和可执行文件解析进行匹配后的验证。

execpolicy-legacy/src/execv_checker.rs源码 ↗
domain_logicrequest handling

可以把这个文件想成门卫:有人想执行一个命令,比如复制文件,门卫先看这条命令是不是政策里允许的,再看它要读的文件、要写的文件是不是在批准的区域里。这里的核心是 ExecvChecker。它先用 Policy(政策规则)匹配 ExecCall(一次准备执行的命令),得到 ValidExec(已经按规则识别过的命令)。然后 check 会逐个查看参数:标成“可读文件”的参数必须在 readable_folders 里,标成“可写文件”的参数必须在 writeable_folders 里。相对路径会根据当前工作目录变成绝对路径;如果没有当前目录,就拒绝检查,因为它不知道文件到底在哪里。最后它还会在候选 system_path 里挑一个真实存在、可执行的程序路径返回。这样可以避免命令偷偷读写不该碰的地方,也避免执行到错误的程序。

函数细节7
ExecvChecker::new34–36 ↗
fn new(execv_policy: Policy) -> Self

作用:创建一个 ExecvChecker,把一份命令执行政策放进去。以后所有检查都会按这份政策来判断什么命令能跑、参数该怎么理解。

数据流:进去的是一份 Policy,也就是命令规则表;函数把它保存到新的 ExecvChecker 里面;出来的是一个可以反复用来检查命令的检查器。

调用关系:测试里的 tests::setup 会调用它来搭一个检查器。真实流程里,它通常会在系统准备好政策规则后被创建,后面 ExecvChecker::r#match 和 ExecvChecker::check 都靠这个检查器工作。

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

ExecvChecker::r#match38–40 ↗
fn r#match(&self, exec_call: &ExecCall) -> Result<MatchedExec>

作用:把一次用户想执行的命令拿去和政策规则对照,看它是不是规则允许的命令。它不做文件夹权限检查,只负责先判断“这条命令像不像一条被批准的命令”。

数据流:进去的是 ExecCall,里面有程序名和参数;函数把它交给内部的 Policy.check;出来的是 MatchedExec,表示匹配成功、匹配失败,或者其他匹配结果。

调用关系:它位于检查流程的第一步。tests::test_check_valid_input_files 先调用它拿到 ValidExec,之后才把 ValidExec 交给 ExecvChecker::check 做更细的读写路径检查。

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

ExecvChecker::check44–98 ↗
fn check(
        &self,
        valid_exec: ValidExec,
        cwd: &Option<OsString>,
        readable_folders: &[PathBuf],
        writeable_folders: &[PathBuf],
    ) -> Result<String>

作用:对一条已经匹配政策的命令做真正的文件访问安全检查。它确认所有要读的文件都在允许读取的文件夹里,所有要写的文件都在允许写入的文件夹里,并找出最终应该执行的程序路径。

数据流:进去的是 ValidExec、当前工作目录 cwd、允许读取的文件夹列表、允许写入的文件夹列表;函数逐个查看命令参数和选项,把文件路径变成绝对路径,再检查它们是否位于对应的允许文件夹下;最后从 system_path 里找第一个真实可执行的文件,出来的是这个程序路径字符串。如果路径不合法或越界,就返回对应错误。

调用关系:它是本文件最核心的检查步骤。它会调用 ensure_absolute_path 解决相对路径问题,调用 check_file_in_folders! 这个小检查规则确认文件没有跑出允许范围,还会调用 is_executable_file 选择可执行程序。测试函数 tests::test_check_valid_input_files 用多种情况验证它会在缺少权限文件夹时拒绝、权限足够时通过。

调用图:调用 2 个内部函数(ensure_absolute_path, is_executable_file);外部调用 1 个(check_file_in_folders!)。

ensure_absolute_path101–117 ↗
fn ensure_absolute_path(path: &str, cwd: &Option<OsString>) -> Result<PathBuf>

作用:把传进来的文件路径整理成绝对路径,也就是从文件系统根部开始的明确位置。这样后续才能可靠判断它是不是在某个允许的文件夹里。

数据流:进去的是一个路径字符串和可选的当前工作目录;如果路径本来就是绝对路径,它会直接规范化;如果是相对路径,它会用当前工作目录补全;如果没有当前工作目录,就返回“不能检查相对路径”的错误。出来的是整理好的 PathBuf,或者是路径无法规范化的错误。

调用关系:它只被 ExecvChecker::check 调用。check 在检查每个可读或可写文件前都先找它,把模糊的路径变成清楚的位置,避免像“../secret”这种写法绕过文件夹限制。

调用图:被 1 处调用(check);外部调用 1 个(from)。

is_executable_file119–140 ↗
fn is_executable_file(path: &str) -> bool

作用:判断某个路径是不是真的指向一个可以执行的程序文件。这样系统不会随便返回一个不存在的路径或普通不可执行文件来运行。

数据流:进去的是一个路径字符串;函数读取这个路径的文件信息;在 Unix 系统上,它会确认这是普通文件,并且权限里有可执行位;在 Windows 上,目前只确认它是普通文件。出来的是 true 或 false,表示能不能当作可执行程序使用。

调用关系:它被 ExecvChecker::check 在最后阶段调用。check 会遍历 ValidExec 里的 system_path 候选列表,找到第一个 is_executable_file 认可的路径,然后把它作为最终要执行的程序返回。

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

tests::setup152–165 ↗
fn setup(fake_cp: &Path) -> ExecvChecker

作用:给测试准备一个只允许执行 cp 命令的临时政策和检查器。它让测试不用依赖外部配置文件,就能稳定地验证检查逻辑。

数据流:进去的是一个假的 cp 程序路径;函数把这个路径写进一段政策文本,创建 PolicyParser 解析它,再用解析出的 Policy 创建 ExecvChecker;出来的是一个测试专用的检查器。

调用关系:它只服务于测试。tests::test_check_valid_input_files 会先创建假的可执行文件,再调用 tests::setup 得到检查器,然后用这个检查器跑匹配和路径权限检查。

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

tests::test_check_valid_input_files168–294 ↗
fn test_check_valid_input_files() -> Result<()>

作用:验证 ExecvChecker::check 对读文件、写文件、允许文件夹、可执行程序路径这些关键情况的判断是否正确。它像一组小剧场,模拟命令被允许和被拒绝的不同场景。

数据流:进去没有外部输入;测试自己创建临时目录和假的 cp 程序,再构造源文件路径、目标文件路径、当前工作目录和命令参数;它先匹配命令,再多次调用 checker.check;出来不是业务结果,而是用断言确认结果必须等于预期,错了测试就失败。

调用关系:它是本文件的安全网。它调用 tests::setup 搭检查器,间接使用 ExecvChecker::new;随后调用 ExecvChecker::r#match 和 ExecvChecker::check,覆盖没有允许文件夹、只允许读、不允许写、读写都允许、以及试图读取允许文件夹父目录等情况。

调用图:外部调用 8 个(default, new, assert_eq!, setup, panic!, create, set_permissions, vec!)。