Codex 系统手册

旧版和当前 execpolicy 可执行文件测试

stage-23.3.413 个文件

这一阶段不是系统正式干活儿,而是给“能不能执行命令”的安全门做体检。当前测试会直接跑 execpolicy check,看它是否用 JSON 正确回答放行、提醒或禁止,也检查网络规则。旧版测试像一套老题库:good、bad 先查默认规则的大方向,cp、ls、pwd、head、sed 等再逐个盯常见命令的参数细节。all 和 mod 负责把这些题目串起来一起跑,防止新旧策略被改坏。

本阶段的文件13

当前 execpolicy 集成

这些测试覆盖当前 execpolicy 的运行时和面向 CLI 的集成行为,从核心解析器/运行时场景到可执行 JSON 输出检查。

cli/tests/execpolicy.rs源码 ↗
testtest

这个测试文件像是在给命令行工具做一次小型“安检演练”。它先临时建一个假的 Codex 主目录,再写入一份规则文件,规则说:只要命令开头是 git push,就判定为 forbidden,也就是禁止执行。然后测试真正启动 codex 这个命令行程序,运行 execpolicy check,让它检查 git push origin main。程序输出后,测试把标准输出里的 JSON(常见的数据交换格式,可以理解成机器容易读的结构化文本)解析出来,和预期结果逐字比较。第二个测试还多检查了 justification,也就是“为什么禁止”的说明文字,确保规则里写了理由时,输出里也会带上。这里重要的不只是判断对不对,还包括 JSON 字段名、匹配到的前缀、禁止原因这些对外承诺的格式不能乱变。

函数细节2
execpolicy_check_matches_expected_json9–61 ↗
fn execpolicy_check_matches_expected_json() -> Result<(), Box<dyn std::error::Error>>

作用:这个测试确认:当规则禁止 git push 时,codex execpolicy check 会成功运行,并输出预期的 JSON。它主要守住“基本禁止规则能匹配、输出格式正确”这件事。

数据流:进去的是一份临时写好的规则文件,内容说命令前缀 git push 要被禁止;测试还传入要检查的命令 git push origin main。它创建临时目录,写规则文件,启动真实的 codex 可执行程序,把规则路径和命令参数交给它;然后读取程序的标准输出,把 JSON 文本转成可比较的数据结构。出来的是一次断言结果:程序必须退出成功,并且输出必须等于包含 decision: forbidden 和匹配规则详情的 JSON;测试本身只改动临时目录里的测试文件。

调用关系:这是测试框架在跑测试时直接调用的函数。它把准备测试环境的活交给临时目录创建、目录创建和文件写入这些外部工具,把运行程序的活交给 assert_cmd::Commandcargo_bin,把检查输出的活交给 JSON 解析和断言宏。它不被业务代码调用,而是站在外部用户角度验证命令行行为。

调用图:外部调用 8 个(new, assert!, assert_eq!, new, cargo_bin, create_dir_all, write, from_slice)。

execpolicy_check_includes_justification_when_present64–119 ↗
fn execpolicy_check_includes_justification_when_present() -> Result<(), Box<dyn std::error::Error>>

作用:这个测试确认:如果规则里写了禁止理由,codex execpolicy check 的 JSON 输出也会带上这段理由。它防止“判断结果还在,但解释原因丢了”的问题。

数据流:进去的是一份临时规则文件,规则同样禁止 git push,但额外带有 justification,也就是说明文字“pushing is blocked in this repo”。测试创建目录、写文件、启动真实的 codex 命令,并把 git push origin main 交给它检查;随后把输出 JSON 解析成数据。出来的是一次断言结果:程序必须成功退出,输出里除了 decision: forbidden 和匹配到的 git push 前缀,还必须包含同一段禁止理由;它只在临时测试目录里留下中间文件,测试结束后会由临时目录机制清理。

调用关系:这是另一个面向命令行外部行为的测试,由测试运行器调用。它和前一个测试走同一条流程:准备规则文件,调用真实 codex 程序,再解析并比较 JSON;不同点是它专门覆盖“规则带解释文字”这个分支,确保底层策略匹配逻辑和最终输出格式能把理由一路传出来。

调用图:外部调用 8 个(new, assert!, assert_eq!, new, cargo_bin, create_dir_all, write, from_slice)。

execpolicy/tests/basic.rs源码 ↗
testtest run

这个文件不负责正式运行策略,而是专门“挑刺”。它会造出一段段规则文本,比如允许 git status、禁止 rm、限制网络域名、把 /usr/bin/git 识别成 git,然后交给 PolicyParser 解析,再用 Policy 去检查命令,最后对比结果是不是完全正确。这里还测试很多容易出错的边角:重复追加规则不能重复写入;多个规则同时命中时,最严格的结果要赢;没有规则时要走启发式判断(也就是外部传进来的默认判断函数);主机可执行文件路径必须是绝对路径,还要和名字对得上。整体作用就像给安全门配的一套试钥匙:每把钥匙都试过,才知道门锁没坏。

函数细节35
tokens26–28 ↗
fn tokens(cmd: &[&str]) -> Vec<String>

作用:把测试里写的命令片段从字符串切片变成真正的字符串列表。这样测试可以用很短的写法表达一条命令,比如 git status。

数据流:进去的是一组借用的文本片段 → 它逐个复制成 String → 出来的是 Vec<String>,供策略检查和断言使用,不改动外部状态。

调用关系:很多测试先用它准备命令输入,例如 basic_match、add_prefix_rule_extends_policy、heuristics_match_is_returned_when_no_policy_matches 等,然后把结果交给 Policy 的检查函数或拿来和匹配结果比较。

调用图:被 11 处调用(add_prefix_rule_extends_policy, append_allow_prefix_rule_dedupes_existing_rule, basic_match, heuristics_match_is_returned_when_no_policy_matches, justification_can_be_used_with_allow_decision, justification_is_attached_to_forbidden_matches, match_and_not_match_examples_are_enforced, only_first_token_alias_expands_to_multiple_rules, parses_multiple_policy_files, strictest_decision_wins_across_matches (+1 more))。

allow_all30–32 ↗
fn allow_all(_: &[String]) -> Decision

作用:这是测试用的默认判断函数,意思是“如果没有规则管这条命令,就放行”。它让测试能清楚地区分规则命中和默认放行。

数据流:进去的是一条命令列表,但它不查看内容 → 直接返回 Decision::Allow → 不产生其他副作用。

调用关系:它通常作为回调传给 policy.check 或 check_multiple;当策略没有明确匹配,Policy 会问它该怎么办。

prompt_all34–36 ↗
fn prompt_all(_: &[String]) -> Decision

作用:这是另一个测试用默认判断函数,意思是“如果没有规则管这条命令,就要求询问用户”。它用来测试默认判断不是固定允许的情况。

数据流:进去的是命令列表,但内容被忽略 → 直接返回 Decision::Prompt → 外部状态不变。

调用关系:它被传给 Policy 的检查流程,尤其用于确认没有规则命中时,结果会按外部传入的默认策略走。

absolute_path38–40 ↗
fn absolute_path(path: &str) -> AbsolutePathBuf

作用:把普通字符串包装成项目认可的“绝对路径”类型。测试用它来和策略内部保存的路径做精确比较。

数据流:进去的是一个路径字符串 → 调用 try_from 检查它是不是绝对路径并转换 → 出来是 AbsolutePathBuf;如果测试给错路径会直接失败。

调用关系:主机可执行文件相关测试会用它构造期望结果,再和 policy.host_executables 或匹配结果里的 resolved_program 对比。

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

host_absolute_path42–52 ↗
fn host_absolute_path(segments: &[&str]) -> String

作用:按当前操作系统拼出一个绝对路径,避免 Windows 和 Unix 路径格式不同导致测试失败。它让同一份测试能跨平台运行。

数据流:进去的是路径各段,比如 usr、bin、git → 先按系统选择 C:\ 或 / 作为开头,再逐段拼接 → 出来是适合本机格式的路径字符串。

调用关系:主机可执行文件的一系列测试都会先用它造路径,再交给 starlark_string 写进规则文本,或者直接作为待检查命令传给策略。

调用图:被 10 处调用(host_executable_last_definition_wins, host_executable_rejects_name_with_path_separator, host_executable_rejects_path_with_wrong_basename, host_executable_resolution_does_not_override_exact_match, host_executable_resolution_falls_back_without_mapping, host_executable_resolution_ignores_path_not_in_allowlist, host_executable_resolution_respects_explicit_empty_allowlist, host_executable_resolution_uses_basename_rule_when_allowed, parses_host_executable_paths, prefix_rule_examples_honor_host_executable_resolution);外部调用 2 个(from, cfg!)。

host_executable_name54–60 ↗
fn host_executable_name(name: &str) -> String

作用:生成当前系统上的可执行文件名。Windows 上程序通常带 .exe,其他系统一般不带,所以测试不能写死。

数据流:进去的是基础名字,比如 git → 根据 cfg! 判断系统 → Windows 返回 git.exe,其他系统返回 git。

调用关系:host_executable_resolution_uses_basename_rule_when_allowed 和 prefix_rule_examples_honor_host_executable_resolution 用它保证路径末尾名字符合本机习惯。

调用图:被 2 处调用(host_executable_resolution_uses_basename_rule_when_allowed, prefix_rule_examples_honor_host_executable_resolution);外部调用 2 个(cfg!, format!)。

starlark_string62–64 ↗
fn starlark_string(value: &str) -> String

作用:把普通字符串转成能安全放进规则脚本里的字符串内容。主要是转义反斜杠和双引号,避免路径把规则文本弄坏。

数据流:进去的是原始字符串 → 把 \ 变成 \\,把 " 变成 \" → 出来是可嵌入规则文本的字符串。

调用关系:凡是测试需要把本机路径写进规则源码,通常都会先调用它,再用 format! 拼成 policy_src。

调用图:被 8 处调用(host_executable_last_definition_wins, host_executable_rejects_name_with_path_separator, host_executable_rejects_path_with_wrong_basename, host_executable_resolution_does_not_override_exact_match, host_executable_resolution_ignores_path_not_in_allowlist, host_executable_resolution_uses_basename_rule_when_allowed, parses_host_executable_paths, prefix_rule_examples_honor_host_executable_resolution)。

rule_snapshots71–83 ↗
fn rule_snapshots(rules: &[RuleRef]) -> Vec<RuleSnapshot>

作用:把策略内部保存的规则引用,转成测试容易比较的快照。它目前只接受前缀规则,遇到别的规则会直接报错。

数据流:进去的是一组 RuleRef → 它逐个尝试识别成 PrefixRule 并复制出来 → 出来是 RuleSnapshot 列表;如果出现意外规则类型,测试会 panic。

调用关系:add_prefix_rule_extends_policy、parses_multiple_policy_files、only_first_token_alias_expands_to_multiple_rules 和 tail_aliases_are_not_cartesian_expanded 用它检查解析后内部规则长什么样。

调用图:被 4 处调用(add_prefix_rule_extends_policy, only_first_token_alias_expands_to_multiple_rules, parses_multiple_policy_files, tail_aliases_are_not_cartesian_expanded);外部调用 1 个(iter)。

append_allow_prefix_rule_dedupes_existing_rule86–101 ↗
fn append_allow_prefix_rule_dedupes_existing_rule() -> Result<()>

作用:测试向规则文件追加同一条“允许前缀规则”两次时,文件里只保留一条。这样用户反复授权同一个命令,不会把规则文件写成一堆重复内容。

数据流:先创建临时目录和规则路径 → 用 tokens 造出 python3 前缀,两次调用 blocking_append_allow_prefix_rule → 读取文件内容并确认只有一行规则。

调用关系:它直接覆盖“写规则文件”这个功能,调用文件追加函数和磁盘读取函数,用 assert_eq! 验证最终文件内容。

调用图:调用 1 个内部函数(tokens);外部调用 4 个(assert_eq!, blocking_append_allow_prefix_rule, read_to_string, tempdir)。

network_rules_compile_into_domain_lists104–128 ↗
fn network_rules_compile_into_domain_lists() -> Result<()>

作用:测试网络规则能被解析,并能整理成“允许域名列表”和“禁止域名列表”。这对后续网络拦截或放行很重要。

数据流:进去是一段包含四条 network_rule 的规则文本 → PolicyParser 解析并 build 成 Policy → 检查规则数量、协议类型,以及 compiled_network_domains 输出的允许和禁止域名。

调用关系:它从解析器入口 new 开始,走完整的规则解析和策略构建流程,最后验证 Policy 暴露出的网络规则结果。

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

network_rule_rejects_wildcard_hosts131–140 ↗
fn network_rule_rejects_wildcard_hosts()

作用:测试网络规则不允许写通配主机名 *。这是为了防止一条规则不小心放开整个网络。

数据流:输入一条 host="*" 的 network_rule → 解析器尝试解析 → 期望得到错误,并检查错误文字包含“不允许通配符”。

调用关系:它专门压测 PolicyParser 的校验逻辑;如果以后解析器放过了通配符,这个测试会立刻失败。

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

basic_match143–167 ↗
fn basic_match() -> Result<()>

作用:测试最基本的前缀规则:规则写 git status,命令也是 git status,就应该命中并允许。

数据流:先解析一条 prefix_rule → 构建 Policy → 用 tokens 生成命令 → policy.check 返回 Evaluation,里面应包含允许结果和匹配到的前缀。

调用关系:这是前缀匹配的基础样例,调用 PolicyParser 和 Policy.check,allow_all 只作为没有规则时的兜底。

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

justification_is_attached_to_forbidden_matches170–199 ↗
fn justification_is_attached_to_forbidden_matches() -> Result<()>

作用:测试禁止规则里的说明文字会保留下来。这样系统禁止危险命令时,不只是说“不行”,还能告诉人原因。

数据流:输入一条禁止 rm 的规则,并带 justification → 检查 rm -rf 命令 → 输出应是 Forbidden,匹配结果里带上“destructive command”。

调用关系:它验证解析器、策略存储和匹配结果三段都没有丢失 justification 字段。

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

justification_can_be_used_with_allow_decision202–228 ↗
fn justification_can_be_used_with_allow_decision() -> Result<()>

作用:测试允许规则也可以带说明文字。说明文字不只属于禁止规则,也可以解释为什么某命令被认为安全。

数据流:解析一条允许 ls 的规则并带说明 → 用 prompt_all 作为兜底检查 ls -l → 规则命中后结果是 Allow,说明文字也出现在匹配结果中。

调用关系:它和 forbidden 的说明测试互补,确认不同 Decision 下 justification 都能正常传递。

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

justification_cannot_be_empty231–247 ↗
fn justification_cannot_be_empty()

作用:测试说明文字不能只是空白。否则看起来有说明,实际上没有任何有用信息。

数据流:输入一条 justification 为多个空格的规则 → PolicyParser 解析时应失败 → 检查错误信息说明 justification 不能为空。

调用关系:它守住规则文件的质量门槛,确保解析阶段就拒绝无意义的说明。

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

add_prefix_rule_extends_policy250–281 ↗
fn add_prefix_rule_extends_policy() -> Result<()>

作用:测试直接往空策略里添加前缀规则后,策略内部会多出这条规则,并且检查命令时能命中它。

数据流:先创建空 Policy → 添加 ls -l 的 Prompt 规则 → 用 rule_snapshots 查看内部规则 → 再检查 ls -l 路径命令,输出应是 Prompt 匹配。

调用关系:它不走规则文本解析,而是直接测试 Policy::add_prefix_rule 这条程序接口,再用 Policy.check 验证效果。

调用图:调用 2 个内部函数(rule_snapshots, tokens);外部调用 2 个(assert_eq!, empty)。

add_prefix_rule_rejects_empty_prefix284–293 ↗
fn add_prefix_rule_rejects_empty_prefix() -> Result<()>

作用:测试不能添加空前缀规则。空前缀等于什么命令都可能匹配,会让策略含义变得危险又模糊。

数据流:创建空 Policy → 尝试添加空列表作为规则前缀 → 得到 Error::InvalidPattern,并确认错误信息是 prefix cannot be empty。

调用关系:它专门检查 add_prefix_rule 的输入校验;如果错误类型不对,会用 panic! 明确指出。

调用图:外部调用 3 个(assert_eq!, empty, panic!)。

parses_multiple_policy_files296–373 ↗
fn parses_multiple_policy_files() -> Result<()>

作用:测试解析器可以连续读取多个规则文件,并把规则合并到同一个策略里。实际使用中,系统规则和用户规则常常来自不同文件。

数据流:输入 first.rules 和 second.rules 两段文本 → 同一个 PolicyParser 连续 parse 后 build → 检查 git 下有两条规则,并分别测试 git status 和 git commit 的结果。

调用关系:它验证 PolicyParser 的累积解析行为,也验证多个匹配同时出现时 Evaluation 会列出所有命中的规则。

调用图:调用 3 个内部函数(new, rule_snapshots, tokens);外部调用 1 个(assert_eq!)。

only_first_token_alias_expands_to_multiple_rules376–444 ↗
fn only_first_token_alias_expands_to_multiple_rules() -> Result<()>

作用:测试规则第一个词如果写成多个别名,比如 bash 或 sh,会拆成多条入口规则。这样两个程序名都能找到同一套后续匹配。

数据流:解析 pattern = [["bash", "sh"], ["-c", "-l"]] → 构建策略后分别查看 bash 和 sh 的规则 → 再检查 bash -c 与 sh -l 都应允许。

调用关系:它用 rule_snapshots 看内部拆分结果,用 tokens 和 Policy.check 验证拆分后的规则确实可用。

调用图:调用 3 个内部函数(new, rule_snapshots, tokens);外部调用 1 个(assert_eq!)。

tail_aliases_are_not_cartesian_expanded447–508 ↗
fn tail_aliases_are_not_cartesian_expanded() -> Result<()>

作用:测试第一个词后面的别名不会被展开成很多组合规则,而是作为“这个位置可以是这些值之一”保存。这样规则数量不会爆炸。

数据流:解析 npm 后面带两个别名位置的规则 → 查看内部只有一条 npm 规则,后续 token 是 Alts → 检查 npm i --legacy-peer-deps 和 npm install --no-save 都能命中。

调用关系:它和第一个词别名展开测试形成对比,说明策略内部对命令名和后续参数采用不同保存方式。

调用图:调用 3 个内部函数(new, rule_snapshots, tokens);外部调用 1 个(assert_eq!)。

match_and_not_match_examples_are_enforced511–554 ↗
fn match_and_not_match_examples_are_enforced() -> Result<()>

作用:测试规则里的示例真的会被检查:match 示例必须能匹配,not_match 示例必须不能匹配。这样规则作者写错样例时会被发现。

数据流:解析带 match 和 not_match 的 git status 规则 → 检查正常 git status 命中前缀规则 → 检查带 --config 的命令不应命中该规则,只走默认启发式允许。

调用关系:它验证解析期示例校验和运行期匹配行为一致,调用 PolicyParser、tokens 和 Policy.check 完成整个流程。

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

strictest_decision_wins_across_matches557–594 ↗
fn strictest_decision_wins_across_matches() -> Result<()>

作用:测试同一条命令命中多条规则时,最严格的决定最终胜出。比如 prompt 和 forbidden 同时命中,结果必须是 forbidden。

数据流:解析 git 为 Prompt、git commit 为 Forbidden 两条规则 → 检查 git commit -m hi → Evaluation 里列出两条匹配,但总 decision 是 Forbidden。

调用关系:它关注 Policy.check 的合并规则,不只是看是否匹配,还看多个匹配怎样合成最终判断。

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

strictest_decision_across_multiple_commands597–645 ↗
fn strictest_decision_across_multiple_commands() -> Result<()>

作用:测试一次检查多条命令时,也要取最严格的总结果。只要其中一条命令被禁止,整体就不能算安全。

数据流:准备两条命令 git status 和 git commit -m hi → 调用 policy.check_multiple → 它合并每条命令的匹配结果,最终输出 Forbidden,并保留所有匹配记录。

调用关系:它使用 check_multiple 这条批量检查路径,和单命令的 strictest_decision_wins_across_matches 互相补充。

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

heuristics_match_is_returned_when_no_policy_matches648–663 ↗
fn heuristics_match_is_returned_when_no_policy_matches()

作用:测试没有任何策略规则命中时,系统会返回一个启发式匹配记录。启发式就是“交给外部默认判断函数来猜该怎么办”。

数据流:创建空 Policy → 检查 python 命令,并把 prompt_all 作为默认判断 → 输出 Decision::Prompt,matched_rules 里记录 HeuristicsRuleMatch。

调用关系:它直接测试 Policy.check 的兜底路径,说明即使没有规则,Evaluation 也会解释结果来自哪里。

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

parses_host_executable_paths666–696 ↗
fn parses_host_executable_paths() -> Result<()>

作用:测试 host_executable 能正确读取主机上的可执行文件路径,并去掉重复路径。它用于把真实路径和规则里的短命令名联系起来。

数据流:先造 homebrew git 和 /usr/bin/git 这类绝对路径 → 写进 host_executable 规则且故意重复一个 → 解析后检查 policy.host_executables 里只保留去重后的两个绝对路径。

调用关系:它把 host_absolute_path、starlark_string、absolute_path 串起来,验证 PolicyParser 对 host_executable 的正常解析结果。

调用图:调用 3 个内部函数(new, host_absolute_path, starlark_string);外部调用 2 个(assert_eq!, format!)。

host_executable_rejects_non_absolute_path699–711 ↗
fn host_executable_rejects_non_absolute_path()

作用:测试 host_executable 的路径必须是绝对路径。相对路径会受当前目录影响,不适合作安全判断。

数据流:输入 paths = ["git"] 的规则 → 解析器尝试解析 → 期望失败,并检查错误说明路径必须是绝对路径。

调用关系:它验证 host_executable 的基础安全校验,防止策略接受含义不稳定的路径。

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

host_executable_rejects_name_with_path_separator714–727 ↗
fn host_executable_rejects_name_with_path_separator()

作用:测试 host_executable 的 name 只能是单纯程序名,不能带路径分隔符。name 是别名,不应该伪装成路径。

数据流:造一个 git 的绝对路径,并把它错误地放到 name 里 → 解析器返回错误 → 检查错误说明 name 必须是裸可执行文件名。

调用关系:它用 host_absolute_path 和 starlark_string 构造跨平台输入,专门检查解析器对 name 字段的限制。

调用图:调用 3 个内部函数(new, host_absolute_path, starlark_string);外部调用 2 个(assert!, format!)。

host_executable_rejects_path_with_wrong_basename730–739 ↗
fn host_executable_rejects_path_with_wrong_basename()

作用:测试 host_executable 的路径末尾文件名必须和 name 对得上。比如 name 是 git,路径却指向 rg,就应该拒绝。

数据流:构造 /usr/bin/rg 这样的路径,却写 host_executable(name = "git") → 解析失败 → 错误信息应提示 basename 必须是 git。

调用关系:它保护路径映射不被写错,避免策略把一个程序的规则误套到另一个程序上。

调用图:调用 3 个内部函数(new, host_absolute_path, starlark_string);外部调用 2 个(assert!, format!)。

host_executable_last_definition_wins742–767 ↗
fn host_executable_last_definition_wins() -> Result<()>

作用:测试同一个可执行文件名被定义多次时,后面的定义覆盖前面的定义。这让用户规则可以覆盖共享规则。

数据流:先解析 shared.rules 里的 git 路径,再解析 user.rules 里的另一个 git 路径 → build 后检查 host_executables 中只剩后一个定义。

调用关系:它验证多文件解析时 host_executable 的覆盖规则,和 parses_multiple_policy_files 的“规则累积”形成对照。

调用图:调用 3 个内部函数(new, host_absolute_path, starlark_string);外部调用 2 个(assert_eq!, format!)。

host_executable_resolution_uses_basename_rule_when_allowed770–804 ↗
fn host_executable_resolution_uses_basename_rule_when_allowed() -> Result<()>

作用:测试开启路径解析后,允许名单里的 /usr/bin/git 可以按规则里的 git 来匹配。这样用户写规则时不用列出每个完整路径。

数据流:规则里写 prefix_rule git status,并声明 git 对应某个绝对路径 → 检查命令用完整路径加 status → 输出应命中 git status 规则,resolved_program 记录原始绝对路径。

调用关系:它调用 check_with_options 并打开 resolve_host_executables,专门覆盖完整路径转短名字的主流程。

调用图:调用 4 个内部函数(new, host_absolute_path, host_executable_name, starlark_string);外部调用 2 个(assert_eq!, format!)。

prefix_rule_examples_honor_host_executable_resolution807–828 ↗
fn prefix_rule_examples_honor_host_executable_resolution() -> Result<()>

作用:测试 prefix_rule 的 match 和 not_match 示例也遵守主机可执行文件解析规则。也就是说,示例校验和真实检查用同一套认路径逻辑。

数据流:构造一个允许的 git 路径和另一个不允许的 git 路径 → 在规则示例里分别写入 match 和 not_match → 解析应成功,说明示例判断符合 host_executable 映射。

调用关系:它主要走 PolicyParser 的解析和示例校验流程,使用 host_executable_name、host_absolute_path 和 starlark_string 准备跨平台规则文本。

调用图:调用 4 个内部函数(new, host_absolute_path, host_executable_name, starlark_string);外部调用 1 个(format!)。

host_executable_resolution_respects_explicit_empty_allowlist831–859 ↗
fn host_executable_resolution_respects_explicit_empty_allowlist() -> Result<()>

作用:测试如果 host_executable 明确写了空路径列表,就表示“不允许任何路径映射”。这和没写映射不是一回事。

数据流:规则里有 git 的前缀规则,但 host_executable(name="git", paths=[]) → 用完整路径 /usr/bin/git 检查 → 不应映射成 git,只走默认 allow_all。

调用关系:它验证 check_with_options 的路径解析会尊重空白名单,不会因为 basename 是 git 就擅自匹配。

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

host_executable_resolution_ignores_path_not_in_allowlist862–894 ↗
fn host_executable_resolution_ignores_path_not_in_allowlist() -> Result<()>

作用:测试只有白名单里的完整路径才能映射成短程序名。路径末尾同样叫 git,但不在名单里,也不能套用 git 规则。

数据流:规则允许 /usr/bin/git 映射为 git → 实际检查另一个 /opt/homebrew/bin/git → 输出不命中前缀规则,而是走默认启发式允许。

调用关系:它和 uses_basename_rule_when_allowed 配对,说明路径解析既能放行白名单路径,也会拒绝白名单外路径。

调用图:调用 3 个内部函数(new, host_absolute_path, starlark_string);外部调用 2 个(assert_eq!, format!)。

host_executable_resolution_falls_back_without_mapping897–926 ↗
fn host_executable_resolution_falls_back_without_mapping() -> Result<()>

作用:测试没有写 host_executable 映射时,完整路径仍可退回到文件名匹配。也就是 /usr/bin/git 还能按 git 规则检查。

数据流:只写 prefix_rule git → 检查完整路径 git 加 status,并开启路径解析 → 系统从路径取出 basename git,命中规则,resolved_program 记录原路径。

调用关系:它说明 host_executable 不是必须配置;当没有显式映射时,check_with_options 会采用较宽松的 basename 退路。

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

host_executable_resolution_does_not_override_exact_match929–963 ↗
fn host_executable_resolution_does_not_override_exact_match() -> Result<()>

作用:测试完整路径本身有精确规则时,不能被短名字映射规则抢走。更具体的原样匹配应该优先。

数据流:规则同时写完整路径 allow 和 git prompt,并声明该路径映射为 git → 检查完整路径命令 → 输出命中完整路径规则,结果是 Allow,resolved_program 为空。

调用关系:它验证 Policy.check_with_options 的优先级:先看命令原样是否有规则,再考虑把路径解析成短程序名。

调用图:调用 3 个内部函数(new, host_absolute_path, starlark_string);外部调用 2 个(assert_eq!, format!)。

旧版套件入口点

这些文件将旧版 execpolicy 集成语料组装成一个有组织的测试套件和二进制入口点。

execpolicy-legacy/tests/all.rs源码 ↗
testtest run

这个文件很短,但作用很明确:它像一本测试目录的“总目录”。Rust 的集成测试通常放在 tests/ 下面,每个测试文件会被当成一个单独的测试程序。这里用 mod suite; 告诉编译器:请把 tests/suite/ 里的测试模块接进来。这样项目的测试可以按主题拆成多个小文件,方便维护;同时又通过这个 all.rs 统一编译、统一运行。可以把它想成一个总开关:它自己不写具体测试题目,只负责把后面的测试集合打开。

execpolicy-legacy/tests/suite/mod.rs源码 ↗
testtest discovery

这个文件本身不写具体测试,也不做判断。它的作用像一本书的目录:告诉 Rust 的测试系统,这里有 bad、cp、good、head、literal、ls、parse_sed_command、pwd、sed 这些测试模块。没有它,这些拆开的测试文件可能不会被编译进同一个测试套件里,测试命令也就跑不到它们。这里的 mod 可以理解成“把另一个同目录下的测试文件接进来”。这样做的好处是,每类命令或场景都有自己的文件,结构清楚;同时又能通过这个总入口统一组织,方便维护旧版 execpolicy 的集成测试。

旧版语料回归

这些回归测试验证精选的默认策略 good 和 bad 命令语料仍会按预期被接受和拒绝。

execpolicy-legacy/tests/suite/bad.rs源码 ↗
testtest run

这个测试文件像一张“黑名单抽查表”。项目里有一套默认执行策略,用来判断某些操作能不能执行;同时也准备了一批反面例子,也就是应该被拦下的坏情况。这个文件做的事很直接:先加载默认策略,再让策略逐个检查这些坏例子,最后确认没有任何坏例子通过检查。这里的“违规结果”反过来看很重要:如果列表里出现内容,说明某个本该被拒绝的例子被放行了,测试就会失败。这样一来,开发者每次改策略时都能及时发现安全边界被弄松了。

函数细节1
verify_everything_in_bad_list_is_rejected5–9 ↗
fn verify_everything_in_bad_list_is_rejected()

作用:这个测试函数确认默认策略会拦住所有坏例子。有人修改执行策略后,可以靠它发现“危险操作被误放行”的问题。

数据流:进入时没有外部参数;它先调用 get_default_policy 读取默认策略,如果读不到就直接报错;然后让策略逐个检查坏例子列表;最后把检查结果和一个空列表比较。出来的结果是:如果没有坏例子通过,测试成功;如果有任何坏例子被放行,测试失败并显示差异。

调用关系:它是测试框架在运行测试时自动调用的函数。它把加载策略的工作交给 get_default_policy,然后用断言 assert_eq! 做最后验收:只有“没有任何坏例子漏网”时,这个测试才算通过。

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

execpolicy-legacy/tests/suite/good.rs源码 ↗
testtest run

这个测试文件关注的是“好例子列表”:也就是项目认为应该允许执行的一批命令或行为。测试先加载默认策略,然后把好例子一个个拿去检查。如果有任何一个好例子被策略误判成违规,测试就会失败。这样做很重要,因为执行策略通常是用来限制危险操作的,但限制太严也会出问题:正常功能可能突然不能用了。可以把它理解成门卫系统的测试:名单上写着“这些人应该能进门”,这个文件就逐个刷卡确认他们真的能进。如果有人被拦下,就说明门禁规则改坏了。

函数细节1
verify_everything_in_good_list_is_allowed5–9 ↗
fn verify_everything_in_good_list_is_allowed()

作用:这个测试函数确认默认策略不会错误拦截“好例子列表”里的内容。有人修改执行策略后,运行测试就能马上发现是否把正常允许的情况误伤了。

数据流:进去时不需要外部输入;它会读取项目提供的默认策略。然后它让策略逐条检查好例子列表,得到一份“本应通过但失败了”的记录。最后它把这份记录和空列表比较:如果为空,说明全部好例子都被允许;如果不为空,测试失败,并指出有样例被错误拦截。

调用关系:它在测试运行时由 Rust 测试框架自动调用。函数先调用 get_default_policy 加载默认策略,再让策略检查好例子列表,最后用 assert_eq! 做结果核对;assert_eq! 就像验收员,确认实际结果必须和“没有任何违规”这个期望完全一致。

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

旧版命令匹配器

这些命令专用匹配器测试检验常见命令的默认策略处理,覆盖可接受的规范化和精确的拒绝行为。

execpolicy-legacy/tests/suite/cp.rs源码 ↗
testtest run

这个测试文件像是在给“命令安全检查员”出题,题目都围绕 Unix/Linux 里的 cp 复制命令。cp 至少需要一个源文件和一个目标位置,所以这里分别测试了没参数、只有一个参数、复制一个文件、复制多个文件这几种情况。测试先用 setup 读入默认策略,然后把一条模拟命令包装成 ExecCall,再交给策略的 check 去检查。检查结果要么是明确的错误,比如“参数不够”或“没有匹配到可读取文件”;要么是成功匹配,并标出每个参数的身份:前面的源文件是可读文件,最后一个目标是可写文件。这个文件重要在于,它防止策略把 cp 的参数理解错。比如如果把目标文件当成可读文件,或者允许缺参数的 cp 通过,后面的执行安全判断就会出偏差。

函数细节5
setup15–17 ↗
fn setup() -> Policy

作用:准备一份默认执行策略,供下面每个测试使用。可以把它理解成测试前先把“规则手册”拿出来。

数据流:进去没有参数 → 它调用 get_default_policy 读取默认策略;如果读取失败,测试直接报错停止 → 出来是一份 Policy,也就是后续用来检查命令是否合法的规则对象。

调用关系test_cp_no_argstest_cp_one_argtest_cp_one_filetest_cp_multiple_files 都先调用它。它本身把加载默认策略的工作交给外部的 get_default_policy,让每个测试不用重复写同样的准备代码。

调用图:被 4 处调用(test_cp_multiple_files, test_cp_no_args, test_cp_one_arg, test_cp_one_file);外部调用 1 个(get_default_policy)。

test_cp_no_args20–31 ↗
fn test_cp_no_args()

作用:测试空参数的 cp 会不会被正确拒绝。因为 cp 没有源文件和目标文件时根本不能安全、明确地执行。

数据流:进去是测试框架启动这个测试 → 它先通过 setup 拿到默认策略,再用 ExecCall::new 模拟一条 cp 但不带任何参数的命令 → 它期望 policy.check 返回 NotEnoughArgs 错误,并说明缺少“可读文件”和“可写文件”这两类参数。

调用关系:这是最基础的失败场景测试。它调用 setup 准备规则,再创建模拟命令,最后用断言比较实际检查结果和预期错误,确保策略不会放过完全没参数的 cp

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

test_cp_one_arg34–45 ↗
fn test_cp_one_arg()

作用:测试只有一个参数的 cp 会不会被正确拒绝。一个参数不够判断“从哪里复制到哪里”,所以不能算合法。

数据流:进去是测试框架启动这个测试 → 它通过 setup 取得默认策略,再创建 cp foo/bar 这样的模拟命令 → 策略检查后,预期返回 VarargMatcherDidNotMatchAnything,意思是用于匹配多个源文件的规则没有成功匹配到完整需要的参数组合。

调用关系:它接在“完全没参数”的情况之后,覆盖另一个常见错误:参数有但不够。它依赖 setup 的默认策略,并用断言确认策略给出的错误类型正是预期的那一种。

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

test_cp_one_file48–65 ↗
fn test_cp_one_file() -> Result<()>

作用:测试复制一个文件到一个目标位置时,策略能否正确放行并标清楚每个参数的用途。

数据流:进去是测试框架启动这个测试 → 它拿到默认策略,创建 cp foo/bar ../baz 这条模拟命令 → 检查结果应当是成功匹配:第 0 个参数 foo/bar 被认定为可读取的源文件,第 1 个参数 ../baz 被认定为可写入的目标文件,同时合法程序路径包括 /bin/cp/usr/bin/cp

调用关系:这是正常成功场景之一。它调用 setup 准备规则,用 new 构造模拟命令和期望里的匹配参数,再用断言确认 policy.check 的理解和测试写出的标准答案完全一致。

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

test_cp_multiple_files68–86 ↗
fn test_cp_multiple_files() -> Result<()>

作用:测试 cp 复制多个源文件到一个目标位置时,策略是否能正确处理多个源文件。这个场景很常见,比如把几个文件一起复制到同一个目录。

数据流:进去是测试框架启动这个测试 → 它加载默认策略,创建 cp foo bar baz 这条模拟命令 → 预期结果是成功匹配:foobar 都是可读取的源文件,最后的 baz 是可写入的目标位置,并且命令只能对应 /bin/cp/usr/bin/cp 这些允许的程序路径。

调用关系:这是对可变数量参数的成功测试。它验证策略不是只能处理一个源文件,而是能把前面多个参数都交给“可读文件”规则,最后一个参数交给“可写文件”规则;整个判断仍然通过断言和实际检查结果对比来完成。

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

execpolicy-legacy/tests/suite/head.rs源码 ↗
testtest run

这里像一组验收题,专门考默认策略会不会正确看懂 head 命令。head 是读取文件开头几行的工具,风险点在于:它可能读文件,也可能通过参数控制读取多少行。测试先加载默认策略,再把不同写法包装成一次“准备执行的命令”,交给策略检查。文件重点覆盖几种情况:没有文件参数时会被拒绝,因为策略没法确认它读的是什么;给一个普通文件时会通过;带 -n 100 这种正整数选项时也会通过;但 0、小数、负数等都不能当作合法行数。比较特别的是 -1 会被当成“另一个选项”,所以报错不是“数字不合法”,而是“选项后面缺少真正的值”。

函数细节8
setup16–18 ↗
fn setup() -> Policy

作用:这个小帮手负责拿到默认的执行策略,后面的每个测试都靠它开始。要是默认策略加载失败,测试会直接失败,因为没有策略就没法判断命令。

数据流:进去没有参数 → 它调用 get_default_policy 读取项目内置的默认规则 → 出来一个 Policy 策略对象;如果读取失败,会用 expect 让测试立刻报错停止。

调用关系:它是所有这些 head 测试的共同起点。每个测试先调用 setup 拿到同一套默认规则,然后再构造 ExecCall,最后用断言比较策略检查结果。

调用图:被 7 处调用(test_head_invalid_n_as_0, test_head_invalid_n_as_float, test_head_invalid_n_as_negative_int, test_head_invalid_n_as_nonint_float, test_head_no_args, test_head_one_file_no_flags, test_head_one_flag_one_file);外部调用 1 个(get_default_policy)。

test_head_no_args21–39 ↗
fn test_head_no_args()

作用:这个测试确认:只写 head、不带任何文件或参数时,当前策略会拒绝。真实命令本身可以从标准输入读取,但策略无法保证输入来源安全,所以这里期望拒绝。

数据流:进去没有外部输入 → 它先通过 setup 拿默认策略,再用 ExecCall::new 做出一个程序名为 head、参数为空的命令 → 策略检查后应输出一个错误,说明“需要匹配可读文件的参数,但什么也没匹配到”。

调用关系:它用 setup 准备规则,用 new 构造待检查命令,然后通过断言验证结果。这个测试守住一个边界:不要因为 head 本身常见,就默认允许没有明确文件的调用。

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

test_head_one_file_no_flags42–60 ↗
fn test_head_one_file_no_flags() -> Result<()>

作用:这个测试确认:head src/extension.ts 这种只读一个文件、没有额外选项的用法会被允许。它证明策略能识别一个普通文件参数是“可读文件”。

数据流:进去是一条模拟命令:程序 head,参数 src/extension.ts → 测试拿默认策略,把这个参数按可读文件去匹配 → 出来应是成功结果,里面记录了匹配到的文件参数,以及允许的系统路径 /bin/head/usr/bin/head

调用关系:它接在 setup 之后运行,和其他测试一起验证同一份默认策略。这里测试的是最基本的成功路径:构造命令后,断言策略返回一个完整的“已验证可执行命令”。

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

test_head_one_flag_one_file63–86 ↗
fn test_head_one_flag_one_file() -> Result<()>

作用:这个测试确认:带 -n 100head 调用会被允许,因为 100 是合法的正整数,后面还有一个明确的可读文件。

数据流:进去是一条模拟命令:head -n 100 src/extension.ts → 它拿默认策略,把 -n 识别成需要一个数值的选项,把 100 验证为正整数,再把文件路径验证为可读文件参数 → 出来应是成功匹配结果,记录选项、文件参数和可用的系统路径。

调用关系:它用 setup 加载规则,用 new 生成命令,再用断言确认策略能把“选项”和“文件”分开看懂。这个测试补上了比单文件更常见的 head 用法。

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

test_head_invalid_n_as_089–98 ↗
fn test_head_invalid_n_as_0()

作用:这个测试确认:-n 0 不会被接受。这里的规则要求行数必须是正整数,也就是至少为 1。

数据流:进去是一条模拟命令:head -n 0 src/extension.ts → 策略看到 -n 后检查它的值 0,发现它不是合格的正整数 → 出来是 InvalidPositiveInteger 错误,并把出错值 0 放进错误信息里。

调用关系:它和其他非法 -n 测试一起,专门检查数字验证是否严格。它先借助 setup 拿规则,再构造命令,最后用断言保证策略不会把 0 放过去。

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

test_head_invalid_n_as_nonint_float101–110 ↗
fn test_head_invalid_n_as_nonint_float()

作用:这个测试确认:-n 1.5 不会被接受。虽然 1.5 看起来是数字,但它不是整数,不能表示读取多少“整行”。

数据流:进去是一条模拟命令:head -n 1.5 src/extension.ts → 策略检查 -n 的值时,发现 1.5 不是正整数格式 → 出来是 InvalidPositiveInteger 错误,错误里保留原始值 1.5

调用关系:它依赖 setup 提供默认规则,依赖 new 做出待检查命令。它和 test_head_invalid_n_as_float 类似,都是为了防止小数形式绕过正整数检查。

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

test_head_invalid_n_as_float113–122 ↗
fn test_head_invalid_n_as_float()

作用:这个测试确认:-n 1.0 也不会被接受。即使数学上等于 1,写成小数格式仍然不符合策略要求的“正整数”。

数据流:进去是一条模拟命令:head -n 1.0 src/extension.ts → 策略读取 -n 后面的值,按正整数规则校验,发现 1.0 格式不对 → 出来是 InvalidPositiveInteger 错误,并指出问题值是 1.0

调用关系:它用和其他测试相同的套路:setup 准备策略,new 构造命令,断言比较结果。它强调策略看的是严格格式,而不是帮用户把小数自动换算成整数。

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

test_head_invalid_n_as_negative_int125–136 ↗
fn test_head_invalid_n_as_negative_int()

作用:这个测试确认:-n -1 会被拒绝,而且报错原因是“选项后面跟了另一个像选项的东西”。因为 -1 以短横线开头,策略把它当成选项,而不是合法的数值。

数据流:进去是一条模拟命令:head -n -1 src/extension.ts → 策略看到 -n 需要一个值,但紧跟着的 -1 长得像另一个选项 → 出来是 OptionFollowedByOptionInsteadOfValue 错误,说明 -n 后面的 -1 不能当作它的值。

调用关系:它同样从 setup 获取默认策略,再构造命令并断言错误。它补充了一个容易误解的边界情况:负数不是先进入“正整数校验”,而是在参数解析阶段就被当成错误的选项位置。

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

execpolicy-legacy/tests/suite/ls.rs源码 ↗
testtest run

这个文件不是正式功能代码,而是一组自动测试。项目里有一套“执行策略”,意思是程序在真正运行外部命令前,先检查这个命令和参数是不是安全、是不是符合规则。这里测试的是最常见的 ls 命令:不带参数时应该通过,带 -a-l 这类已知选项时应该通过,带文件名时要被识别成“读取文件”的参数,带未知选项 -z 或打包写法 -al 时目前应该失败。它还记录了一个有点微妙的行为:文件名后面再跟 -l,当前策略会接受,虽然真实 ls 可能不喜欢这种顺序。整体上,这个文件像给门卫出考题:每种进门方式都试一遍,确保门卫没有误放危险用法,也没有误拦正常用法。

函数细节9
setup15–17 ↗
fn setup() -> Policy

作用:这个小函数给每个测试准备同一份默认策略。这样每个测试都从干净、统一的规则开始,不会因为手写重复准备代码而出错。

数据流:进去没有参数 → 它调用 get_default_policy 读取项目默认的命令规则,如果读取失败就直接让测试失败 → 出来是一份 Policy,也就是后面用来检查 ls 命令是否合法的规则表。

调用关系:所有测试函数开头都会先找它要一份策略。它把真正加载默认策略的工作交给外部的 get_default_policy,测试本身只关心拿到规则后检查命令。

调用图:被 8 处调用(test_flags_after_file_args, test_ls_dash_a_dash_l, test_ls_dash_al, test_ls_dash_z, test_ls_multiple_file_args, test_ls_multiple_flags_and_file_args, test_ls_no_args, test_ls_one_file_arg);外部调用 1 个(get_default_policy)。

test_ls_no_args20–29 ↗
fn test_ls_no_args()

作用:这个测试确认最简单的 ls,也就是不带任何参数的 ls,会被默认策略允许。没有这个测试,最基础的列目录命令被误拦也可能没人发现。

数据流:进去没有外部输入 → 它先用 setup 拿默认策略,再用 ExecCall::new 做出一个表示 ls 命令的对象,参数为空 → 它检查结果应该是通过,并且匹配到系统里的 /bin/ls/usr/bin/ls

调用关系:它是 ls 测试里最基础的一关。它依赖 setup 准备策略,依赖 new 构造待检查的命令,最后用断言确认 policy.check 的结果正好符合预期。

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

test_ls_dash_a_dash_l32–47 ↗
fn test_ls_dash_a_dash_l()

作用:这个测试确认 ls -a -l 这种常见写法会被允许。-a 通常表示显示隐藏文件,-l 通常表示用详细列表显示,它们是安全策略里认识的选项。

数据流:进去没有外部输入 → 它准备默认策略,把 ls 和参数 -a-l 包成一次待检查的执行请求 → 输出上期待策略返回“匹配成功”,并把两个参数都记录成已经识别的合法标志。

调用关系:它验证默认策略认识多个独立选项。流程上先通过 setup 拿规则,再通过 new 造命令,最后用断言比较实际检查结果和手写的预期结果。

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

test_ls_dash_z50–63 ↗
fn test_ls_dash_z()

作用:这个测试确认未知选项 -z 会被拒绝。它防止策略把自己不认识的 ls 参数随便放行。

数据流:进去没有外部输入 → 它拿到默认策略,构造 ls -z 这次调用 → 出来期待是一个错误,错误内容说明程序是 ls,不认识的选项是 -z

调用关系:它是反向测试,也就是故意给一个不该通过的输入。它同样使用 setupnew,然后用断言确认 policy.check 没有放行这个未知选项。

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

test_ls_dash_al66–78 ↗
fn test_ls_dash_al()

作用:这个测试确认 ls -al 这种把多个短选项挤在一起的写法目前会失败。注释里也说明,将来如果实现“选项打包”功能,这个预期可能会改变。

数据流:进去没有外部输入 → 它准备默认策略,构造参数为 -alls 调用 → 当前期待输出是未知选项错误,因为策略还没有把 -al 拆成 -a-l 来理解。

调用关系:它记录了当前系统的边界:认识 -a -l,但暂时不认识 -al。它调用 setup 准备规则,调用 new 创建命令,再用断言锁定当前行为,避免无意中改变。

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

test_ls_one_file_arg81–100 ↗
fn test_ls_one_file_arg() -> Result<()>

作用:这个测试确认 ls foo 会被允许,并且 foo 会被当成一个要读取的文件或目录参数。这样策略不只是看选项,也能理解普通文件名参数的含义。

数据流:进去没有外部输入 → 它拿默认策略,构造 ls 加一个参数 foo → 期待结果是匹配成功,并把 foo 标记成位置 0 的 ReadableFile,也就是“这个命令会读取这个路径”。

调用关系:它测试的是文件参数的识别。它先用 setup 取规则,再用 new 造调用;预期结果里还用 MatchedArg::new 构造“已匹配的参数”,最后和 policy.check 的真实结果比较。

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

test_ls_multiple_file_args103–122 ↗
fn test_ls_multiple_file_args() -> Result<()>

作用:这个测试确认 ls foo bar baz 这种一次查看多个文件或目录的写法会被允许。它还检查每个文件名的位置都被正确记下来。

数据流:进去没有外部输入 → 它创建默认策略和带三个普通参数的 ls 调用 → 出来期待是通过,并且 foobarbaz 分别被标成第 0、1、2 个可读取文件参数。

调用关系:它是在单个文件参数测试上的扩展,确保策略能处理一串文件名,而不是只认第一个。它通过 setupnew 准备场景,再用断言检查 policy.check 给出的完整匹配结果。

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

test_ls_multiple_flags_and_file_args125–146 ↗
fn test_ls_multiple_flags_and_file_args() -> Result<()>

作用:这个测试确认 ls -l -a foo bar baz 这种“先给选项,再给多个文件名”的常见组合会被允许。它同时检查选项和文件参数有没有被分清楚。

数据流:进去没有外部输入 → 它拿默认策略,构造带两个标志和三个文件名的 ls 调用 → 期待输出是匹配成功,其中 -l-a 被记录成合法标志,foobarbaz 被记录成原始位置 2、3、4 的可读取文件参数。

调用关系:它把前面几个简单场景合在一起,验证策略能在同一条命令里同时识别标志和普通参数。它调用 setup 准备规则,调用 new 构造命令,然后用断言把整体结果钉住。

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

test_flags_after_file_args149–175 ↗
fn test_flags_after_file_args() -> Result<()>

作用:这个测试记录当前策略对 ls foo -l 的行为:即使标志出现在文件名后面,策略现在也会允许。注释提醒说,这可能不完全符合真实 ls 的习惯,将来可能要做成可配置规则。

数据流:进去没有外部输入 → 它准备默认策略,构造参数顺序为文件名 foo 后跟标志 -lls 调用 → 当前期待结果是通过:foo 被当成第 0 个可读取文件参数,-l 被当成合法标志。

调用关系:它测试一个边界行为,也顺便给未来改进留下提醒。它和其他测试一样先用 setup 拿策略、用 new 造命令,再通过断言确认 policy.check 的当前结果没有被无意改变。

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

execpolicy-legacy/tests/suite/pwd.rs源码 ↗
testtest run

这个文件像一组“安检题”,用来检查系统有没有正确识别 pwd 这个命令。pwd 的作用是显示当前所在目录;在这套执行策略里,它只能按规定方式使用。文件先通过 setup 读入默认策略,然后分别造出几种 pwd 调用:空参数、-L-P,以及多出来的 foo bar。前三种应该通过检查,并返回“匹配到合法命令”的结果;最后一种应该失败,并明确指出哪些位置上的参数是不该出现的。这样做的价值是防止策略以后被改坏:比如本来不该允许 pwd foo,却不小心放行了;或者本来安全的 pwd -P,却被误拦了。

函数细节5
setup15–17 ↗
fn setup() -> Policy

作用:这个函数准备测试要用的默认策略。它相当于每个测试开头先拿到同一份“规则表”,避免每个测试重复写加载代码。

数据流:进去时没有参数 → 它调用 get_default_policy 读取系统内置的默认执行策略,如果读取失败就直接让测试报错 → 出来的是一个 Policy,也就是后面用来判断命令能不能执行的规则对象。

调用关系test_pwd_no_argstest_pwd_capital_ltest_pwd_capital_ptest_pwd_extra_args 都会先调用它拿到策略。它自己把真正的加载工作交给外部的 get_default_policy

调用图:被 4 处调用(test_pwd_capital_l, test_pwd_capital_p, test_pwd_extra_args, test_pwd_no_args);外部调用 1 个(get_default_policy)。

test_pwd_no_args20–32 ↗
fn test_pwd_no_args()

作用:这个测试确认最普通的 pwd,也就是不带任何参数的 pwd,会被默认策略允许。没有它,就可能发现不了基础用法被误拦的问题。

数据流:进去时没有外部输入 → 它先用 setup 拿到默认策略,再用 ExecCall::new 构造一个程序名为 pwd、参数为空的命令请求 → 它检查策略返回的结果,期望得到一个成功匹配,并且匹配到的程序名就是 pwd

调用关系:它是针对 pwd 基础用法的一条测试。它调用 setup 准备规则,调用 new 造出被检查的命令,最后用断言比较实际结果和预期结果。

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

test_pwd_capital_l35–48 ↗
fn test_pwd_capital_l()

作用:这个测试确认 pwd -L 会被允许,并且 -L 会被识别成合法标志。这里的标志可以理解为命令后面的开关,用来改变命令行为。

数据流:进去时没有外部输入 → 它加载默认策略,构造一个 pwd-L 的调用 → 它期望策略检查成功,并且返回结果里明确记录匹配到了 -L 这个标志。

调用关系:它测试的是 pwd 的一个允许选项。流程上和其他测试一样:先找 setup 拿规则,再用 new 造命令,最后用断言确认策略没有误拒绝。

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

test_pwd_capital_p51–64 ↗
fn test_pwd_capital_p()

作用:这个测试确认 pwd -P 会被允许,并且 -P 会被识别成合法标志。它保证默认策略知道 pwd 的这个常见开关是安全范围内的。

数据流:进去时没有外部输入 → 它通过 setup 得到默认策略,构造一个带 -P 参数的 pwd 调用 → 它期望检查结果是成功匹配,并且结果中包含 -P 这个已匹配标志。

调用关系:它和 test_pwd_capital_l 是成对的测试,分别覆盖 pwd 的两个合法开关。它依赖 setup 提供策略,依赖 new 创建待检查命令,再用断言验证结果。

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

test_pwd_extra_args67–86 ↗
fn test_pwd_extra_args()

作用:这个测试确认 pwd foo bar 这种多带普通参数的写法会被拒绝。它重点检查系统不只是说“失败”,还会指出哪些多余参数不合规。

数据流:进去时没有外部输入 → 它加载默认策略,构造一个程序名为 pwd、参数为 foobar 的调用 → 策略检查后应返回错误,错误里列出第 0 个参数 foo 和第 1 个参数 bar 都是意外出现的参数。

调用关系:它覆盖的是拒绝路径,也就是命令不符合规则时系统应该怎么反应。它调用 setup 获取规则,调用 new 构造违规命令,然后用断言确认错误类型和错误内容都符合预期。

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

旧版自定义与 sed 解析

这些测试关注专门的匹配行为,包括字面位置匹配、独立 sed 解析,以及完整的 sed 策略执行。

execpolicy-legacy/tests/suite/literal.rs源码 ↗
testtest run

这个测试文件检查一个很具体但很重要的场景:某个可执行程序只能带指定的参数运行。可以把策略想成门卫手里的名单,不只看人名,也看他说的暗号是否一字不差。文件里先写了一段策略,声明 fake_executable 这个程序必须带两个参数:subcommand 和 sub-subcommand。然后它构造一次“合法调用”,确认策略检查结果是匹配成功,并且两个参数都被当作字面量匹配。字面量就是必须完全相同的固定文字。接着它再构造一次“非法调用”,第一个参数对,第二个参数变成 not-a-real-subcommand,测试确认系统返回 LiteralValueDidNotMatch 这个错误。这个文件的价值在于防止策略检查过于宽松:如果子命令写错还被允许,真实运行时就可能执行到不该执行的功能。

函数细节1
test_invalid_subcommand13–54 ↗
fn test_invalid_subcommand() -> Result<()>

作用:这个测试确认:当策略要求某个参数必须是固定文字时,只有完全一样的参数才会通过检查。它同时验证了正确调用会被放行,错误子命令会被明确拒绝。

数据流:进去的是一段测试用的策略文本,以及两次模拟的程序调用:一次参数完全正确,一次第二个参数错误。函数先用 PolicyParser::new 创建策略解析器,把文本变成可检查的策略;再用 ExecCall::new 伪造命令调用;随后用 assert_eq! 比较实际检查结果和预期结果。出来的结果不是普通返回值,而是测试是否通过:如果合法调用没有匹配成功,或非法调用没有报出预期错误,测试就会失败;如果都符合预期,最后返回 Ok(())。

调用关系:它是在测试框架运行这个文件时被自动调用的测试函数。它先把策略文本交给解析器生成 policy,再把模拟出来的 ExecCall 交给 policy.check 检查,最后用 assert_eq! 当裁判,确认检查结果和预先写好的答案一致。

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

execpolicy-legacy/tests/suite/parse_sed_command.rs源码 ↗
testtest run

这个测试文件盯住一个很小但很关键的规则:用户给的 sed 命令,必须能明确证明是安全的。sed 可以理解成一个按行处理文本的小工具,这里只允许类似 122,202p 这种“打印第 122 到 202 行”的命令。文件里有两个测试:一个确认合法命令能通过;另一个确认少了 p、或者格式乱掉的命令会被拒绝,并返回指定错误。这样做像门卫检查通行证:格式完整、用途明确就放行;说不清楚要干什么就拦下,避免执行到不该执行的文本命令。

函数细节2
parses_simple_print_command5–7 ↗
fn parses_simple_print_command()

作用:这个测试确认最普通的 sed 打印命令 122,202p 会被认为是安全的。有人改解析规则时,它能提醒大家不要把正常用法误伤掉。

数据流:进去的是一段固定字符串 122,202p,测试把它交给 parse_sed_command 判断。期望结果是 Ok(()),意思是“没有错误,可以接受”。最后用 assert_eq! 做比较;如果实际结果不一样,测试就失败。

调用关系:它是测试套件中的一个正向案例,也就是检查“该通过的确实通过”。它主要依靠 assert_eq! 这个测试比较工具来判断结果是否符合预期。

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

rejects_malformed_print_command10–23 ↗
fn rejects_malformed_print_command()

作用:这个测试确认格式不完整或不清楚的 sed 命令会被拒绝。它保护的是安全边界:不能因为命令看起来像行号范围,就随便放行。

数据流:进去的是两个坏例子:122,202 少了最后表示打印的 p122202 连范围分隔也不清楚。测试分别把它们交给 parse_sed_command,期望都得到 Error::SedCommandNotProvablySafe,并且错误里保留原始命令文本。最后用 assert_eq! 检查实际错误和期望错误完全一致。

调用关系:它是测试套件中的反向案例,也就是检查“该拦住的确实拦住”。它同样通过 assert_eq! 来核对结果,重点配合前一个测试一起说明解析器的边界:合法打印命令能过,模糊命令不能过。

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

execpolicy-legacy/tests/suite/sed.rs源码 ↗
testtest run

这个文件像一组“安检样题”,用来确认系统看到 sed 命令时会不会放行正确、拦截正确。测试先通过 setup 取到默认策略,然后把不同形式的 sed 调用包装成 ExecCall,也就是“有人想执行这个程序和这些参数”的记录。安全例子包括 sed -n 122,202p hello.txt,以及用 -e 明确写脚本的形式;测试会检查策略是否把程序名、标志、选项、文件参数都认出来。危险例子是 sed 的 e 命令形式,它可能让 sed 执行外部 shell 命令,测试要求策略必须拒绝。还有一个测试确认:如果 sed 只给了类似脚本的内容但缺少必需的文件或 -e 选项,策略要报清楚缺了什么。这个文件重要的地方在于,它把“允许常见安全用法”和“拒绝可执行命令的危险用法”这条边界固定下来,避免以后改代码时不小心放宽规则。

函数细节5
setup16–18 ↗
fn setup() -> Policy

作用:准备每个测试都要用的默认执行策略。它把“读取默认规则”这一步集中起来,避免每个测试重复写同样的初始化代码。

数据流:进去时不需要参数;它调用 get_default_policy 读取默认策略配置,如果读取失败就直接让测试失败;出来的是一个 Policy,也就是后面用来判断 sed 命令能不能执行的规则集合。

调用关系:它是这些 sed 测试的共同开头。test_sed_print_specific_lines、test_sed_print_specific_lines_with_e_flag、test_sed_reject_dangerous_command 和 test_sed_verify_e_or_pattern_is_required 都先调用它拿到同一套默认规则,然后再各自检查不同的 sed 调用。

调用图:被 4 处调用(test_sed_print_specific_lines, test_sed_print_specific_lines_with_e_flag, test_sed_reject_dangerous_command, test_sed_verify_e_or_pattern_is_required);外部调用 1 个(get_default_policy)。

test_sed_print_specific_lines21–40 ↗
fn test_sed_print_specific_lines() -> Result<()>

作用:验证一种常见安全用法:sed -n 122,202p hello.txt,也就是只打印文件里第 122 到 202 行。这个测试确保系统会允许这种读文件、打印指定行的操作。

数据流:进去时没有外部输入;它先用 setup 拿到默认策略,再用 ExecCall::new 构造一条“要执行 sed,参数是 -n、122,202p、hello.txt”的请求;然后把策略检查结果和预期结果比较。预期出来的是 Match,里面说明程序是 sed,-n 被认作标志,122,202p 被认作安全的 sed 命令,hello.txt 被认作可读取文件。

调用关系:这是正常放行场景的测试。它把构造好的 ExecCall 交给 policy.check,由策略系统判断,再用 assert_eq! 确认判断结果完全符合预期。

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

test_sed_print_specific_lines_with_e_flag43–66 ↗
fn test_sed_print_specific_lines_with_e_flag() -> Result<()>

作用:验证 sed 使用 -e 选项写脚本时,安全的打印指定行命令仍然会被允许。-e 可以理解为“后面跟一段 sed 要执行的小脚本”。

数据流:进去时没有外部输入;它通过 setup 取得默认策略,再构造 sed -n -e 122,202p hello.txt 这条执行请求;策略检查后,测试要求结果是成功匹配。出来的预期结果会把 -n 识别为标志,把 -e 和它后面的 122,202p 识别为一个安全的选项值,把 hello.txt 识别为可读取文件。

调用关系:它补充测试了 sed 的另一种合法写法。和前一个测试一样,它创建 ExecCall 后交给 policy.check,再用 assert_eq! 确认策略既没有误拒绝,也没有把参数位置识别错。

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

test_sed_reject_dangerous_command69–78 ↗
fn test_sed_reject_dangerous_command()

作用:验证策略会拒绝危险的 sed 脚本:s/y/echo hi/e。这里末尾的 e 可能让 sed 执行外部命令,相当于把文本处理工具变成了命令执行入口。

数据流:进去时没有外部输入;它先拿到默认策略,再构造 sed -e s/y/echo hi/e hello.txt 这条请求;策略检查后,预期出来的不是成功,而是 Error::SedCommandNotProvablySafe,明确指出这段 sed 命令不能证明是安全的。

调用关系:这是安全边界测试。它故意把危险命令交给 policy.check,确认策略会拦下来;assert_eq! 用来确保报错类型和报错里记录的危险命令都正确。

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

test_sed_verify_e_or_pattern_is_required81–91 ↗
fn test_sed_verify_e_or_pattern_is_required()

作用:验证 sed 调用缺少必需选项时会被拒绝,并且报错要说明缺了 -e。这个测试防止策略把不完整或含糊的 sed 参数误当成安全命令。

数据流:进去时没有外部输入;它用 setup 取得默认策略,再构造只有一个参数 122,202p 的 sed 请求;策略检查后,预期出来的是 Error::MissingRequiredOptions,说明程序 sed 缺少必需的 -e 选项。

调用关系:这是输入格式检查的测试。它把一个不符合策略要求的 ExecCall 交给 policy.check,然后用 assert_eq! 确认策略不是随便放行,而是给出具体、可理解的缺项错误。

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