Codex 系统手册

审批相关的执行运行时

stage-14.1.610 个文件

这一阶段是系统运行时的“门禁和值班保安”,主程序要联网、沙箱命令要读写文件时,都在这里被检查。network-proxy 的入口先把各模块摆好;state 把用户配置和管理员硬规则合成可执行状态;network_approval 负责该问人时只问一次;network_policy 和 runtime 判断请求放行还是拦下,并记日志、热更新规则。Windows 这边则给沙箱加临时可读目录、同步禁止读取规则、保护工作区特殊目录,再用 WFP 这道系统级网络关卡拦住不该去的地址。

本阶段的文件10

代理策略接口

这些文件引入网络代理子系统,并定义经过验证的策略类型,供实时执行引擎使用。

network-proxy/src/lib.rs源码 ↗
othercross-cutting

可以把这个文件理解成一本工具箱的目录页。真正做事的零件分散在 certs、config、proxy、runtime、policy 等文件里;而这个文件负责告诉 Rust 编译器这些零件存在,并决定哪些零件可以被库外的人直接拿来用。比如,外部代码想创建网络代理、读取代理配置、判断网络请求是否允许、处理被拦截的请求,通常会从这里导出的名字开始用。文件开头还禁止直接向标准输出或错误输出打印,避免库代码偷偷往终端写东西,影响调用它的程序。它本身没有函数,也没有运行流程,但它很重要:没有它,外部项目就很难稳定、清晰地使用这个网络代理库。

network-proxy/src/state.rs源码 ↗
orchestrationconfig load / settings update

这个文件解决的是网络代理启动或改配置时最容易出错的问题:配置看起来能写,但可能和管理员限制冲突,或者写了过宽的域名规则,比如想允许所有网站。这里先定义了一组“约束”,意思是管理员给用户配置画的红线。然后它会检查用户配置有没有踩线,比如网络模式不能比允许的更开放,禁止列表不能少掉管理员要求禁止的域名,Unix socket(本机进程间通信用的特殊文件通道)不能随便放开。检查通过后,build_config_state 会把域名名单编译成更快的匹配器,把 MITM(中间人式流量检查/改写能力)和钩子脚本准备好,再放进 ConfigState,供代理运行时直接使用。重要的一点是:这里特别禁止“全局通配域名”,也就是类似“所有域名都算”的规则,避免安全边界被一句配置绕开。

函数细节6
build_config_state64–94 ↗
fn build_config_state(
    config: NetworkProxyConfig,
    constraints: NetworkProxyConstraints,
) -> anyhow::Result<ConfigState>

作用:把已经读进来的网络代理配置,变成代理运行时可以直接使用的一整包状态。有人在启动代理、刷新配置、更新域名名单时会用它。

数据流:进去的是完整配置和管理员约束。它先检查 Unix socket 白名单路径是否合法,再取出允许域名和禁止域名,确认禁止域名里没有“允许/禁止全世界”这种过宽写法;接着把域名规则编译成快速匹配用的集合,编译 MITM 钩子;如果配置开启了 MITM,就创建对应状态。最后出来的是 ConfigState,里面带着原配置、匹配器、MITM 状态、约束和被拦截请求的记录队列。

调用关系:它是配置进入运行期前的组装台。state_with_metadata、update_domain_list、state_for_settings 以及一些测试会调用它;它把具体小活交给 validate_unix_socket_allowlist_paths、compile_allowlist_globset、compile_denylist_globset、compile_mitm_hooks、validate_non_global_wildcard_domain_patterns 和 MitmState::new 去做。

调用图:调用 6 个内部函数(validate_unix_socket_allowlist_paths, new, compile_mitm_hooks, compile_allowlist_globset, compile_denylist_globset, validate_non_global_wildcard_domain_patterns);被 6 处调用(state_with_metadata, update_domain_list, add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed, add_allowed_domain_succeeds_when_managed_baseline_allows_expansion, add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed, state_for_settings);外部调用 2 个(new, new)。

validate_policy_against_constraints96–386 ↗
fn validate_policy_against_constraints(
    config: &NetworkProxyConfig,
    constraints: &NetworkProxyConstraints,
) -> Result<(), NetworkProxyConstraintError>

作用:检查用户想用的网络策略有没有超过管理员给的上限。它不负责启动代理,只负责回答一句话:这份配置合不合规。

数据流:进去的是一份网络代理配置和一份约束。它读取开关、网络模式、上游代理、非本机代理、Unix socket、允许域名、禁止域名、MITM 钩子等设置,然后逐项比较:该不能开的有没有开,该必须保留的域名规则有没有缺,用户新增的规则是不是仍在允许范围内。通过就返回成功;发现越界就返回 NetworkProxyConstraintError,指出哪个字段、填了什么、允许什么。

调用关系:它通常出现在用户改网络模式或更新域名名单的时候,例如 set_network_mode 和 update_domain_list 会先请它把关。它内部会调用 validate_mitm_hook_config 检查 MITM 钩子配置,也会调用 validate_non_global_wildcard_domain_patterns 防止过宽域名规则。

调用图:调用 2 个内部函数(validate_mitm_hook_config, validate_non_global_wildcard_domain_patterns);被 2 处调用(set_network_mode, update_domain_list)。

invalid_mitm_hook_configuration388–394 ↗
fn invalid_mitm_hook_configuration(err: anyhow::Error) -> NetworkProxyConstraintError

作用:把 MITM 钩子配置的普通错误,包装成统一的“配置违反约束”错误。这样外面看到的错误格式一致,容易展示给用户或日志系统。

数据流:进去的是一个 MITM 钩子校验失败产生的错误。它把错误转成文字,放进 NetworkProxyConstraintError::InvalidValue 里,并标明出错字段是 network.mitm_hooks,要求是“有效的 MITM 钩子配置”。出来的是一个约束错误对象。

调用关系:它是 validate_policy_against_constraints 里的错误翻译器。validate_mitm_hook_config 发现钩子配置不对时,这个函数负责把底层错误换成网络代理约束体系能理解的错误。

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

validate_non_global_wildcard_domain_patterns396–412 ↗
fn validate_non_global_wildcard_domain_patterns(
    field_name: &'static str,
    patterns: &[String],
) -> Result<(), NetworkProxyConstraintError>

作用:检查一批域名规则里有没有“全局通配”这种太宽的写法。简单说,它阻止配置用一个规则覆盖所有网站。

数据流:进去的是字段名和一组域名模式字符串。它逐个看这些模式是不是全局通配;如果找到了,就返回错误,告诉调用者只能写具体主机名,或者像 *.example.com、**.example.com 这种限定在某个域名下面的通配。没有问题就返回成功。

调用关系:它被 build_config_state 和 validate_policy_against_constraints 共用。前者在生成运行状态前用它挡住危险规则,后者在比较用户配置和管理员约束时也用它保证两边规则本身都安全。

调用图:被 2 处调用(build_config_state, validate_policy_against_constraints)。

NetworkProxyConstraintError::into_anyhow425–427 ↗
fn into_anyhow(self) -> anyhow::Error

作用:把本文件自己的约束错误,转换成项目里通用的 anyhow 错误类型。这样上层代码不必认识每一种具体错误,也能统一处理失败。

数据流:进去的是一个 NetworkProxyConstraintError。它用 anyhow! 宏把这个错误包成通用错误对象;出来的是 anyhow::Error,错误文字和原来的约束错误保持一致。

调用关系:它主要给需要返回通用错误的流程用,比如 build_config_state 在调用校验函数失败时会把专门的约束错误转成 anyhow 错误,方便继续向上抛。

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

network_mode_rank430–435 ↗
fn network_mode_rank(mode: NetworkMode) -> u8

作用:给网络模式排一个“开放程度”的数字,方便比较哪个模式更宽松。Limited 排得更低,Full 排得更高。

数据流:进去的是一个 NetworkMode。它把 Limited 变成 0,把 Full 变成 1;出来的是这个数字,用来做大小比较。

调用关系:它服务于 validate_policy_against_constraints。校验网络模式时,需要判断用户选择的模式有没有比管理员允许的更开放,这个函数就提供了比较用的尺子。

实时代理执行

这些文件实现运行时策略引擎,以及具备批准感知能力的决策流程,用于在工具执行期间评估和审计网络访问。

core/src/tools/network_approval.rs源码 ↗
domain_logicrequest handling / tool execution

可以把它想成一名网络门卫。工具运行时,如果要访问某个主机和端口,网络代理会把请求交给这里判断。这里先看本次会话里有没有已经批准或拒绝过;没有的话,就把同一个目标的多个等待请求合并成一次询问,避免弹出一堆重复问题。它还能走不同的审批路线:先让权限钩子自动判断,再根据配置交给 Guardian(一个更集中的审查方)或普通用户审批。结果可能是只放行这一次、整个会话都放行、拒绝,或者修改网络策略。这个文件还把拒绝结果记到正在运行的工具调用上,并通过取消令牌(一种“请停下”的信号)通知工具停止,避免工具在网络被拒后还继续跑。

函数细节38
DeferredNetworkApproval::registration_id64–66 ↗
fn registration_id(&self) -> &str

作用:返回这次延后网络审批登记时拿到的编号。别人可以用这个编号知道它对应的是哪一次工具调用。

数据流:进去的是这个延后审批对象本身 → 它读取里面保存的 registration_id → 出来的是这个编号的文本引用,不改任何状态。

调用关系:它通常在需要追踪某个延后审批时使用,是外界识别这次审批记录的入口。

DeferredNetworkApproval::cancellation_token68–70 ↗
fn cancellation_token(&self) -> CancellationToken

作用:拿到这次延后审批的取消令牌。取消令牌可以理解成一根拉绳,网络被拒时拉一下,相关任务就知道该停。

数据流:进去的是延后审批对象 → 它复制一份取消令牌句柄 → 出来的是可交给其他任务监听的令牌,原对象不变。

调用关系:它会被像 terminate_process_on_network_denial 这样的流程拿去监听,用来在网络审批失败时结束正在运行的进程。

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

DeferredNetworkApproval::is_cancelled72–74 ↗
fn is_cancelled(&self) -> bool

作用:检查这次延后审批是否已经收到取消信号。调用者用它判断后续工作是否还应该继续。

数据流:进去的是延后审批对象 → 它询问内部取消令牌现在是不是已取消 → 出来是真或假,不改状态。

调用关系:它是一个状态查询口,通常给等待或收尾逻辑判断“门卫是否已经叫停”。

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

DeferredNetworkApproval::finish76–83 ↗
async fn finish(&self, service: &NetworkApprovalService) -> Result<(), ToolError>

作用:结束一次延后审批,并把审批结果变成工具能理解的成功或失败。它保证同一个延后审批即使被多人收尾,也只真正取一次结果。

数据流:进去的是延后审批对象和 NetworkApprovalService → 它按 registration_id 向服务取最终拒绝原因或无拒绝结果,并缓存起来 → 出来是 Ok 表示可以继续,或 ToolError 表示被拒绝。

调用关系:它把活交给 NetworkApprovalService::finish_call_outcome 拿结果,再交给 network_approval_outcome_to_result 翻译成工具错误;finish_deferred_network_approval 会调用它。

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

ActiveNetworkApproval::mode94–96 ↗
fn mode(&self) -> NetworkApprovalMode

作用:告诉调用者这次网络审批是立即收尾,还是可以延后收尾。

数据流:进去的是活跃审批对象 → 它读取 mode 字段 → 出来是 Immediate 或 Deferred,不改状态。

调用关系:工具执行流程可以根据这个模式决定是在工具调用结束时马上检查结果,还是把审批对象转成延后对象。

ActiveNetworkApproval::cancellation_token98–100 ↗
fn cancellation_token(&self) -> CancellationToken

作用:取出活跃审批的取消令牌,让外部任务能在网络被拒时及时停下。

数据流:进去的是活跃审批对象 → 它复制内部取消令牌句柄 → 出来是一份可监听的取消令牌,原对象不变。

调用关系:它服务于工具运行期间的中断控制;审批服务记录拒绝结果时会触发这个令牌。

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

ActiveNetworkApproval::into_deferred102–118 ↗
fn into_deferred(self) -> Option<DeferredNetworkApproval>

作用:把一个活跃审批转成“稍后再检查结果”的延后审批。只有原本就是 Deferred 模式、并且有登记编号时才会成功。

数据流:进去的是活跃审批对象本身 → 它拆出 registration_id、mode 和取消令牌 → 如果符合延后条件,出来一个 DeferredNetworkApproval;否则出来 None。

调用关系:工具运行流程在需要把网络审批收尾推迟到后面时会用它;生成的新对象之后由 DeferredNetworkApproval::finish 收尾。

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

HostApprovalKey::from_request129–135 ↗
fn from_request(request: &NetworkPolicyRequest, protocol: NetworkApprovalProtocol) -> Self

作用:把一个网络请求整理成“主机 + 协议 + 端口”的唯一钥匙。这样同一个目标可以被缓存或合并等待。

数据流:进去的是网络策略请求和审批协议 → 它把主机名转成小写,取协议标签和端口 → 出来是 HostApprovalKey。

调用关系:handle_inline_policy_request 会先调用它,随后用这个 key 查会话批准列表、拒绝列表和正在等待的审批。

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

protocol_key_label138–145 ↗
fn protocol_key_label(protocol: NetworkApprovalProtocol) -> &'static str

作用:把内部协议枚举变成稳定的短文本,比如 http、https、socks5-tcp。这个文本会成为审批缓存钥匙的一部分。

数据流:进去的是 NetworkApprovalProtocol → 它按协议种类选择对应字符串 → 出来是静态文本标签。

调用关系:HostApprovalKey::from_request 调用它,确保不同协议访问同一主机时不会被误当成同一件事。

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

network_approval_outcome_to_result160–170 ↗
fn network_approval_outcome_to_result(
    outcome: Option<NetworkApprovalOutcome>,
) -> Result<(), ToolError>

作用:把网络审批的内部结果翻译成工具调用能直接处理的结果。没有拒绝就是成功,有拒绝就变成 ToolError。

数据流:进去的是可选的 NetworkApprovalOutcome → 如果是用户拒绝,生成“rejected by user”;如果是策略拒绝,带上策略消息;如果没有结果,返回成功 → 出来是 Result。

调用关系:DeferredNetworkApproval::finish 和 NetworkApprovalService::finish_call 都用它,把门卫的记录转换成工具执行流程的通过或失败。

调用图:被 2 处调用(finish, finish_call);外部调用 1 个(Rejected)。

allows_network_approval_flow173–175 ↗
fn allows_network_approval_flow(policy: AskForApproval) -> bool

作用:判断当前审批策略是否允许“遇到没在白名单里的网络请求时再去问”。如果策略是 Never,就不能问,直接按不允许处理。

数据流:进去的是 AskForApproval 策略 → 它检查是不是 Never → 出来是真或假。

调用关系:handle_inline_policy_request 在决定要不要发起人工或 Guardian 审批前调用它。

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

permission_profile_allows_network_approval_flow177–179 ↗
fn permission_profile_allows_network_approval_flow(permission_profile: &PermissionProfile) -> bool

作用:判断当前权限配置是否支持网络审批流程。这里要求权限配置是 Managed,也就是由系统托管权限规则的模式。

数据流:进去的是 PermissionProfile → 它检查是不是 Managed 变体 → 出来是真或假。

调用关系:handle_inline_policy_request 用它做前置门槛;不满足时网络请求不会进入询问流程。

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

PendingApprovalDecision::to_network_decision182–187 ↗
fn to_network_decision(self) -> NetworkDecision

作用:把等待中的审批结论变成网络代理真正需要的放行或拒绝决定。

数据流:进去的是 AllowOnce、AllowForSession 或 Deny → 前两种变成 NetworkDecision::Allow,Deny 变成带 not_allowed 原因的拒绝 → 出来是 NetworkDecision。

调用关系:当重复请求在等待同一个审批,或者最终审批完成后,handle_inline_policy_request 会用它把内部结论交回网络代理。

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

PendingHostApproval::new196–201 ↗
fn new() -> Self

作用:创建一个“某个主机审批正在等结果”的共享等待点。它像排队叫号牌,后来的同目标请求可以一起等同一个结果。

数据流:没有业务输入 → 它新建一个空决策槽和一个通知器 → 出来是 PendingHostApproval。

调用关系:NetworkApprovalService::get_or_create_pending_approval 在第一次遇到某个目标时调用它;测试 pending_waiters_receive_owner_decision 也会直接验证它的等待行为。

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

PendingHostApproval::wait_for_decision203–211 ↗
async fn wait_for_decision(&self) -> PendingApprovalDecision

作用:让后来撞上同一个目标的请求等待第一个审批结果。这样用户只需要回答一次。

数据流:进去的是等待对象 → 它循环检查是否已有决定;没有就等通知 → 出来是最终的 PendingApprovalDecision。

调用关系:handle_inline_policy_request 发现自己不是这个目标的审批发起者时,会调用它等 owner 的决定。

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

PendingHostApproval::set_decision213–219 ↗
async fn set_decision(&self, decision: PendingApprovalDecision)

作用:写入某个目标的审批结果,并叫醒所有正在等这个结果的请求。

数据流:进去的是等待对象和一个决定 → 它把决定放进受锁保护的槽里,再通知所有等待者 → 出来没有返回值,但等待者会继续执行。

调用关系:handle_inline_policy_request 在审批失败、审批成功或前置检查拒绝后调用它,让同目标的其他请求拿到同一结论。

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

NetworkApprovalService::default244–251 ↗
fn default() -> Self

作用:创建一个空的网络审批服务。刚创建时没有活跃调用、没有等待审批、也没有本会话批准或拒绝过的主机。

数据流:没有输入 → 它初始化几张受互斥锁保护的表:活跃调用表、等待审批表、会话批准集合、会话拒绝集合 → 出来是可用的 NetworkApprovalService。

调用关系:会话和测试环境创建服务时会用它;后续所有网络审批、缓存和拒绝记录都围绕这个服务展开。

调用图:被 14 处调用(new, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, active_call_preserves_triggering_command_context, blocked_request_policy_does_not_override_user_denial_outcome, deferred_finish_reuses_denial_result_after_first_consumer, finish_call_returns_denial_and_unregisters_active_call, pending_approvals_are_deduped_per_host_protocol_and_port, pending_approvals_do_not_dedupe_across_ports, record_blocked_request_ignores_ambiguous_unattributed_blocked_requests (+4 more));外部调用 4 个(new, new, new, default)。

NetworkApprovalService::sync_session_approved_hosts_to257–262 ↗
async fn sync_session_approved_hosts_to(&self, other: &Self)

作用:把当前服务里“本会话已批准的主机”复制到另一个服务里。它用于让两个会话或上下文共享已放行的网络目标。

数据流:进去的是当前服务和目标服务 → 它复制当前 approved hosts,清空目标的 approved hosts,再填入复制结果 → 出来没有返回值,但目标服务的批准缓存被替换。

调用关系:它只同步批准列表,不同步拒绝列表或正在等待的审批;适合会话切换或复制上下文时使用。

NetworkApprovalService::register_call264–284 ↗
async fn register_call(
        &self,
        registration_id: String,
        turn_id: String,
        trigger: GuardianNetworkAccessTrigger,
        command: String,
        cancellation_token: Can

作用:登记一次正在运行、可能触发网络审批的工具调用。这样如果网络被拒,系统知道该通知哪一次调用停下。

数据流:进去的是登记编号、turn_id、触发来源、命令文本和取消令牌 → 它包装成 ActiveNetworkApprovalCall,放入 active_calls 表 → 出来没有返回值,但服务开始追踪这次调用。

调用关系:begin_network_approval 会调用它;后续 record_call_outcome、resolve_single_active_call 和 finish_call 都依赖这条登记记录。

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

NetworkApprovalService::unregister_call286–288 ↗
async fn unregister_call(&self, registration_id: &str)

作用:取消登记一次工具调用。它用于工具结束或不再需要网络审批跟踪时清理记录。

数据流:进去的是 registration_id → 它调用 remove_call 删除活跃调用和对应结果 → 出来没有返回值。

调用关系:它是 remove_call 的公开包装,给外部流程一个明确的“注销调用”入口。

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

NetworkApprovalService::resolve_single_active_call290–300 ↗
async fn resolve_single_active_call(&self) -> Option<Arc<ActiveNetworkApprovalCall>>

作用:在当前只有一个活跃工具调用时,把它认作这次网络请求的主人。若同时有多个调用,它不会猜,直接返回没有主人。

数据流:进去的是服务自身 → 它查看 active_calls 表的数量 → 如果正好一个,出来这个调用;否则出来 None。

调用关系:handle_inline_policy_request 和 record_outcome_for_single_active_call 都用它。这个保守做法避免把一个网络拒绝错记到另一个并发工具上。

调用图:被 2 处调用(handle_inline_policy_request, record_outcome_for_single_active_call)。

NetworkApprovalService::get_or_create_pending_approval302–314 ↗
async fn get_or_create_pending_approval(
        &self,
        key: HostApprovalKey,
    ) -> (Arc<PendingHostApproval>, bool)

作用:为某个网络目标取得正在等待的审批;如果还没人问,就创建一个新的等待点。它用来合并重复审批。

数据流:进去的是 HostApprovalKey → 它查 pending_host_approvals 表;存在就返回旧等待点和 false,不存在就新建、存入并返回 true → 出来是等待点和“我是不是发起者”的标记。

调用关系:handle_inline_policy_request 用它决定自己是负责发起审批的 owner,还是只需要等别人审批完。

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

NetworkApprovalService::record_outcome_for_single_active_call316–322 ↗
async fn record_outcome_for_single_active_call(&self, outcome: NetworkApprovalOutcome)

作用:把一个拒绝结果记到当前唯一的活跃工具调用上。如果现在有多个调用,它宁可不记,也不乱记。

数据流:进去的是拒绝结果 → 它先找唯一活跃调用;找到就按 registration_id 记录结果,找不到就什么也不做 → 出来没有返回值,但可能触发取消令牌。

调用关系:record_blocked_request 和 handle_inline_policy_request 在无法直接归属但只有一个候选调用时,会通过它记录失败。它再把具体写入交给 record_call_outcome。

调用图:调用 2 个内部函数(record_call_outcome, resolve_single_active_call);被 2 处调用(handle_inline_policy_request, record_blocked_request)。

NetworkApprovalService::take_call_outcome325–328 ↗
async fn take_call_outcome(&self, registration_id: &str) -> Option<NetworkApprovalOutcome>

作用:测试用的小工具:取走某个调用保存的审批结果。取走后表里就没有这条结果了。

数据流:进去的是 registration_id → 它从 call_outcomes 表删除并返回对应结果 → 出来是可选的 NetworkApprovalOutcome。

调用关系:它只在测试编译时存在,用来验证拒绝结果有没有被正确记录。

NetworkApprovalService::record_call_outcome330–347 ↗
async fn record_call_outcome(&self, registration_id: &str, outcome: NetworkApprovalOutcome)

作用:给指定工具调用记录网络审批结果,并通知该调用应该停止。它还保护用户拒绝结果不被后来的策略拒绝覆盖。

数据流:进去的是 registration_id 和结果 → 它确认调用还活跃;如果已有用户拒绝,就不覆盖;否则写入结果 → 然后取消该调用的取消令牌。

调用关系:handle_inline_policy_request 和 record_outcome_for_single_active_call 都会调用它。它是“审批结论影响正在跑的工具”的关键连接点。

调用图:被 2 处调用(handle_inline_policy_request, record_outcome_for_single_active_call);外部调用 1 个(matches!)。

NetworkApprovalService::remove_call349–353 ↗
async fn remove_call(&self, registration_id: &str) -> Option<NetworkApprovalOutcome>

作用:从服务里删除一次活跃调用,并顺手取出它的审批结果。

数据流:进去的是 registration_id → 它从 active_calls 移除该调用,再从 call_outcomes 移除对应结果 → 出来是可选的 NetworkApprovalOutcome。

调用关系:finish_call_outcome 和 unregister_call 都通过它清理状态,避免调用结束后留下脏记录。

调用图:被 2 处调用(finish_call_outcome, unregister_call)。

NetworkApprovalService::finish_call_outcome355–357 ↗
async fn finish_call_outcome(&self, registration_id: &str) -> Option<NetworkApprovalOutcome>

作用:结束某次调用的网络审批跟踪,并拿到最后记录的结果。

数据流:进去的是 registration_id → 它调用 remove_call 清理活跃调用和结果 → 出来是可选的 NetworkApprovalOutcome。

调用关系:finish_call 和 DeferredNetworkApproval::finish 都会间接或直接用它取得收尾结果。

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

NetworkApprovalService::finish_call359–361 ↗
async fn finish_call(&self, registration_id: &str) -> Result<(), ToolError>

作用:结束一次立即模式的网络审批,并把结果变成工具执行的成功或失败。

数据流:进去的是 registration_id → 它取出并清理该调用结果 → 再把结果转换成 Result → 出来是 Ok 或 ToolError。

调用关系:finish_immediate_network_approval 调用它;它内部依赖 finish_call_outcome 和 network_approval_outcome_to_result。

调用图:调用 2 个内部函数(finish_call_outcome, network_approval_outcome_to_result)。

NetworkApprovalService::record_blocked_request363–370 ↗
async fn record_blocked_request(&self, blocked: BlockedRequest)

作用:记录网络代理已经硬性拦下的请求,并把它转成当前工具调用的失败原因。

数据流:进去的是 BlockedRequest → 它先把被拦原因格式化成用户可读消息;如果没有消息就退出;有消息则记录为策略拒绝 → 出来没有返回值,但可能取消当前唯一活跃调用。

调用关系:build_blocked_request_observer 创建的观察器会调用它。它把网络代理层的拦截事件传回工具审批服务。

调用图:调用 2 个内部函数(denied_network_policy_message, record_outcome_for_single_active_call);外部调用 1 个(DeniedByPolicy)。

NetworkApprovalService::active_turn_context372–380 ↗
async fn active_turn_context(
        session: &Session,
    ) -> Option<Arc<crate::session::turn_context::TurnContext>>

作用:从会话里取出当前正在执行的 turn 上下文。turn 可以理解成一次用户请求或一轮任务的运行环境。

数据流:进去的是 Session → 它读取 session.active_turn,找到当前任务的 turn_context → 出来是可选的 TurnContext。

调用关系:handle_inline_policy_request 需要它来知道当前权限配置、工作目录、审批策略和会话编号;没有上下文时会拒绝网络请求。

NetworkApprovalService::format_network_target382–384 ↗
fn format_network_target(protocol: &str, host: &str, port: u16) -> String

作用:把协议、主机和端口拼成一段好读的网络目标文本,比如 https://example.com:443。

数据流:进去的是协议文本、主机名和端口 → 它按统一格式拼接 → 出来是目标字符串。

调用关系:handle_inline_policy_request 用它生成提示给用户或 Guardian 看的目标说明。

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

NetworkApprovalService::approval_id_for_key386–388 ↗
fn approval_id_for_key(key: &HostApprovalKey) -> String

作用:为某个网络目标生成稳定的审批编号。编号里包含协议、主机和端口。

数据流:进去的是 HostApprovalKey → 它按 network#协议#主机#端口 拼接 → 出来是审批 id 字符串。

调用关系:handle_inline_policy_request 用它作为权限钩子和审批请求的标识,方便同一个目标被一致识别。

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

NetworkApprovalService::handle_inline_policy_request390–671 ↗
async fn handle_inline_policy_request(
        &self,
        session: Arc<Session>,
        request: NetworkPolicyRequest,
    ) -> NetworkDecision

作用:这是本文件的核心流程:网络代理问“这个请求能不能放行”时,它给出最终答案。它会查缓存、合并重复请求、跑自动钩子、问 Guardian 或用户,并记录结果。

数据流:进去的是 Session 和 NetworkPolicyRequest → 它把请求转成审批 key,先查会话拒绝和批准缓存;没有缓存就创建或加入等待审批;发起者再检查当前 turn、权限配置和审批策略;然后先跑权限钩子,必要时发起 Guardian 或用户审批;最后把批准、拒绝或策略修改落到缓存和调用结果里 → 出来是 NetworkDecision,告诉网络代理放行或拒绝。

调用关系:build_network_policy_decider 创建的网络决策器会调用它。它串起 HostApprovalKey::from_request、get_or_create_pending_approval、run_permission_request_hooks、review_approval_request 或 session.request_command_approval,并在需要时调用 record_call_outcome 或 record_outcome_for_single_active_call。

调用图:调用 10 个内部函数(run_permission_request_hooks, from_request, get_or_create_pending_approval, record_call_outcome, record_outcome_for_single_active_call, resolve_single_active_call, allows_network_approval_flow, permission_profile_allows_network_approval_flow, bash, deny);外部调用 13 个(active_turn_context, approval_id_for_key, format_network_target, DeniedByPolicy, guardian_rejection_message, guardian_timeout_message, review_approval_request, routes_approval_to_guardian, format!, matches! (+3 more))。

build_blocked_request_observer674–683 ↗
fn build_blocked_request_observer(
    network_approval: Arc<NetworkApprovalService>,
) -> Arc<dyn BlockedRequestObserver>

作用:创建一个给网络代理用的“被拦请求观察器”。当代理发现请求被策略挡住时,这个观察器会把消息送回审批服务。

数据流:进去的是 NetworkApprovalService 的共享引用 → 它包装成一个异步回调 → 出来是 BlockedRequestObserver。

调用关系:网络代理层持有这个观察器;回调触发时会调用 NetworkApprovalService::record_blocked_request。

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

build_network_policy_decider685–701 ↗
fn build_network_policy_decider(
    network_approval: Arc<NetworkApprovalService>,
    network_policy_decider_session: Arc<RwLock<std::sync::Weak<Session>>>,
) -> Arc<dyn NetworkPolicyDecider>

作用:创建一个给网络代理用的“网络请求裁判”。代理每遇到一次请求,就通过它问审批服务能不能放行。

数据流:进去的是审批服务和一个指向 Session 的弱引用容器 → 它包装成异步回调;回调时先尝试拿到 Session,拿不到就要求拒绝/询问,拿到就转交审批服务处理 → 出来是 NetworkPolicyDecider。

调用关系:它把底层网络代理和上层会话审批流程接起来,真正的判断交给 NetworkApprovalService::handle_inline_policy_request。

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

begin_network_approval703–738 ↗
async fn begin_network_approval(
    session: &Session,
    turn_id: &str,
    managed_network_active: bool,
    spec: Option<NetworkApprovalSpec>,
) -> Option<ActiveNetworkApproval>

作用:在工具调用开始前登记网络审批。只有启用了受管理网络、并且确实有网络代理配置时,它才会创建活跃审批。

数据流:进去的是 Session、turn_id、网络是否启用、以及可选审批规格 → 如果规格不存在、网络未启用或没有代理,返回 None;否则生成唯一 registration_id 和取消令牌,并在服务里登记调用 → 出来是 ActiveNetworkApproval。

调用关系:run_attempt 在执行工具前会调用它。它把登记动作交给 NetworkApprovalService::register_call,后面工具结束时再由 finish_immediate_network_approval 或延后流程收尾。

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

finish_immediate_network_approval740–753 ↗
async fn finish_immediate_network_approval(
    session: &Session,
    active: ActiveNetworkApproval,
) -> Result<(), ToolError>

作用:收尾立即模式的网络审批。如果运行期间有网络拒绝,它会在这里把工具调用变成失败。

数据流:进去的是 Session 和 ActiveNetworkApproval → 如果没有登记编号就直接成功;有编号就让审批服务结束该调用并取结果 → 出来是 Ok 或 ToolError。

调用关系:run_attempt 在工具执行结束时调用它;具体结果处理交给 NetworkApprovalService::finish_call。

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

finish_deferred_network_approval755–763 ↗
async fn finish_deferred_network_approval(
    session: &Session,
    deferred: Option<DeferredNetworkApproval>,
) -> Result<(), ToolError>

作用:收尾延后模式的网络审批。没有延后审批对象时直接成功,有的话就检查之前是否被网络审批拒绝。

数据流:进去的是 Session 和可选 DeferredNetworkApproval → None 时返回成功;Some 时调用 deferred.finish 从服务取或复用结果 → 出来是 Ok 或 ToolError。

调用关系:run_attempt、finish_deferred_network_approval_for_session 和 network_denial_message_for_session 会调用它。它把实际收尾交给 DeferredNetworkApproval::finish。

调用图:被 3 处调用(run_attempt, finish_deferred_network_approval_for_session, network_denial_message_for_session)。

network-proxy/src/network_policy.rs源码 ↗
domain_logicrequest handling 和 cross-cutting audit

网络代理不能随便让程序访问任何地址,也不能只说“不行”却不留下原因。这个文件就像门口保安的登记本和裁决规则:先定义请求里要看哪些信息,比如协议、域名、端口、客户端地址和 HTTP 方法;再定义结果,是允许、拒绝,还是需要询问用户;最后把每次决定写入审计事件。核心函数 evaluate_host_policy 会先问当前代理状态“这个主机和端口按基础规则能不能访问”。如果基础规则说允许,就直接放行;如果只是“不在允许名单里”,还可以交给外部的 NetworkPolicyDecider(一个可插拔的判断器)再看一次,允许它覆盖基础拒绝。无论结果如何,文件都会统一记录来源、原因、协议、地址、时间和用户相关元数据。这样出问题时,不只是知道请求被挡了,还能知道是谁按什么规则挡的。

函数细节52
NetworkProtocol::as_policy_protocol31–38 ↗
fn as_policy_protocol(self) -> &'static str

作用:把程序内部的网络协议类型,变成审计日志和策略字段里使用的简短字符串。这样日志里不会出现难懂的内部枚举名。

数据流:进去的是一个协议值,比如 HTTP、HTTPS CONNECT 或 SOCKS5 → 它按固定对应关系挑出文本 → 出来的是“http”“https_connect”“socks5_tcp”或“socks5_udp”。

调用关系:当别的流程需要把协议写进策略结果时会用到它,例如 proxy_disabled_response 会用它生成对外可读的协议名称;审计事件也依赖这种统一命名。

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

NetworkPolicyDecision::as_str49–54 ↗
fn as_str(self) -> &'static str

作用:把策略层面的决定转换成日志可写的文字。这里主要区分是真正拒绝,还是需要询问。

数据流:进去的是 Deny 或 Ask → 它做一个简单映射 → 出来的是“deny”或“ask”。

调用关系:evaluate_host_policy 在整理最终审计字段时会需要这种文字形式,让日志系统能用统一格式记录决定。

NetworkDecisionSource::as_str67–74 ↗
fn as_str(self) -> &'static str

作用:把“这个决定是谁做的”转换成稳定的文本。这样审计日志能说清楚来源是基础策略、模式保护、代理状态还是外部判断器。

数据流:进去的是一个决定来源 → 它按来源类型映射 → 出来的是类似“baseline_policy”“mode_guard”“decider”的字符串。

调用关系:所有写审计事件的路径都会借助它把来源写清楚,尤其是 emit_non_domain_policy_decision_audit_event 和 evaluate_host_policy。

NetworkPolicyRequest::new99–118 ↗
fn new(args: NetworkPolicyRequestArgs) -> Self

作用:把一次网络访问需要判断的信息打包成一个请求对象。调用者不用一个字段一个字段散着传,减少漏传和传错的风险。

数据流:进去的是 NetworkPolicyRequestArgs,里面有协议、主机、端口、客户端地址、方法、命令提示等 → 它拆出这些字段并原样装进 NetworkPolicyRequest → 出来的是可交给策略判断流程使用的请求。

调用关系:HTTP CONNECT、普通 HTTP 代理、SOCKS5 TCP 等入口在准备做策略判断前会调用它;测试也用它快速造出各种场景的请求。

调用图:被 9 处调用(http_connect_accept, http_plain_proxy, evaluate_host_policy_emits_domain_event_for_baseline_deny, evaluate_host_policy_emits_domain_event_for_decider_allow_override, evaluate_host_policy_emits_domain_event_for_decider_ask, evaluate_host_policy_emits_metadata_fields, evaluate_host_policy_still_denies_not_allowed_local_without_decider_override, handle_socks5_tcp, inspect_socks5_udp)。

NetworkDecision::deny132–134 ↗
fn deny(reason: impl Into<String>) -> Self

作用:快速创建一个“拒绝访问”的决定,并默认把来源标成外部判断器。适合调用者只关心拒绝原因,不想手动填来源。

数据流:进去的是拒绝原因文字 → 它转交给 deny_with_source,并使用默认来源 Decider → 出来的是一个 Deny 结果。

调用关系:handle_inline_policy_request 和 to_network_decision 会用它把外部或内联策略结果变成统一的 NetworkDecision;真正填字段的活交给 deny_with_source。

调用图:被 2 处调用(handle_inline_policy_request, to_network_decision);外部调用 1 个(deny_with_source)。

NetworkDecision::ask136–138 ↗
fn ask(reason: impl Into<String>) -> Self

作用:快速创建一个“先别放行,需要询问”的决定。它在结构上仍然属于拒绝类结果,但决定类型会标成 ask。

数据流:进去的是需要询问的原因 → 它转交给 ask_with_source,并使用默认来源 Decider → 出来的是一个带 Ask 标记的 Deny 结果。

调用关系:外部判断器可以用它表达“我不能直接允许,需要用户确认”;底层字段填充由 ask_with_source 完成。

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

NetworkDecision::deny_with_source140–152 ↗
fn deny_with_source(reason: impl Into<String>, source: NetworkDecisionSource) -> Self

作用:创建一个带明确来源的拒绝决定。它还会兜底处理空原因,避免日志里出现没有解释的拒绝。

数据流:进去的是原因和来源 → 它把原因转成字符串,如果是空字符串就换成默认的策略拒绝原因 → 出来的是 source 和 decision 都填好的 Deny 结果。

调用关系:NetworkDecision::deny 会调用它;evaluate_host_policy 在基础策略拒绝时也会用它生成标准拒绝结果。

调用图:被 1 处调用(evaluate_host_policy);外部调用 2 个(into, is_empty)。

NetworkDecision::ask_with_source154–166 ↗
fn ask_with_source(reason: impl Into<String>, source: NetworkDecisionSource) -> Self

作用:创建一个带明确来源的“需要询问”决定。它保证即使调用者没给原因,也会有默认原因可写入日志。

数据流:进去的是原因和来源 → 它把原因标准化,空原因会替换成默认策略拒绝原因 → 出来的是 decision 为 Ask 的 Deny 结果。

调用关系:NetworkDecision::ask 会调用它;当某个判断来源想表达“需要问用户”时,这个函数负责生成统一格式。

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

emit_block_decision_audit_event179–184 ↗
fn emit_block_decision_audit_event(
    state: &NetworkProxyState,
    args: BlockDecisionAuditEventArgs<'_>,
)

作用:记录一次非域名策略的“拦下”审计事件。比如不是按域名名单判断,而是因为方法、模式或代理状态被拦。

数据流:进去的是代理状态和拦截事件参数 → 它把决定固定为 deny → 交给 emit_non_domain_policy_decision_audit_event 写统一审计日志。

调用关系:emit_http_block_decision_audit_event 和 emit_socks_block_decision_audit_event 会在 HTTP 或 SOCKS 请求被拦时调用它;它自己不直接写日志,而是交给统一的非域名事件函数。

调用图:调用 1 个内部函数(emit_non_domain_policy_decision_audit_event);被 2 处调用(emit_http_block_decision_audit_event, emit_socks_block_decision_audit_event)。

emit_allow_decision_audit_event186–191 ↗
fn emit_allow_decision_audit_event(
    state: &NetworkProxyState,
    args: BlockDecisionAuditEventArgs<'_>,
)

作用:记录一次非域名策略的“允许”审计事件。它用于不是域名白名单那条主流程,但仍然需要留下放行记录的场景。

数据流:进去的是代理状态和放行事件参数 → 它把决定固定为 allow → 交给 emit_non_domain_policy_decision_audit_event 生成日志。

调用关系:emit_http_allow_decision_audit_event 会在 HTTP 侧需要记录放行时调用它;后续统一由 emit_non_domain_policy_decision_audit_event 和 emit_policy_audit_event 完成。

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

emit_non_domain_policy_decision_audit_event193–213 ↗
fn emit_non_domain_policy_decision_audit_event(
    state: &NetworkProxyState,
    args: BlockDecisionAuditEventArgs<'_>,
    decision: &'static str,
)

作用:把非域名类的允许或拒绝,整理成统一的策略审计事件参数。它负责标明这次判断的范围不是 domain。

数据流:进去的是状态、事件参数和 allow/deny 文本 → 它补上 scope 为 non_domain、override 为 false 等统一字段 → 输出不是返回值,而是调用 emit_policy_audit_event 写日志。

调用关系:emit_allow_decision_audit_event 和 emit_block_decision_audit_event 都会先到这里;它是非域名审计事件通往通用日志函数的中转站。

调用图:调用 1 个内部函数(emit_policy_audit_event);被 2 处调用(emit_allow_decision_audit_event, emit_block_decision_audit_event)。

emit_policy_audit_event228–255 ↗
fn emit_policy_audit_event(state: &NetworkProxyState, args: PolicyAuditEventArgs<'_>)

作用:真正把策略决定写成 tracing 审计事件。tracing 是 Rust 常用的事件日志系统,这里用它记录可被收集和分析的结构化日志。

数据流:进去的是代理状态和已经整理好的审计字段 → 它从状态里读取会话、用户、应用版本等审计元数据,再补上当前时间 → 出来的是一条带固定事件名和大量字段的日志事件。

调用关系:evaluate_host_policy 会用它记录域名策略结果,emit_non_domain_policy_decision_audit_event 会用它记录非域名结果;它是本文件所有审计日志的最后出口。

调用图:调用 1 个内部函数(audit_metadata);被 2 处调用(emit_non_domain_policy_decision_audit_event, evaluate_host_policy);外部调用 1 个(event!)。

audit_timestamp257–259 ↗
fn audit_timestamp() -> String

作用:生成审计日志使用的当前 UTC 时间戳。格式固定到毫秒,便于不同系统按时间对齐。

数据流:进去没有参数 → 它读取当前 UTC 时间,并转成 RFC3339 格式,也就是类似 2025-01-01T12:00:00.123Z 的标准时间文本 → 出来的是时间字符串。

调用关系:emit_policy_audit_event 在写每条审计事件时会用它,保证每条策略记录都有统一格式的发生时间。

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

Arc::decide274–276 ↗
fn decide(&self, req: NetworkPolicyRequest) -> NetworkPolicyDeciderFuture<'_>

作用:让放在 Arc 里的判断器也能像普通判断器一样使用。Arc 可以理解成多人共用同一个对象的安全引用。

数据流:进去的是共享指针里的判断器和一次网络请求 → 它把请求转交给里面真正的判断器,并包装成异步任务 → 出来的是未来会产生 NetworkDecision 的异步结果。

调用关系:evaluate_host_policy 接收的外部 decider 通常就是 Arc<dyn NetworkPolicyDecider>;这个实现让共享判断器能无缝接入同一套 decide 调用。

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

F::decide284–286 ↗
fn decide(&self, req: NetworkPolicyRequest) -> NetworkPolicyDeciderFuture<'_>

作用:让普通闭包或函数也能当作网络策略判断器使用。这样测试和简单场景不必专门写一个结构体。

数据流:进去的是一个请求 → 它调用这个闭包或函数,并把返回的异步结果装箱固定形状 → 出来的是统一类型的异步策略决定。

调用关系:测试里经常用闭包创建 decider;evaluate_host_policy 只认识 NetworkPolicyDecider trait,这个实现把闭包接到这个接口上。

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

evaluate_host_policy289–359 ↗
async fn evaluate_host_policy(
    state: &NetworkProxyState,
    decider: Option<&Arc<dyn NetworkPolicyDecider>>,
    request: &NetworkPolicyRequest,
) -> Result<NetworkDecision>

作用:这是域名和端口访问判断的主函数。它决定一次网络请求到底允许、拒绝,还是需要询问,并且一定会写审计记录。

数据流:进去的是代理状态、可选外部判断器和网络请求 → 它先问 state.host_blocked 主机端口是否被基础策略挡住;如果允许就放行,如果是“不在允许范围”且有 decider,就再问 decider;随后整理决定、来源、原因和是否覆盖基础策略 → 出来的是 NetworkDecision,同时写入一条 domain 范围的审计事件。

调用关系:HTTP CONNECT、普通 HTTP 代理、SOCKS5 TCP 和 SOCKS5 UDP 检查流程会在处理请求时调用它;它内部会调用 map_decider_decision 统一外部判断器结果,并调用 emit_policy_audit_event 留痕。

调用图:调用 4 个内部函数(deny_with_source, emit_policy_audit_event, map_decider_decision, host_blocked);被 5 处调用(http_connect_accept, http_plain_proxy, evaluate_host_policy_still_denies_not_allowed_local_without_decider_override, handle_socks5_tcp, inspect_socks5_udp);外部调用 2 个(matches!, clone)。

map_decider_decision361–372 ↗
fn map_decider_decision(decision: NetworkDecision) -> NetworkDecision

作用:把外部判断器返回的结果清洗成统一来源。这样无论外部判断器内部怎么构造,日志都会明确写成 decider 来源。

数据流:进去的是外部判断器给出的 NetworkDecision → 如果是 Allow 就原样允许;如果是 Deny,就保留原因和 deny/ask 类型,但把 source 改成 Decider → 出来的是规范化后的决定。

调用关系:evaluate_host_policy 在咨询 decider 后马上调用它,避免外部判断器把来源误标成基础策略或其他来源。

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

test_support::CapturedEvent::field402–404 ↗
fn field(&self, name: &str) -> Option<&str>

作用:在测试捕获到的日志事件里,按字段名取出字段值。它让断言能像查表一样检查日志内容。

数据流:进去的是字段名 → 它到事件字段表里查找 → 出来的是这个字段的字符串值,找不到就返回空。

调用关系:测试里的 find_event_by_name 和各种断言会用它检查 event.name、network.policy.decision 等字段是否正确。

test_support::EventCollector::events414–419 ↗
fn events(&self) -> Vec<CapturedEvent>

作用:取出测试期间收集到的所有日志事件。它会复制一份,避免测试代码直接改内部存储。

数据流:进去没有业务参数 → 它给内部事件列表加锁,取出并克隆当前列表 → 出来的是 CapturedEvent 列表。

调用关系:capture_events 在被测异步代码跑完后调用它,把日志事件交给测试用例检查。

test_support::EventCollector::enabled423–425 ↗
fn enabled(&self, _metadata: &Metadata<'_>) -> bool

作用:告诉 tracing 系统:测试收集器愿意接收所有事件。这里不按来源或级别过滤。

数据流:进去的是日志元数据,但这个函数不使用它 → 直接返回 true → 结果是所有事件都会继续进入收集流程。

调用关系:作为 tracing::Subscriber 的一部分由 tracing 框架调用;它保证测试不会因为过滤条件漏掉审计事件。

test_support::EventCollector::register_callsite427–429 ↗
fn register_callsite(&self, _metadata: &'static Metadata<'static>) -> Interest

作用:告诉 tracing 系统:某个日志产生点永远值得关注。callsite 可以理解成代码里发日志的位置。

数据流:进去的是日志产生点的元数据 → 它返回 always 兴趣标记 → 结果是这些位置产生的事件都会被送给收集器。

调用关系:tracing 框架在注册日志点时调用它;它配合 enabled 让测试收集器完整捕获事件。

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

test_support::EventCollector::max_level_hint431–433 ↗
fn max_level_hint(&self) -> Option<tracing::level_filters::LevelFilter>

作用:告诉 tracing 系统这个测试收集器最高愿意接收 TRACE 级别。TRACE 是最细的日志级别。

数据流:进去没有使用的信息 → 它返回 TRACE 级别提示 → 出来的是一个过滤提示,表示尽量别提前丢日志。

调用关系:tracing 框架用它优化日志过滤;测试希望看到 INFO 审计事件,所以给出足够宽的级别。

test_support::EventCollector::new_span435–437 ↗
fn new_span(&self, _span: &Attributes<'_>) -> Id

作用:给测试中的新日志 span 分配一个编号。span 可以理解成一段有开始和结束的日志上下文。

数据流:进去的是 span 属性,但这里不读取 → 它用原子计数器加一生成新编号 → 出来的是 tracing 使用的 Id。

调用关系:tracing 框架创建 span 时调用它;虽然这些测试主要关心 event,但 Subscriber 接口要求提供这个能力。

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

test_support::EventCollector::record439–439 ↗
fn record(&self, _span: &Id, _values: &Record<'_>)

作用:接收 span 字段更新,但测试里不需要处理,所以这是一个空实现。

数据流:进去的是 span 编号和字段值 → 它不读取也不保存 → 出来没有变化。

调用关系:tracing 框架在记录 span 字段时可能调用它;当前测试只验证事件字段,所以这里故意不做事。

test_support::EventCollector::record_follows_from441–441 ↗
fn record_follows_from(&self, _span: &Id, _follows: &Id)

作用:接收 span 之间的跟随关系,但测试里不关心这种关系,所以不保存。

数据流:进去的是两个 span 编号 → 它不处理 → 出来没有状态变化。

调用关系:这是 tracing::Subscriber 接口的一部分;为让 EventCollector 成为合法订阅器,需要实现它。

test_support::EventCollector::event443–453 ↗
fn event(&self, event: &Event<'_>)

作用:真正捕获一条测试期间发出的日志事件。它把事件里的结构化字段转成字符串表,方便后面断言。

数据流:进去的是 tracing 事件 → 它创建 FieldVisitor 逐个读取字段,再把事件目标和字段表放进内部列表 → 出来没有返回值,但收集器多了一条 CapturedEvent。

调用关系:emit_policy_audit_event 发出的审计日志会被 tracing 送到这里;capture_events 最后再把这里存下的事件取出来。

调用图:外部调用 3 个(default, metadata, record)。

test_support::EventCollector::enter455–455 ↗
fn enter(&self, _span: &Id)

作用:处理进入 span 的通知,但测试不需要记录,所以为空。

数据流:进去的是 span 编号 → 它不做处理 → 没有输出也不改状态。

调用关系:tracing 框架进入某个日志上下文时调用它;这里存在是为了完整实现 Subscriber 接口。

test_support::EventCollector::exit457–457 ↗
fn exit(&self, _span: &Id)

作用:处理离开 span 的通知,但测试不需要记录,所以为空。

数据流:进去的是 span 编号 → 它不做处理 → 没有输出也不改状态。

调用关系:tracing 框架离开某个日志上下文时调用它;当前审计测试只关注事件本身。

test_support::FieldVisitor::insert466–468 ↗
fn insert(&mut self, field: &Field, value: impl Into<String>)

作用:把一个日志字段存进测试用的字段表。它统一把字段名和值都变成可比较的字符串。

数据流:进去的是字段对象和值 → 它取字段名,把值转成字符串 → 内部 fields 表新增或覆盖这一项。

调用关系:FieldVisitor 的各种 record_* 方法都会调用它;它是把不同类型日志字段统一保存的公共小工具。

调用图:被 9 处调用(record_bool, record_debug, record_error, record_f64, record_i128, record_i64, record_str, record_u128, record_u64);外部调用 2 个(name, into)。

test_support::FieldVisitor::record_str472–474 ↗
fn record_str(&mut self, field: &Field, value: &str)

作用:记录字符串类型的日志字段。

数据流:进去的是字段名和值字符串 → 它把值交给 insert → 字段表里出现这条字符串字段。

调用关系:tracing 在事件字段是字符串时调用它;最终由 insert 完成保存。

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

test_support::FieldVisitor::record_bool476–478 ↗
fn record_bool(&mut self, field: &Field, value: bool)

作用:记录布尔类型的日志字段,也就是 true 或 false。

数据流:进去的是字段名和布尔值 → 它把布尔值转成文字 → 通过 insert 存入字段表。

调用关系:network.policy.override 这类布尔字段会走这里;insert 负责统一落表。

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

test_support::FieldVisitor::record_i64480–482 ↗
fn record_i64(&mut self, field: &Field, value: i64)

作用:记录 64 位有符号整数日志字段。简单说,就是可正可负的整数。

数据流:进去的是字段名和 i64 数字 → 它把数字转成字符串 → 通过 insert 存起来。

调用关系:tracing 遇到 i64 字段时调用它;本测试收集器用统一字符串比较所有字段。

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

test_support::FieldVisitor::record_u64484–486 ↗
fn record_u64(&mut self, field: &Field, value: u64)

作用:记录 64 位无符号整数日志字段。端口号这类非负数字可能会以这种形式出现。

数据流:进去的是字段名和 u64 数字 → 它转成字符串 → 通过 insert 存入字段表。

调用关系:tracing 遇到 u64 字段时调用它;server.port 等数字字段可通过这种方式被测试检查。

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

test_support::FieldVisitor::record_i128488–490 ↗
fn record_i128(&mut self, field: &Field, value: i128)

作用:记录 128 位有符号整数日志字段,用来兼容更大的整数类型。

数据流:进去的是字段名和 i128 数字 → 它转成字符串 → 通过 insert 保存。

调用关系:tracing 如果给出 i128 字段会调用它;它和其他 record_* 方法一起覆盖常见字段类型。

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

test_support::FieldVisitor::record_u128492–494 ↗
fn record_u128(&mut self, field: &Field, value: u128)

作用:记录 128 位无符号整数日志字段,用来兼容很大的非负整数。

数据流:进去的是字段名和 u128 数字 → 它转成字符串 → 通过 insert 放进字段表。

调用关系:tracing 遇到 u128 字段时调用它;测试收集器因此不容易因为字段类型不同而漏记。

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

test_support::FieldVisitor::record_f64496–498 ↗
fn record_f64(&mut self, field: &Field, value: f64)

作用:记录浮点数日志字段。浮点数就是带小数的数字。

数据流:进去的是字段名和 f64 数字 → 它转成字符串 → 通过 insert 保存。

调用关系:tracing 遇到小数字段时调用它;虽然当前审计字段多为字符串和整数,但收集器保持通用。

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

test_support::FieldVisitor::record_error500–502 ↗
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static))

作用:记录错误类型的日志字段,把错误信息转成文字保存。

数据流:进去的是字段名和错误对象 → 它调用错误的 to_string 得到人能读的消息 → 通过 insert 存入字段表。

调用关系:tracing 遇到 error 字段时调用它;这样测试也能检查日志里的错误内容。

调用图:调用 1 个内部函数(insert);外部调用 1 个(to_string)。

test_support::FieldVisitor::record_debug504–506 ↗
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug)

作用:记录只能用调试格式展示的日志字段。调试格式就是开发者常见的 {:?} 输出。

数据流:进去的是字段名和可调试显示的值 → 它格式化成字符串 → 通过 insert 保存。

调用关系:tracing 遇到没有专门类型记录方法的字段时可能调用它;insert 仍然负责最终存储。

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

test_support::capture_events509–519 ↗
async fn capture_events(f: F) -> (T, Vec<CapturedEvent>)

作用:在测试里运行一段异步代码,并把这段代码期间产生的 tracing 事件一起抓出来。它像给测试临时装了一个日志录音机。

数据流:进去的是一个会返回异步任务的函数 → 它设置默认日志订阅器,运行这段异步代码,然后取出收集到的事件 → 出来的是业务结果和事件列表。

调用关系:多个审计测试用它包住 evaluate_host_policy 或 emit_block_decision_audit_event;它内部依赖 EventCollector 收集事件。

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

test_support::find_event_by_name521–528 ↗
fn find_event_by_name(
        events: &'a [CapturedEvent],
        event_name: &str,
    ) -> Option<&'a CapturedEvent>

作用:从一堆捕获到的日志事件里,找到指定 event.name 的那一条。

数据流:进去的是事件列表和目标事件名 → 它逐个查看 event.name 字段 → 出来的是匹配事件的引用,找不到就返回空。

调用关系:各个测试用它定位策略审计事件,然后再检查里面的字段;它依赖 CapturedEvent::field 读取事件名。

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

tests::StaticReloader::maybe_reload565–567 ↗
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>>

作用:测试用的配置重载器:表示“现在没有新配置”。它避免测试因为真实文件或外部配置变化而不稳定。

数据流:进去没有实际配置输入 → 它返回一个异步结果,内容是 Ok(None) → 表示不需要更新当前配置。

调用关系:state_with_metadata 创建测试状态时会用 StaticReloader;这个方法满足 ConfigReloader 接口要求。

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

tests::StaticReloader::reload_now569–571 ↗
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState>

作用:测试用的强制重载:直接返回预先保存的配置状态。这样测试可以稳定地拿到同一份配置。

数据流:进去没有额外参数 → 它克隆内部保存的 ConfigState 并包进异步 Ok → 出来的是这份固定配置。

调用关系:NetworkProxyState 在需要立即加载配置时会通过 ConfigReloader 调它;测试里用固定返回值替代真实重载。

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

tests::StaticReloader::source_label573–575 ↗
fn source_label(&self) -> String

作用:给测试重载器起一个可读名字。这个名字主要用于提示或日志,而不是影响策略判断。

数据流:进去没有参数 → 它返回“static test reloader”字符串 → 外部可用这个标签识别配置来源。

调用关系:作为 ConfigReloader 接口的一部分被代理状态或调试信息使用;它说明当前来源是测试里的静态配置。

tests::state_with_metadata578–590 ↗
fn state_with_metadata(metadata: NetworkProxyAuditMetadata) -> NetworkProxyState

作用:为测试创建一个带审计元数据的 NetworkProxyState。这样测试可以验证用户、会话、模型等信息是否写进日志。

数据流:进去的是一组审计元数据 → 它创建启用的 Full 网络配置,构建 ConfigState,再配上 StaticReloader 和元数据 → 出来的是可用于策略评估的测试状态。

调用关系:evaluate_host_policy_emits_metadata_fields 调用它准备测试环境;它把 build_config_state 和 with_reloader_and_audit_metadata 串起来。

调用图:调用 3 个内部函数(default, with_reloader_and_audit_metadata, build_config_state);外部调用 2 个(new, default)。

tests::is_rfc3339_utc_millis592–608 ↗
fn is_rfc3339_utc_millis(timestamp: &str) -> bool

作用:检查一个时间字符串是不是 UTC、毫秒精度的 RFC3339 格式。它用于确认审计时间戳长得对。

数据流:进去的是时间字符串 → 它检查长度、分隔符位置、结尾 Z,以及数字位置 → 出来 true 或 false。

调用关系:evaluate_host_policy_emits_domain_event_for_decider_allow_override 用它验证 event.timestamp;它不解析时间,只做格式检查。

tests::evaluate_host_policy_emits_domain_event_for_decider_allow_override611–675 ↗
async fn evaluate_host_policy_emits_domain_event_for_decider_allow_override()

作用:测试当基础策略拒绝但外部判断器允许时,最终会放行,并且审计日志会标明这是 decider 覆盖了基础策略。

数据流:进去的是测试构造的默认状态、计数器、允许型 decider 和 example.com 请求 → 它运行 evaluate_host_policy 并捕获事件 → 断言结果为 Allow、decider 被调用一次、日志字段包含 allow、decider、override=true 和正确时间格式。

调用关系:这个测试覆盖 evaluate_host_policy、NetworkPolicyRequest::new、capture_events 和 find_event_by_name 的协作;它确保外部判断器覆盖路径不会悄悄漏审计。

调用图:调用 2 个内部函数(default, new);外部调用 7 个(new, new, assert!, assert_eq!, network_proxy_state_for_policy, capture_events, find_event_by_name)。

tests::evaluate_host_policy_emits_domain_event_for_baseline_deny678–721 ↗
async fn evaluate_host_policy_emits_domain_event_for_baseline_deny()

作用:测试基础域名策略明确拒绝某个域名时,结果和审计日志都写成 baseline_policy 的 deny。

数据流:进去的是允许 example.com、拒绝 blocked.com 的测试配置和一个 blocked.com 请求 → 它执行 evaluate_host_policy 并抓日志 → 断言结果是基础策略拒绝,日志包含 deny、baseline_policy、拒绝原因、方法和客户端地址。

调用关系:这个测试验证 evaluate_host_policy 不需要 decider 也能正确处理明确黑名单;同时检查审计字段通过 emit_policy_audit_event 写对。

调用图:调用 2 个内部函数(default, new);外部调用 5 个(assert_eq!, network_proxy_state_for_policy, capture_events, find_event_by_name, vec!)。

tests::evaluate_host_policy_emits_domain_event_for_decider_ask724–762 ↗
async fn evaluate_host_policy_emits_domain_event_for_decider_ask()

作用:测试外部判断器返回“需要询问”时,最终决定会记录为 ask,而不是普通 deny。

数据流:进去的是默认状态、返回 NetworkDecision::ask 的 decider 和 GET 请求 → 它运行 evaluate_host_policy 并捕获事件 → 出来通过断言确认结果 source 是 Decider、decision 是 Ask,日志里也写 ask。

调用关系:这个测试把 NetworkDecision::ask、evaluate_host_policy、map_decider_decision 和审计日志串起来,确保询问路径不会被误写成直接拒绝。

调用图:调用 2 个内部函数(default, new);外部调用 5 个(new, assert_eq!, network_proxy_state_for_policy, capture_events, find_event_by_name)。

tests::evaluate_host_policy_emits_metadata_fields765–806 ↗
async fn evaluate_host_policy_emits_metadata_fields()

作用:测试审计日志会带上会话、用户、应用版本、终端和模型等元数据。没有这些字段,事后追查会很难。

数据流:进去的是一组假的审计元数据和一个请求 → 它用 state_with_metadata 创建状态,运行 evaluate_host_policy 并捕获日志 → 断言日志字段逐项包含这些元数据。

调用关系:这个测试重点覆盖 emit_policy_audit_event 从 NetworkProxyState.audit_metadata 读取并写入字段的行为。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, capture_events, find_event_by_name, state_with_metadata)。

tests::emit_block_decision_audit_event_emits_non_domain_event809–854 ↗
async fn emit_block_decision_audit_event_emits_non_domain_event()

作用:测试非域名类的拦截事件会写成统一的新审计事件,而不是旧事件名。

数据流:进去的是默认状态和一个因为方法不允许而被挡的 HTTP 事件参数 → 它调用 emit_block_decision_audit_event 并捕获日志 → 断言日志 scope 是 non_domain、decision 是 deny、source 是 mode_guard,并且没有旧事件名。

调用关系:这个测试覆盖 emit_block_decision_audit_event 到 emit_non_domain_policy_decision_audit_event 再到 emit_policy_audit_event 的完整链路。

调用图:调用 1 个内部函数(default);外部调用 4 个(assert_eq!, network_proxy_state_for_policy, capture_events, find_event_by_name)。

tests::evaluate_host_policy_still_denies_not_allowed_local_without_decider_override857–885 ↗
async fn evaluate_host_policy_still_denies_not_allowed_local_without_decider_override()

作用:测试本地地址被配置禁止时,如果没有外部判断器覆盖,仍然必须拒绝。这样防止本机服务被意外访问。

数据流:进去的是只允许 example.com 且禁止本地绑定的配置,以及 127.0.0.1 请求 → 它运行 evaluate_host_policy → 出来断言决定是基础策略拒绝,原因是本地地址不允许。

调用关系:这个测试直接调用 evaluate_host_policy,确认 HostBlockReason::NotAllowedLocal 这类更具体的拒绝不会被当成可随意覆盖的普通 not_allowed。

调用图:调用 3 个内部函数(default, new, evaluate_host_policy);外部调用 3 个(assert_eq!, network_proxy_state_for_policy, vec!)。

tests::ask_uses_decider_source_and_ask_decision888–897 ↗
fn ask_uses_decider_source_and_ask_decision()

作用:测试 NetworkDecision::ask 生成的结果来源是 decider,决定类型是 ask。它保护这个便捷函数的默认行为不被改坏。

数据流:进去的是一个不允许原因 → 它调用 NetworkDecision::ask → 出来通过断言确认得到的 Deny 结构里 source 和 decision 字段都正确。

调用关系:这个测试专门覆盖 NetworkDecision::ask 和 ask_with_source 的约定;evaluate_host_policy 的 ask 路径依赖这个约定。

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

network-proxy/src/runtime.rs源码 ↗
domain_logiccross-cutting / request handling / config reload

这个文件保存并维护网络代理当前正在使用的规则。可以把它想成公司门卫手里的最新访客名单:名单会变,门卫要随时刷新;有人来访时,先看黑名单,再看是不是想进机房、本机或内网,最后看白名单。这里的 NetworkProxyState 就是这位门卫,内部用读写锁(一种防止多人同时乱改同一份数据的锁)保护配置。它还会记录被拦下的请求,最多留最近 200 条,并可通知外部观察者。它支持配置重载,所以改了配置文件后不必重启代理。比较重要的一点是:即使域名在白名单里,如果 DNS(把域名查成 IP 地址的系统)显示它指向本机或私有网络,也会被当成危险连接拦住,除非明确允许。

函数细节116
HostBlockReason::as_str68–74 ↗
fn as_str(self) -> &'static str

作用:把“为什么拦截”的内部枚举值,换成日志和接口里用的固定文字。

数据流:输入是一个拦截原因 → 根据它是明确禁止、未允许、还是本地网络未允许,选出对应字符串 → 输出这段原因文字,不改任何状态。

调用关系:它主要被 HostBlockReason::fmt 使用,让原因可以被直接打印出来。

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

HostBlockReason::fmt78–80 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:让拦截原因能像普通文字一样显示出来,方便写日志或报错。

数据流:输入是格式化器和一个原因 → 调用 HostBlockReason::as_str 得到文字 → 把文字写进输出流。

调用关系:它是 Rust 的显示接口实现,外部打印 HostBlockReason 时会自动走这里。

调用图:调用 1 个内部函数(as_str);外部调用 1 个(write_str)。

BlockedRequest::new119–143 ↗
fn new(args: BlockedRequestArgs) -> Self

作用:创建一条“被拦请求”的记录,并自动补上当前时间。

数据流:输入是一组请求信息,比如主机、原因、方法、端口 → 原样放进 BlockedRequest,并调用 unix_timestamp 取当前时间 → 输出完整的被拦记录。

调用关系:代理各处发现请求被拒时会用它生成记录,之后通常交给 NetworkProxyState::record_blocked 保存。

调用图:调用 1 个内部函数(unix_timestamp);被 9 处调用(denied_blocked_request, http_connect_accept, http_plain_proxy, proxy_disabled_response, evaluate_mitm_policy, blocked_snapshot_does_not_consume_entries, drain_blocked_returns_buffered_window, handle_socks5_tcp, inspect_socks5_udp)。

blocked_request_violation_log_line146–157 ↗
fn blocked_request_violation_log_line(entry: &BlockedRequest) -> String

作用:把一条被拦请求整理成一行专门的违规日志,方便机器或人检索。

数据流:输入是 BlockedRequest → 尝试转成 JSON;成功就加上固定前缀,失败就退回成简单的 host/reason 文本 → 输出一行日志字符串。

调用关系:NetworkProxyState::record_blocked 在保存拦截记录时调用它,用来打出统一格式的安全日志。

调用图:被 1 处调用(record_blocked);外部调用 3 个(debug!, format!, to_string)。

Arc::on_blocked_request191–193 ↗
fn on_blocked_request(&self, request: BlockedRequest) -> BlockedRequestObserverFuture<'_>

作用:让用 Arc 包起来的观察者也能接收“请求被拦”的通知。Arc 是共享指针,方便多处安全地引用同一个对象。

数据流:输入是一条被拦请求 → 转交给 Arc 里面真正的观察者 → 输出一个异步任务,任务完成时没有返回值。

调用关系:这是 BlockedRequestObserver 的适配层,NetworkProxyState::record_blocked 通知观察者时可以不关心观察者是否包在 Arc 里。

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

F::on_blocked_request201–203 ↗
fn on_blocked_request(&self, request: BlockedRequest) -> BlockedRequestObserverFuture<'_>

作用:让普通函数或闭包也能当作“被拦请求观察者”使用。

数据流:输入是一条被拦请求 → 调用这个函数/闭包 → 包装成异步任务返回。

调用关系:这是给测试或调用方的方便入口,NetworkProxyState::record_blocked 可以统一调用观察者接口。

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

NetworkProxyState::fmt214–218 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:控制 NetworkProxyState 被调试打印时显示什么,避免把敏感配置完整打出来。

数据流:输入是调试格式化器 → 只写出结构名和省略标记 → 输出安全的调试文本。

调用关系:当日志或调试工具打印 NetworkProxyState 时自动使用它。

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

NetworkProxyState::clone222–229 ↗
fn clone(&self) -> Self

作用:复制一个指向同一份运行时状态的句柄,而不是复制整套配置数据。

数据流:输入是当前 NetworkProxyState → 克隆内部共享指针和审计信息 → 输出一个新的 NetworkProxyState 句柄,仍指向同一份实时状态。

调用关系:代理不同任务需要共享状态时会用到它,保证大家看的是同一套规则。

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

NetworkProxyState::with_reloader233–239 ↗
fn with_reloader(state: ConfigState, reloader: Arc<dyn ConfigReloader>) -> Self

作用:用初始配置和配置重载器创建运行时状态,适合不需要额外审计信息的场景。

数据流:输入是编译好的 ConfigState 和 reloader → 补上默认审计信息 → 交给更完整的构造函数 → 输出 NetworkProxyState。

调用关系:启动代码和测试常用它;它把实际创建工作交给 NetworkProxyState::with_reloader_and_audit_metadata。

调用图:被 8 处调用(build_network_proxy_state, test_network_proxy, network_proxy_state_for_policy, add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed, add_allowed_domain_succeeds_when_managed_baseline_allows_expansion, add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed, state_for_settings, create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths);外部调用 2 个(with_reloader_and_audit_metadata, default)。

NetworkProxyState::with_reloader_and_blocked_observer241–252 ↗
fn with_reloader_and_blocked_observer(
        state: ConfigState,
        reloader: Arc<dyn ConfigReloader>,
        blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
    ) -> Self

作用:创建运行时状态,同时预先装好“被拦请求观察者”。

数据流:输入是配置状态、重载器、可选观察者 → 补上默认审计信息 → 输出 NetworkProxyState。

调用关系:需要实时接收拦截通知的调用方会用它;底层交给最完整的构造函数。

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

NetworkProxyState::with_reloader_and_audit_metadata254–265 ↗
fn with_reloader_and_audit_metadata(
        state: ConfigState,
        reloader: Arc<dyn ConfigReloader>,
        audit_metadata: NetworkProxyAuditMetadata,
    ) -> Self

作用:创建运行时状态,并带上审计日志需要的用户或会话信息。

数据流:输入是配置状态、重载器和审计元数据 → 默认不设置拦截观察者 → 输出 NetworkProxyState。

调用关系:审计相关的构建流程会用它;实际初始化交给 NetworkProxyState::with_reloader_and_audit_metadata_and_blocked_observer。

调用图:被 2 处调用(build_state_with_audit_metadata, state_with_metadata);外部调用 1 个(with_reloader_and_audit_metadata_and_blocked_observer)。

NetworkProxyState::with_reloader_and_audit_metadata_and_blocked_observer267–279 ↗
fn with_reloader_and_audit_metadata_and_blocked_observer(
        state: ConfigState,
        reloader: Arc<dyn ConfigReloader>,
        audit_metadata: NetworkProxyAuditMetadata,
        blocked_requ

作用:这是最完整的构造函数,把状态、重载器、审计信息和观察者一次装好。

数据流:输入是 ConfigState、重载器、审计信息、可选观察者 → 用共享指针和读写锁包起来 → 输出可并发使用的 NetworkProxyState。

调用关系:其他几个 with_reloader 开头的构造函数最终都会来到这里。

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

NetworkProxyState::set_blocked_request_observer281–287 ↗
async fn set_blocked_request_observer(
        &self,
        blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
    )

作用:运行中更换或移除“被拦请求观察者”。

数据流:输入是新的可选观察者 → 拿到写锁 → 用新观察者替换旧观察者。

调用关系:外部系统想开始或停止接收拦截通知时会调用它;之后 record_blocked 会使用新的观察者。

NetworkProxyState::audit_metadata289–291 ↗
fn audit_metadata(&self) -> &NetworkProxyAuditMetadata

作用:取出这份代理状态携带的审计元数据,比如会话、用户、版本信息。

数据流:输入是当前状态 → 直接读取 audit_metadata → 输出只读引用,不修改状态。

调用关系:emit_policy_audit_event 这类审计事件生成代码会调用它。

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

NetworkProxyState::current_cfg293–299 ↗
async fn current_cfg(&self) -> Result<NetworkProxyConfig>

作用:拿到当前最新的网络代理配置。

数据流:输入是状态句柄 → 先调用 reload_if_needed 检查配置是否该刷新 → 读取并克隆当前配置 → 输出配置副本。

调用关系:需要展示或检查完整配置的地方会调用它;它依赖 reload_if_needed 保证看到的不是过期配置。

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

NetworkProxyState::current_patterns301–308 ↗
async fn current_patterns(&self) -> Result<(Vec<String>, Vec<String>)>

作用:拿到当前允许名单和禁止名单里的域名模式。

数据流:输入是状态句柄 → 先按需重载 → 从配置里取 allowed_domains 和 denied_domains → 输出两个字符串列表。

调用关系:测试和管理接口会用它查看规则;它通过 reload_if_needed 保持结果新鲜。

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

NetworkProxyState::enabled310–314 ↗
async fn enabled(&self) -> Result<bool>

作用:查询网络代理策略现在是否启用。

数据流:输入是状态句柄 → 先按需重载 → 读取 network.enabled → 输出 true 或 false。

调用关系:请求处理或启动流程可用它决定是否执行代理限制。

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

NetworkProxyState::force_reload316–342 ↗
async fn force_reload(&self) -> Result<()>

作用:强制重新加载配置,不等文件变化检测。

数据流:输入是状态句柄 → 保存旧配置 → 调用 reloader.reload_now → 成功则记录规则变化、保留拦截历史并替换状态;失败则保留旧状态并报警 → 输出成功或错误。

调用关系:管理命令或外部触发刷新时会调用它;它用 log_policy_changes 记录 allowlist/denylist 的变化。

调用图:调用 1 个内部函数(log_policy_changes);外部调用 2 个(info!, warn!)。

NetworkProxyState::replace_config_state344–353 ↗
async fn replace_config_state(&self, mut new_state: ConfigState) -> Result<()>

作用:用一份已经构建好的新配置状态替换当前状态。

数据流:输入是新的 ConfigState → 先按需重载 → 记录规则差异,保留旧的拦截记录和总数 → 写入新状态。

调用关系:当别的模块已经算好新状态时会用它;它也调用 log_policy_changes 留痕。

调用图:调用 2 个内部函数(reload_if_needed, log_policy_changes);外部调用 1 个(info!)。

NetworkProxyState::host_blocked355–430 ↗
async fn host_blocked(&self, host: &str, port: u16) -> Result<HostBlockDecision>

作用:判断某个主机和端口是否应该被拦,是这个文件最核心的放行/拒绝判断。

数据流:输入是 host 和 port → 刷新配置,解析并规范化主机 → 先查禁止名单,再检查本机/私有 IP,再查允许名单 → 输出 Allowed 或 Blocked,并带上原因。

调用关系:evaluate_host_policy 在处理连接前会调用它;它会用 globset_matches_host_or_unscoped、host_resolves_to_non_public_ip、is_explicit_local_allowlisted 等 helper 做细分检查。

调用图:调用 8 个内部函数(parse, is_loopback_host, is_non_public_ip, unscoped_ip_literal, reload_if_needed, globset_matches_host_or_unscoped, host_resolves_to_non_public_ip, is_explicit_local_allowlisted);被 1 处调用(evaluate_host_policy);外部调用 1 个(Blocked)。

NetworkProxyState::record_blocked432–465 ↗
async fn record_blocked(&self, entry: BlockedRequest) -> Result<()>

作用:保存一条被拦请求,写安全日志,并通知观察者。

数据流:输入是 BlockedRequest → 按需重载 → 放进内存队列,总数加一,超过 200 条就丢最旧的 → 输出成功;同时写调试日志并可能通知观察者。

调用关系:proxy_disabled_response 等拒绝请求的流程会调用它;它用 blocked_request_violation_log_line 生成统一日志。

调用图:调用 2 个内部函数(reload_if_needed, blocked_request_violation_log_line);被 1 处调用(proxy_disabled_response);外部调用 2 个(debug!, clone)。

NetworkProxyState::blocked_snapshot469–473 ↗
async fn blocked_snapshot(&self) -> Result<Vec<BlockedRequest>>

作用:查看当前缓存的被拦请求,但不清空它们。

数据流:输入是状态句柄 → 按需重载 → 克隆队列里的拦截记录 → 输出记录列表,原队列保持不变。

调用关系:监控或管理接口想“看一眼”最近拦截事件时使用它。

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

NetworkProxyState::drain_blocked476–483 ↗
async fn drain_blocked(&self) -> Result<Vec<BlockedRequest>>

作用:取走并清空当前缓存的被拦请求。

数据流:输入是状态句柄 → 按需重载 → 把队列整体拿出来并把内部队列变空 → 输出按先后顺序排列的记录列表。

调用关系:消费型上报流程会用它,表示这些记录已经被取走。

调用图:调用 1 个内部函数(reload_if_needed);外部调用 1 个(take)。

NetworkProxyState::is_unix_socket_allowed485–535 ↗
async fn is_unix_socket_allowed(&self, path: &str) -> Result<bool>

作用:判断某个 Unix socket 路径是否允许访问。Unix socket 是本机进程之间用文件路径通信的一种方式。

数据流:输入是路径字符串 → 按需重载,先确认当前系统支持;再要求路径必须是绝对路径 → 比较允许列表,必要时解析符号链接后的真实路径 → 输出是否允许。

调用关系:HTTP 代理处理 Unix socket 目标时会调用它;它依赖 unix_socket_permissions_supported 和配置里的 allow_unix_sockets。

调用图:调用 4 个内部函数(parse, reload_if_needed, unix_socket_permissions_supported, from_absolute_path);外部调用 3 个(new, canonicalize, warn!)。

NetworkProxyState::method_allowed537–541 ↗
async fn method_allowed(&self, method: &str) -> Result<bool>

作用:检查某个 HTTP 方法是否被当前网络模式允许,比如 GET、POST、CONNECT。

数据流:输入是方法名 → 按需重载 → 调用配置里的模式判断 → 输出 true 或 false。

调用关系:HTTP 请求处理代码会用它决定某种请求方式能不能继续。

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

NetworkProxyState::allow_upstream_proxy543–547 ↗
async fn allow_upstream_proxy(&self) -> Result<bool>

作用:查询是否允许再走上游代理。

数据流:输入是状态句柄 → 按需重载 → 读取 allow_upstream_proxy → 输出布尔值。

调用关系:连接转发逻辑会用它判断能不能使用外部代理链。

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

NetworkProxyState::allow_local_binding549–553 ↗
async fn allow_local_binding(&self) -> Result<bool>

作用:查询是否允许访问本机或内网这类本地绑定目标。

数据流:输入是状态句柄 → 按需重载 → 读取 allow_local_binding → 输出布尔值。

调用关系:请求检查和沙箱参数生成等地方会用它理解当前本地网络策略。

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

NetworkProxyState::network_mode555–559 ↗
async fn network_mode(&self) -> Result<NetworkMode>

作用:读取当前网络模式。

数据流:输入是状态句柄 → 按需重载 → 读取 network.mode → 输出 NetworkMode。

调用关系:请求处理或 UI 展示当前限制级别时会调用它。

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

NetworkProxyState::set_network_mode561–584 ↗
async fn set_network_mode(&self, mode: NetworkMode) -> Result<()>

作用:运行时修改网络模式,同时确保没有违反受管配置约束。

数据流:输入是新模式 → 按需重载,复制当前配置并改模式 → 用 validate_policy_against_constraints 检查 → 若检查通过且约束没被别人同时改掉,就写入新模式并记录日志。

调用关系:管理命令或交互式策略调整会调用它;它必须服从 managed config(由管理员下发的硬性限制)。

调用图:调用 2 个内部函数(reload_if_needed, validate_policy_against_constraints);外部调用 1 个(info!)。

NetworkProxyState::mitm_state586–590 ↗
async fn mitm_state(&self) -> Result<Option<Arc<MitmState>>>

作用:取得 MITM 状态。MITM 在这里指代理能解密检查 HTTPS 流量的中间人功能状态。

数据流:输入是状态句柄 → 按需重载 → 克隆当前 mitm 状态指针 → 输出可选的 MitmState。

调用关系:需要做 HTTPS 拦截或证书处理的流程会调用它。

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

NetworkProxyState::evaluate_mitm_hook_request592–600 ↗
async fn evaluate_mitm_hook_request(
        &self,
        host: &str,
        req: &rama_http::Request,
    ) -> Result<HookEvaluation>

作用:对某个请求运行 MITM hook。hook 是可插入的小规则,用来决定请求要不要特殊处理。

数据流:输入是 host 和 HTTP 请求 → 按需重载 → 把当前 hook 表交给 evaluate_mitm_hooks → 输出 hook 评估结果。

调用关系:MITM 请求处理路径调用它;真正的规则执行在 evaluate_mitm_hooks。

调用图:调用 2 个内部函数(evaluate_mitm_hooks, reload_if_needed)。

NetworkProxyState::host_has_mitm_hooks602–606 ↗
async fn host_has_mitm_hooks(&self, host: &str) -> Result<bool>

作用:快速判断某个主机有没有配置 MITM hook。

数据流:输入是 host → 按需重载并规范化主机名 → 查 mitm_hooks 表 → 输出 true 或 false。

调用关系:连接处理可用它提前判断是否需要进入更重的 MITM 流程。

调用图:调用 2 个内部函数(normalize_host, reload_if_needed)。

NetworkProxyState::add_allowed_domain608–610 ↗
async fn add_allowed_domain(&self, host: &str) -> Result<()>

作用:把一个主机加入允许名单。

数据流:输入是 host → 交给 update_domain_list,并指定目标是 Allow → 成功后配置状态会更新。

调用关系:用户或系统想临时放行一个域名时调用它;实际增删和约束检查由 update_domain_list 完成。

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

NetworkProxyState::add_denied_domain612–614 ↗
async fn add_denied_domain(&self, host: &str) -> Result<()>

作用:把一个主机加入禁止名单。

数据流:输入是 host → 交给 update_domain_list,并指定目标是 Deny → 成功后配置状态会更新。

调用关系:用户或系统想明确封禁一个域名时调用它;实际逻辑由 update_domain_list 统一处理。

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

NetworkProxyState::update_domain_list616–673 ↗
async fn update_domain_list(&self, host: &str, target: DomainListKind) -> Result<()>

作用:统一处理“加入允许名单”或“加入禁止名单”的复杂流程。

数据流:输入是 host 和目标名单类型 → 解析并规范化主机,读取当前配置和约束 → 更新目标名单,同时移除相反名单里的同名项 → 校验约束,重新编译配置状态 → 原子替换当前状态。

调用关系:add_allowed_domain 和 add_denied_domain 都调用它;它会用 DomainListKind 的几个方法判断改哪张名单,并用 build_config_state 重新生成可执行规则。

调用图:调用 10 个内部函数(parse, constraint_field, entries, list_name, opposite_entries, permission, reload_if_needed, log_policy_changes, build_config_state, validate_policy_against_constraints);被 2 处调用(add_allowed_domain, add_denied_domain);外部调用 1 个(info!)。

NetworkProxyState::reload_if_needed675–699 ↗
async fn reload_if_needed(&self) -> Result<()>

作用:在每次读取或判断策略前,悄悄检查配置是否需要刷新。

数据流:输入是状态句柄 → 调用 reloader.maybe_reload → 如果没有新配置就结束;如果有,就保留拦截记录、记录规则变化、替换状态 → 输出成功或错误。

调用关系:几乎所有公开查询和修改函数都会先调用它,保证代理用的是最新规则。

调用图:调用 1 个内部函数(log_policy_changes);被 18 处调用(allow_local_binding, allow_upstream_proxy, blocked_snapshot, current_cfg, current_patterns, drain_blocked, enabled, evaluate_mitm_hook_request, host_blocked, host_has_mitm_hooks (+8 more));外部调用 1 个(info!)。

DomainListKind::list_name709–714 ↗
fn list_name(self) -> &'static str

作用:把名单类型转换成日志里好读的名字,比如 allowlist 或 denylist。

数据流:输入是 Allow 或 Deny → 选择对应名字 → 输出静态字符串。

调用关系:update_domain_list 用它写日志和错误上下文。

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

DomainListKind::constraint_field716–721 ↗
fn constraint_field(self) -> &'static str

作用:告诉代码这个名单对应受管配置里的哪个字段。

数据流:输入是 Allow 或 Deny → 返回 network.allowed_domains 或 network.denied_domains → 输出字段名字符串。

调用关系:update_domain_list 在约束校验失败时用它说明是哪项配置被限制。

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

DomainListKind::permission723–728 ↗
fn permission(self) -> NetworkDomainPermission

作用:把名单类型转换成配置里保存的权限值。

数据流:输入是 Allow 或 Deny → 返回 NetworkDomainPermission::Allow 或 Deny → 输出权限枚举。

调用关系:update_domain_list 用它调用配置的 upsert_domain_permission。

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

DomainListKind::entries730–735 ↗
fn entries(self, network: &crate::config::NetworkProxySettings) -> Vec<String>

作用:取出目标名单当前已有的条目。

数据流:输入是名单类型和网络配置 → 如果是 Allow 就取 allowed_domains,如果是 Deny 就取 denied_domains → 输出字符串列表。

调用关系:update_domain_list 用它判断目标名单里是否已经有这个域名。

调用图:调用 2 个内部函数(allowed_domains, denied_domains);被 1 处调用(update_domain_list)。

DomainListKind::opposite_entries737–742 ↗
fn opposite_entries(self, network: &crate::config::NetworkProxySettings) -> Vec<String>

作用:取出目标名单的“另一边”条目,比如加白名单时查看黑名单。

数据流:输入是名单类型和网络配置 → Allow 时取 denied_domains,Deny 时取 allowed_domains → 输出字符串列表。

调用关系:update_domain_list 用它避免同一个主机同时出现在允许和禁止名单里。

调用图:调用 2 个内部函数(allowed_domains, denied_domains);被 1 处调用(update_domain_list)。

unix_socket_permissions_supported745–747 ↗
fn unix_socket_permissions_supported() -> bool

作用:判断当前平台是否支持这里的 Unix socket 权限控制。

数据流:输入为空 → 检查编译目标系统是否是 macOS → 输出 true 或 false。

调用关系:is_unix_socket_allowed、run 和 HTTP 代理逻辑会用它;非 macOS 上直接不放行这类 socket allowlist 功能。

调用图:被 3 处调用(http_plain_proxy, run, is_unix_socket_allowed);外部调用 1 个(cfg!)。

host_resolves_to_non_public_ip749–788 ↗
async fn host_resolves_to_non_public_ip(
    host: &str,
    port: u16,
    lookup_timeout: Duration,
    lookup: F,
) -> bool

作用:检查一个主机最终是否会指向本机、内网等非公网 IP,防止域名伪装成普通网站绕过限制。

数据流:输入是 host、port、超时时间和 DNS 查询函数 → 如果 host 本身就是 IP 就直接分类;否则限时查 DNS → 查询失败或超时按危险处理;查到任何非公网 IP 也算危险 → 输出是否应视为非公网。

调用关系:NetworkProxyState::host_blocked 在本地网络不允许时调用它;测试也直接调用它验证超时、错误、私网和公网情况。

调用图:调用 1 个内部函数(is_non_public_ip);被 5 处调用(host_blocked, host_resolves_to_non_public_ip_allows_public_resolution, host_resolves_to_non_public_ip_blocks_on_dns_lookup_error, host_resolves_to_non_public_ip_blocks_on_dns_lookup_timeout, host_resolves_to_non_public_ip_blocks_private_resolution);外部调用 2 个(debug!, timeout)。

log_policy_changes790–801 ↗
fn log_policy_changes(previous: &NetworkProxyConfig, next: &NetworkProxyConfig)

作用:记录允许名单和禁止名单相比之前发生了哪些变化。

数据流:输入是旧配置和新配置 → 分别取出 allowed_domains 和 denied_domains → 调用 log_domain_list_changes 记录新增和删除项。

调用关系:force_reload、reload_if_needed、replace_config_state、update_domain_list 在替换配置前后都会调用它。

调用图:调用 1 个内部函数(log_domain_list_changes);被 4 处调用(force_reload, reload_if_needed, replace_config_state, update_domain_list)。

log_domain_list_changes803–837 ↗
fn log_domain_list_changes(list_name: &str, previous: &[String], next: &[String])

作用:具体记录某一张名单新增了哪些项、删除了哪些项。

数据流:输入是名单名、旧列表、新列表 → 不区分大小写地比较集合 → 按新旧原始顺序写出新增和删除日志。

调用关系:它只由 log_policy_changes 调用,是配置变化审计的底层工具。

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

globset_matches_host_or_unscoped839–841 ↗
fn globset_matches_host_or_unscoped(set: &GlobSet, host: &str) -> bool

作用:用通配规则匹配主机,同时兼顾带作用域的 IPv6 地址。

数据流:输入是 GlobSet 和 host → 先直接匹配;如果 host 是带作用域的 IP,再去掉作用域匹配一次 → 输出是否命中。

调用关系:host_blocked 用它查允许名单和禁止名单,避免 fe80::1%lo0 这类地址绕过规则。

调用图:调用 1 个内部函数(unscoped_ip_literal);被 1 处调用(host_blocked);外部调用 1 个(is_match)。

is_explicit_local_allowlisted843–858 ↗
fn is_explicit_local_allowlisted(allowed_domains: &[String], host: &Host) -> bool

作用:判断本机或私网地址是不是被“明确写死”在允许名单里,而不是被通配符顺手放行。

数据流:输入是允许名单和 Host → 跳过 . 以及带通配符的规则 → 只比较精确主机名或去作用域后的 IP → 输出是否明确允许。

调用关系:host_blocked 在本地网络默认禁止时调用它;只有明确列出的 localhost 或私网 IP 才能被放行。

调用图:调用 2 个内部函数(as_str, unscoped_ip_literal);被 1 处调用(host_blocked)。

unix_timestamp860–862 ↗
fn unix_timestamp() -> i64

作用:取得当前 UTC 时间的 Unix 时间戳,也就是从 1970 年开始算的秒数。

数据流:输入为空 → 读取当前 UTC 时间 → 输出 i64 时间戳。

调用关系:BlockedRequest::new 用它给每条拦截记录打时间。

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

network_proxy_state_for_policy865–888 ↗
fn network_proxy_state_for_policy(
    mut network: crate::config::NetworkProxySettings,
) -> NetworkProxyState

作用:测试用的快速造状态函数:给一份网络设置,生成可直接使用的 NetworkProxyState。

数据流:输入是 NetworkProxySettings → 强制启用网络策略,编译允许/禁止 glob 规则和 MITM hook → 装进 ConfigState,再配上 NoopReloader → 输出测试状态。

调用关系:大量测试用它搭建场景;生产代码不依赖它,因为它只在测试编译时存在。

调用图:调用 4 个内部函数(compile_mitm_hooks, compile_allowlist_globset, compile_denylist_globset, with_reloader);被 39 处调用(http_connect_accept_allows_allowlisted_host_in_full_mode, http_connect_accept_blocks_hooked_host_in_full_mode_without_mitm_state, http_connect_accept_blocks_in_limited_mode, http_connect_accept_denies_denylisted_host, http_plain_proxy_attempts_allowed_unix_socket_proxy, http_plain_proxy_blocks_unix_socket_when_method_not_allowed, http_plain_proxy_rejects_absolute_uri_host_header_mismatch, http_plain_proxy_rejects_unix_socket_when_not_allowlisted, http_proxy_listener_accepts_plain_http1_connect_requests, mitm_policy_allows_matching_hooked_write_in_full_mode (+15 more));外部调用 3 个(new, new, default)。

NoopReloader::source_label895–897 ↗
fn source_label(&self) -> String

作用:测试重载器返回一个固定来源说明。

数据流:输入为空 → 返回“test config state”字符串 → 不读取文件也不改状态。

调用关系:测试里的 NetworkProxyState 如果需要写日志,会通过这个方法说明配置来源。

NoopReloader::maybe_reload899–901 ↗
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>>

作用:测试里表示“永远不需要自动重载”。

数据流:输入为空 → 返回一个异步结果 Ok(None) → 当前状态保持不变。

调用关系:NetworkProxyState::reload_if_needed 在测试中调用它时,总是发现没有新配置。

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

NoopReloader::reload_now903–905 ↗
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState>

作用:测试里拒绝强制重载,因为没有真实配置文件可读。

数据流:输入为空 → 返回一个异步错误 → 状态不会被替换。

调用关系:如果测试误调用 force_reload,就会通过这里得到明确错误。

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

tests::strings921–923 ↗
fn strings(entries: &[&str]) -> Vec<String>

作用:把测试里的字符串切片转换成 String 列表,少写重复代码。

数据流:输入是一组 &str → 每个复制成 String → 输出 Vec<String>。

调用关系:network_settings 等测试 helper 会调用它。

tests::network_settings925–934 ↗
fn network_settings(allowed_domains: &[&str], denied_domains: &[&str]) -> NetworkProxySettings

作用:测试用:快速创建带允许名单和禁止名单的网络设置。

数据流:输入是允许域名数组和禁止域名数组 → 从默认设置开始,非空时写入对应名单 → 输出 NetworkProxySettings。

调用关系:大多数 host_blocked 和配置约束测试都用它准备场景。

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

tests::network_settings_with_unix_sockets936–946 ↗
fn network_settings_with_unix_sockets(
        allowed_domains: &[&str],
        denied_domains: &[&str],
        unix_sockets: &[String],
    ) -> NetworkProxySettings

作用:测试用:在普通网络设置基础上再加 Unix socket 允许列表。

数据流:输入是允许域名、禁止域名和 socket 路径列表 → 先调用 network_settings → 再写入 socket 允许列表 → 输出设置。

调用关系:Unix socket 相关测试用它搭建配置。

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

tests::host_blocked_denied_wins_over_allowed949–960 ↗
async fn host_blocked_denied_wins_over_allowed()

作用:验证同一个域名同时在允许和禁止名单里时,禁止名单优先。

数据流:输入是测试框架运行 → 创建同时允许和禁止 example.com 的状态 → 调用 host_blocked → 断言结果是 Denied。

调用关系:覆盖 host_blocked 的第一条决策规则:显式禁止永远赢。

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

tests::host_blocked_requires_allowlist_match963–979 ↗
async fn host_blocked_requires_allowlist_match()

作用:验证配置了允许名单时,只有命中的主机才能访问。

数据流:输入是测试运行 → 允许 example.com → 检查 example.com 放行、8.8.8.8 被 NotAllowed 拦截 → 测试结束。

调用关系:覆盖 host_blocked 的白名单 enforcement 行为。

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

tests::add_allowed_domain_removes_matching_deny_entry982–997 ↗
async fn add_allowed_domain_removes_matching_deny_entry()

作用:验证把域名加入允许名单时,会移除同名禁止项。

数据流:输入是带 deny example.com 的状态 → 调用 add_allowed_domain → 读取名单并检查允许名单有它、禁止名单清空、访问可放行。

调用关系:覆盖 update_domain_list 处理两张名单冲突的逻辑。

调用图:调用 1 个内部函数(network_proxy_state_for_policy);外部调用 3 个(assert!, assert_eq!, network_settings)。

tests::add_denied_domain_removes_matching_allow_entry1000–1015 ↗
async fn add_denied_domain_removes_matching_allow_entry()

作用:验证把域名加入禁止名单时,会移除同名允许项。

数据流:输入是带 allow example.com 的状态 → 调用 add_denied_domain → 检查允许名单为空、禁止名单包含它、访问被 Denied。

调用关系:覆盖 update_domain_list 对反向名单的清理。

调用图:调用 1 个内部函数(network_proxy_state_for_policy);外部调用 3 个(assert!, assert_eq!, network_settings)。

tests::add_denied_domain_forces_block_with_global_wildcard_allowlist1018–1036 ↗
async fn add_denied_domain_forces_block_with_global_wildcard_allowlist()

作用:验证即使允许名单是“全部允许”,单独加入禁止名单仍能挡住目标。

数据流:输入是允许 * 的状态 → 先确认公网 IP 可访问 → 加入 deny 8.8.8.8 → 再确认该 IP 被 Denied。

调用关系:证明 host_blocked 的 deny 优先级高于全局 allow。

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

tests::add_allowed_domain_succeeds_when_managed_baseline_allows_expansion1039–1068 ↗
async fn add_allowed_domain_succeeds_when_managed_baseline_allows_expansion()

作用:验证管理员允许扩展白名单时,用户可以追加域名。

数据流:输入是受管基线包含 managed.example.com 且允许扩展 → 创建状态并 add_allowed_domain → 输出检查白名单包含基线和新增项。

调用关系:覆盖 update_domain_list 与 validate_policy_against_constraints 的配合。

调用图:调用 2 个内部函数(with_reloader, build_config_state);外部调用 6 个(new, assert!, assert_eq!, network_settings, default, vec!)。

tests::add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed1071–1098 ↗
async fn add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed()

作用:验证管理员固定白名单时,用户不能追加域名。

数据流:输入是禁止扩展的受管白名单 → 调用 add_allowed_domain → 得到错误并检查错误说明提到 network.allowed_domains 受限。

调用关系:保证 update_domain_list 不会绕过受管配置。

调用图:调用 2 个内部函数(with_reloader, build_config_state);外部调用 5 个(new, assert!, network_settings, default, vec!)。

tests::add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed1101–1128 ↗
async fn add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed()

作用:验证管理员固定黑名单时,用户不能追加禁止域名。

数据流:输入是禁止扩展的受管禁止名单 → 调用 add_denied_domain → 得到错误并检查错误说明提到 network.denied_domains 受限。

调用关系:覆盖 denylist 的受管约束。

调用图:调用 2 个内部函数(with_reloader, build_config_state);外部调用 5 个(new, assert!, network_settings, default, vec!)。

tests::blocked_snapshot_does_not_consume_entries1131–1167 ↗
async fn blocked_snapshot_does_not_consume_entries()

作用:验证查看拦截记录快照不会把记录清掉。

数据流:输入是空状态 → record_blocked 一条记录 → blocked_snapshot 得到一条 → drain_blocked 仍能取到同一条 → 断言字段一致。

调用关系:区分 blocked_snapshot 和 drain_blocked 的行为。

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

tests::drain_blocked_returns_buffered_window1170–1193 ↗
async fn drain_blocked_returns_buffered_window()

作用:验证拦截记录缓存只保留最近 200 条,并按顺序取出。

数据流:输入是空状态 → 写入超过上限的记录 → drain_blocked → 输出长度为 MAX_BLOCKED_EVENTS,第一条是被保留下来的最旧记录。

调用关系:覆盖 record_blocked 的环形窗口效果和 drain_blocked 的取走逻辑。

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

tests::blocked_request_violation_log_line_serializes_payload1196–1214 ↗
fn blocked_request_violation_log_line_serializes_payload()

作用:验证违规日志能正确序列化成带固定前缀的 JSON 行。

数据流:输入是一条手写 BlockedRequest → 调用 blocked_request_violation_log_line → 和预期字符串精确比较。

调用关系:保证 record_blocked 打出的机器可读日志格式稳定。

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

tests::host_blocked_subdomain_wildcards_exclude_apex1217–1231 ↗
async fn host_blocked_subdomain_wildcards_exclude_apex()

作用:验证 *.openai.com 只匹配子域名,不匹配 openai.com 本身。

数据流:输入是允许 *.openai.com 的状态 → 检查 api.openai.com 放行,openai.com 被 NotAllowed → 测试结束。

调用关系:覆盖 glob 规则对子域名和根域名的区别。

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

tests::host_blocked_global_wildcard_allowlist_allows_public_hosts_except_denylist1234–1258 ↗
async fn host_blocked_global_wildcard_allowlist_allows_public_hosts_except_denylist()

作用:验证全局允许名单会放行公网主机,但仍受禁止名单限制。

数据流:输入是 allow * 且 deny evil.example → 检查普通公网域名放行,evil.example 被 Denied。

调用关系:再次确认 host_blocked 的 deny 优先级。

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

tests::host_blocked_rejects_loopback_when_local_binding_disabled1261–1272 ↗
async fn host_blocked_rejects_loopback_when_local_binding_disabled()

作用:验证默认不允许访问 localhost 和 127.0.0.1。

数据流:输入是只允许 example.com 的状态 → 检查 127.0.0.1 和 localhost → 输出都是 NotAllowedLocal。

调用关系:覆盖 host_blocked 的本地地址保护。

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

tests::host_blocked_allows_loopback_when_explicitly_allowlisted_and_local_binding_disabled1275–1282 ↗
async fn host_blocked_allows_loopback_when_explicitly_allowlisted_and_local_binding_disabled()

作用:验证 localhost 被明确写入允许名单时可以访问。

数据流:输入是 allow localhost 的状态 → 调用 host_blocked localhost → 输出 Allowed。

调用关系:覆盖 is_explicit_local_allowlisted 对本地地址的例外放行。

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

tests::host_blocked_allows_private_ip_literal_when_explicitly_allowlisted1285–1292 ↗
async fn host_blocked_allows_private_ip_literal_when_explicitly_allowlisted()

作用:验证私有 IP 被明确写入允许名单时可以访问。

数据流:输入是 allow 10.0.0.1 的状态 → 检查该 IP → 输出 Allowed。

调用关系:覆盖本地/私网保护中的精确白名单例外。

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

tests::host_blocked_rejects_scoped_ipv6_literal_when_not_allowlisted1295–1305 ↗
async fn host_blocked_rejects_scoped_ipv6_literal_when_not_allowlisted()

作用:验证带网卡作用域的 IPv6 本地地址未明确允许时会被拦。

数据流:输入是只允许 example.com 的状态 → 检查 fe80::1%lo0 → 输出 NotAllowedLocal。

调用关系:覆盖 unscoped_ip_literal 和本地地址判断。

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

tests::host_blocked_allows_scoped_ipv6_literal_when_explicitly_allowlisted1308–1318 ↗
async fn host_blocked_allows_scoped_ipv6_literal_when_explicitly_allowlisted()

作用:验证允许名单写 fe80::1 时,可以匹配 fe80::1%lo0 这种带作用域地址。

数据流:输入是 allow fe80::1 的状态 → 检查 fe80::1%lo0 → 输出 Allowed。

调用关系:覆盖 globset_matches_host_or_unscoped 和 is_explicit_local_allowlisted 的配合。

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

tests::host_blocked_requires_exact_scoped_ipv6_allowlist_match1321–1341 ↗
async fn host_blocked_requires_exact_scoped_ipv6_allowlist_match()

作用:验证在允许本地绑定时,带作用域的 IPv6 允许名单需要精确匹配作用域。

数据流:输入是 allow_local_binding=true 且 allow fe80::1%eth0 → eth0 放行,eth1 不放行 → 输出对应断言。

调用关系:覆盖 host_blocked 对 scoped IPv6 精确匹配的规则。

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

tests::host_blocked_denies_scoped_ipv6_literal_before_local_binding1344–1357 ↗
async fn host_blocked_denies_scoped_ipv6_literal_before_local_binding()

作用:验证禁止名单会在本地绑定判断之前生效,即使地址带作用域或括号。

数据流:输入是 allow *、deny fd00::1、allow_local_binding=true → 测试多个 fd00::1 变体 → 都输出 Denied。

调用关系:证明 host_blocked 的第一步 deny 检查优先于本地网络选项。

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

tests::host_blocked_requires_exact_scoped_ipv6_denylist_match1360–1380 ↗
async fn host_blocked_requires_exact_scoped_ipv6_denylist_match()

作用:验证禁止名单写了带作用域 IPv6 时,只禁止同一作用域。

数据流:输入是 deny fd00::1%eth0 且 allow * → eth0 被 Denied,eth1 放行 → 输出断言。

调用关系:覆盖 scoped IPv6 在 denylist 中的精确匹配行为。

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

tests::host_blocked_rejects_private_ip_literals_when_local_binding_disabled1383–1390 ↗
async fn host_blocked_rejects_private_ip_literals_when_local_binding_disabled()

作用:验证未允许本地绑定时,私有 IP 字面量会被拦。

数据流:输入是只允许 example.com 的状态 → 检查 10.0.0.1 → 输出 NotAllowedLocal。

调用关系:覆盖 host_blocked 对私网 IP 的默认防护。

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

tests::host_blocked_rejects_loopback_when_allowlist_empty1393–1400 ↗
async fn host_blocked_rejects_loopback_when_allowlist_empty()

作用:验证即使没有白名单,loopback 地址也会按本地风险拦截。

数据流:输入是默认网络设置 → 检查 127.0.0.1 → 输出 NotAllowedLocal。

调用关系:保证本地地址保护不依赖白名单是否存在。

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

tests::host_blocked_rejects_allowlisted_hostname_when_dns_lookup_fails1403–1415 ↗
async fn host_blocked_rejects_allowlisted_hostname_when_dns_lookup_fails()

作用:验证白名单域名 DNS 查询失败时会被保守拦截。

数据流:输入是 allow does-not-resolve.invalid → host_blocked 查询时 DNS 失败 → 输出 NotAllowedLocal。

调用关系:覆盖 host_resolves_to_non_public_ip 的失败即拦截策略。

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

tests::host_resolves_to_non_public_ip_blocks_on_dns_lookup_timeout1418–1430 ↗
async fn host_resolves_to_non_public_ip_blocks_on_dns_lookup_timeout()

作用:验证 DNS 查询超时时会按危险处理。

数据流:输入是一个永远不返回的假查询函数和很短超时 → 调用 host_resolves_to_non_public_ip → 输出 true。

调用关系:直接测试 host_resolves_to_non_public_ip 的超时分支。

调用图:调用 1 个内部函数(host_resolves_to_non_public_ip);外部调用 2 个(from_millis, assert!)。

tests::host_resolves_to_non_public_ip_blocks_on_dns_lookup_error1433–1448 ↗
async fn host_resolves_to_non_public_ip_blocks_on_dns_lookup_error()

作用:验证 DNS 查询报错时会按危险处理。

数据流:输入是返回错误的假查询函数 → 调用 host_resolves_to_non_public_ip → 输出 true。

调用关系:直接测试 host_resolves_to_non_public_ip 的错误分支。

调用图:调用 1 个内部函数(host_resolves_to_non_public_ip);外部调用 2 个(from_millis, assert!)。

tests::host_resolves_to_non_public_ip_blocks_private_resolution1451–1461 ↗
async fn host_resolves_to_non_public_ip_blocks_private_resolution()

作用:验证域名解析到 127.0.0.1 时会被判定为非公网。

数据流:输入是假 DNS 返回 127.0.0.1:80 → 调用检查函数 → 输出 true。

调用关系:覆盖 host_resolves_to_non_public_ip 对私有/本地 IP 的识别。

调用图:调用 1 个内部函数(host_resolves_to_non_public_ip);外部调用 2 个(from_millis, assert!)。

tests::host_resolves_to_non_public_ip_allows_public_resolution1464–1474 ↗
async fn host_resolves_to_non_public_ip_allows_public_resolution()

作用:验证域名解析到公网 IP 时不会被本地网络检查拦住。

数据流:输入是假 DNS 返回 8.8.8.8:80 → 调用检查函数 → 输出 false。

调用关系:覆盖 host_resolves_to_non_public_ip 的正常放行分支。

调用图:调用 1 个内部函数(host_resolves_to_non_public_ip);外部调用 2 个(from_millis, assert!)。

tests::validate_policy_against_constraints_disallows_widening_allowed_domains1477–1492 ↗
fn validate_policy_against_constraints_disallows_widening_allowed_domains()

作用:验证受管白名单不允许被偷偷扩大。

数据流:输入是约束只允许 example.com,但配置多了 evil.com → 调用约束校验 → 断言失败。

调用关系:虽然校验函数来自 state 模块,这里作为运行时相关策略测试一起覆盖。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_allows_expanding_allowed_domains_when_enabled1495–1511 ↗
fn validate_policy_against_constraints_allows_expanding_allowed_domains_when_enabled()

作用:验证管理员明确允许扩展白名单时,新增域名可通过校验。

数据流:输入是基线 example.com 且 allowlist_expansion_enabled=true → 配置加入 api.openai.com → 校验成功。

调用关系:支持 update_domain_list 中用户扩展白名单的合法场景。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_disallows_widening_mode1514–1529 ↗
fn validate_policy_against_constraints_disallows_widening_mode()

作用:验证受管网络模式不能被改得更宽松。

数据流:输入是约束 Limited、配置 Full → 调用校验 → 断言失败。

调用关系:保护 NetworkProxyState::set_network_mode 不绕过管理员限制。

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

tests::validate_policy_against_constraints_allows_narrowing_wildcard_allowlist1532–1547 ↗
fn validate_policy_against_constraints_allows_narrowing_wildcard_allowlist()

作用:验证把通配白名单收窄到具体域名是允许的。

数据流:输入是约束 *.example.com,配置只允许 api.example.com → 校验成功。

调用关系:说明受管约束允许更严格的用户配置。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_rejects_widening_wildcard_allowlist1550–1565 ↗
fn validate_policy_against_constraints_rejects_widening_wildcard_allowlist()

作用:验证把 *.example.com 扩成 **.example.com 这种更宽规则会被拒绝。

数据流:输入是较窄约束和较宽配置 → 校验 → 输出错误。

调用关系:防止用户用更强通配符扩大访问范围。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_rejects_global_wildcard_in_managed_allowlist1568–1583 ↗
fn validate_policy_against_constraints_rejects_global_wildcard_in_managed_allowlist()

作用:验证受管白名单里出现全局通配符时,不会被当成可随便放行的基线。

数据流:输入是 managed allowed_domains 为 *,配置是具体域名 → 校验失败。

调用关系:覆盖约束系统对危险全局通配的保守处理。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_rejects_bracketed_global_wildcard_in_managed_allowlist1586–1602 ↗
fn validate_policy_against_constraints_rejects_bracketed_global_wildcard_in_managed_allowlist()

作用:验证 [*] 这种写法也不会绕过全局通配限制。

数据流:输入是 managed allowed_domains 为 [*] → 校验配置 → 输出失败。

调用关系:防止特殊 glob 写法逃过约束。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_rejects_double_wildcard_bracketed_global_wildcard_in_managed_allowlist1605–1621 ↗
fn validate_policy_against_constraints_rejects_double_wildcard_bracketed_global_wildcard_in_managed_allowlist()

作用:验证 **.[*] 这种更隐蔽的全局通配也会被拒绝。

数据流:输入是 managed allowed_domains 为 **.[*] → 校验 → 输出失败。

调用关系:补齐危险通配写法的测试。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_requires_managed_denied_domains_entries1624–1638 ↗
fn validate_policy_against_constraints_requires_managed_denied_domains_entries()

作用:验证受管禁止名单要求的条目不能被用户删掉。

数据流:输入是约束要求 deny evil.com,但配置没有它 → 校验失败。

调用关系:保证管理员封禁项始终存在。

调用图:调用 1 个内部函数(default);外部调用 3 个(assert!, default, vec!)。

tests::validate_policy_against_constraints_disallows_expanding_denied_domains_when_fixed1641–1657 ↗
fn validate_policy_against_constraints_disallows_expanding_denied_domains_when_fixed()

作用:验证固定禁止名单时,用户不能自行增加更多禁止项。

数据流:输入是 denylist_expansion_enabled=false 且配置多了 more-evil.com → 校验失败。

调用关系:对应 add_denied_domain 的固定基线拒绝场景。

调用图:外部调用 4 个(assert!, network_settings, default, vec!)。

tests::validate_policy_against_constraints_disallows_enabling_when_managed_disabled1660–1674 ↗
fn validate_policy_against_constraints_disallows_enabling_when_managed_disabled()

作用:验证管理员禁用网络代理时,用户不能自行启用。

数据流:输入是约束 enabled=false,但配置 enabled=true → 校验失败。

调用关系:保护全局开关的受管约束。

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

tests::validate_policy_against_constraints_disallows_allow_local_binding_when_managed_disabled1677–1692 ↗
fn validate_policy_against_constraints_disallows_allow_local_binding_when_managed_disabled()

作用:验证管理员禁止本地绑定时,用户不能打开它。

数据流:输入是 allow_local_binding 约束为 false,但配置为 true → 校验失败。

调用关系:保护本机/内网访问这一敏感开关。

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

tests::validate_policy_against_constraints_disallows_allow_all_unix_sockets_without_managed_opt_in1695–1711 ↗
fn validate_policy_against_constraints_disallows_allow_all_unix_sockets_without_managed_opt_in()

作用:验证没有管理员明确同意时,不能打开允许全部 Unix socket 的危险开关。

数据流:输入是约束 dangerously_allow_all_unix_sockets=false,配置为 true → 校验失败。

调用关系:保护 Unix socket 权限不要被用户放宽。

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

tests::validate_policy_against_constraints_disallows_allow_all_unix_sockets_when_allowlist_is_managed1714–1730 ↗
fn validate_policy_against_constraints_disallows_allow_all_unix_sockets_when_allowlist_is_managed()

作用:验证管理员管理了 Unix socket 允许列表时,用户不能用“全允许”绕过列表。

数据流:输入是受管 allow_unix_sockets 包含一个路径,配置打开全允许 → 校验失败。

调用关系:保证 is_unix_socket_allowed 背后的配置不会被绕过。

调用图:调用 1 个内部函数(default);外部调用 3 个(assert!, default, vec!)。

tests::validate_policy_against_constraints_allows_allow_all_unix_sockets_with_managed_opt_in1733–1748 ↗
fn validate_policy_against_constraints_allows_allow_all_unix_sockets_with_managed_opt_in()

作用:验证管理员明确允许时,可以打开 Unix socket 全允许。

数据流:输入是约束 dangerously_allow_all_unix_sockets=true,配置也为 true → 校验成功。

调用关系:覆盖危险开关的合法开启场景。

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

tests::validate_policy_against_constraints_allows_allow_all_unix_sockets_when_unmanaged1751–1763 ↗
fn validate_policy_against_constraints_allows_allow_all_unix_sockets_when_unmanaged()

作用:验证没有受管约束时,本地配置可以打开 Unix socket 全允许。

数据流:输入是默认无约束,配置开启全允许 → 校验成功。

调用关系:说明限制只来自受管配置,不是校验器绝对禁止。

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

tests::compile_globset_is_case_insensitive1766–1771 ↗
fn compile_globset_is_case_insensitive()

作用:验证域名匹配不区分大小写。

数据流:输入是模式 ExAmPle.CoM → 编译 deny glob → 检查 example.com 和 EXAMPLE.COM 都命中。

调用关系:保证 allowlist/denylist 对域名大小写不敏感。

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

tests::compile_globset_excludes_apex_for_subdomain_patterns1774–1780 ↗
fn compile_globset_excludes_apex_for_subdomain_patterns()

作用:验证 *.openai.com 只匹配子域,不匹配根域。

数据流:输入是模式 *.openai.com → 编译 → 检查 api.openai.com 命中,openai.com 和 evilopenai.com 不命中。

调用关系:支持 host_blocked_subdomain_wildcards_exclude_apex 的底层匹配行为。

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

tests::compile_globset_includes_apex_for_double_wildcard_patterns1783–1789 ↗
fn compile_globset_includes_apex_for_double_wildcard_patterns()

作用:验证 **.openai.com 同时匹配根域和子域。

数据流:输入是模式 **.openai.com → 编译 → 检查 openai.com 和 api.openai.com 命中,evilopenai.com 不命中。

调用关系:说明单星和双星通配规则的差别。

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

tests::compile_globset_rejects_global_wildcard1792–1795 ↗
fn compile_globset_rejects_global_wildcard()

作用:验证禁止名单不能使用全局通配符 *。

数据流:输入是 deny 模式 * → 编译 → 断言报错。

调用关系:避免 denylist 写成全禁这种不被支持或太危险的规则。

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

tests::compile_globset_allows_global_wildcard_when_enabled1798–1804 ↗
fn compile_globset_allows_global_wildcard_when_enabled()

作用:验证允许名单可以在允许的情况下使用全局通配符。

数据流:输入是 allow 模式 * → 编译 → 检查多个主机都命中。

调用关系:支持“允许所有公网主机,再用 denylist 排除”的策略。

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

tests::compile_globset_rejects_bracketed_global_wildcard1807–1810 ↗
fn compile_globset_rejects_bracketed_global_wildcard()

作用:验证 [*] 这种伪装成字符集的全局通配会被禁止名单拒绝。

数据流:输入是 deny 模式 [*] → 编译 → 断言失败。

调用关系:防止全局通配用另一种写法绕过规则。

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

tests::compile_globset_rejects_double_wildcard_bracketed_global_wildcard1813–1816 ↗
fn compile_globset_rejects_double_wildcard_bracketed_global_wildcard()

作用:验证 **.[*] 这种全局通配变体会被拒绝。

数据流:输入是 deny 模式 **.[*] → 编译 → 断言失败。

调用关系:补充 glob 安全边界测试。

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

tests::compile_globset_dedupes_patterns_without_changing_behavior1819–1825 ↗
fn compile_globset_dedupes_patterns_without_changing_behavior()

作用:验证重复规则不会改变匹配结果。

数据流:输入是两个相同 example.com 模式 → 编译 → 检查 example.com 命中,not-example.com 不命中。

调用关系:保证规则去重或重复输入不会影响代理判断。

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

tests::compile_globset_rejects_invalid_patterns1828–1831 ↗
fn compile_globset_rejects_invalid_patterns()

作用:验证非法 glob 模式会被拒绝。

数据流:输入是无效模式 [ → 编译 → 断言失败。

调用关系:保证坏配置不会进入运行时状态。

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

tests::build_config_state_allows_global_wildcard_allowed_domains1834–1844 ↗
fn build_config_state_allows_global_wildcard_allowed_domains()

作用:验证构建配置状态时允许白名单使用 *。

数据流:输入是 allowed_domains 为 * 的配置 → 调用 build_config_state → 断言成功。

调用关系:覆盖运行时状态构建对 allowlist 全局通配的支持。

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

tests::build_config_state_allows_bracketed_global_wildcard_allowed_domains1847–1857 ↗
fn build_config_state_allows_bracketed_global_wildcard_allowed_domains()

作用:验证白名单里 [*] 这种写法在构建状态时也可被接受。

数据流:输入是 allowed_domains 为 [*] → 构建配置状态 → 断言成功。

调用关系:说明 allowlist 比 denylist 对通配更宽松。

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

tests::build_config_state_rejects_global_wildcard_denied_domains1860–1870 ↗
fn build_config_state_rejects_global_wildcard_denied_domains()

作用:验证构建状态时禁止名单不能是 *。

数据流:输入是 denied_domains 为 * 的配置 → build_config_state → 断言失败。

调用关系:防止危险 denylist 规则进入 NetworkProxyState。

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

tests::build_config_state_rejects_bracketed_global_wildcard_denied_domains1873–1883 ↗
fn build_config_state_rejects_bracketed_global_wildcard_denied_domains()

作用:验证构建状态时禁止名单不能用 [*] 绕过全局通配限制。

数据流:输入是 denied_domains 为 [*] → build_config_state → 断言失败。

调用关系:补充 denylist glob 安全测试。

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

tests::unix_socket_allowlist_is_respected_on_macos1887–1902 ↗
async fn unix_socket_allowlist_is_respected_on_macos()

作用:在 macOS 上验证 Unix socket 允许列表确实生效。

数据流:输入是允许 /tmp/example.sock 的状态 → 检查该路径允许,另一路径不允许 → 输出断言。

调用关系:覆盖 is_unix_socket_allowed 在支持平台上的主要行为。

调用图:调用 1 个内部函数(network_proxy_state_for_policy);外部调用 3 个(assert!, network_settings_with_unix_sockets, from_ref)。

tests::unix_socket_allow_all_flag_bypasses_allowlist1935–1944 ↗
async fn unix_socket_allow_all_flag_bypasses_allowlist()

作用:在 macOS 上验证“允许所有 Unix socket”开关会跳过具体允许列表,但仍要求绝对路径。

数据流:输入是 dangerously_allow_all_unix_sockets=true → 检查 /tmp/any.sock 允许,relative.sock 不允许。

调用关系:覆盖 is_unix_socket_allowed 的危险开关和绝对路径要求。

调用图:调用 1 个内部函数(network_proxy_state_for_policy);外部调用 2 个(assert!, network_settings)。

tests::unix_socket_allowlist_is_rejected_on_non_macos1948–1961 ↗
async fn unix_socket_allowlist_is_rejected_on_non_macos()

作用:在非 macOS 上验证 Unix socket allowlist 功能整体不启用。

数据流:输入是配置了 socket allowlist 且打开全允许的状态 → 调用 is_unix_socket_allowed → 输出 false。

调用关系:覆盖 unix_socket_permissions_supported 为 false 时的行为。

调用图:调用 1 个内部函数(network_proxy_state_for_policy);外部调用 3 个(assert!, network_settings_with_unix_sockets, from_ref)。

沙盒文件系统保护

这些文件通过应用额外读取授权、保留拒绝读取状态并收紧工作区 ACL,管理 Windows 沙盒文件系统访问。

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

Windows 沙箱可以理解成一个“隔离房间”:程序在里面运行,不能随便碰外面的文件。这个文件做的事很具体:当系统想额外允许沙箱读取某个目录时,它先检查这个目录是不是靠谱——路径必须是绝对路径,目录必须真实存在,而且必须真的是文件夹。这样可以避免把一个写错的、相对的、或者根本不存在的位置塞进沙箱规则里。检查通过后,它会把路径转换成系统认可的标准形式,也就是规范化路径,避免同一个目录因为写法不同被当成不同地方。最后,它把这个目录交给 Windows 沙箱的刷新设置函数,让新的“可读目录”规则生效。这个文件本身不直接改权限细节,而是像门卫一样先验票,再把合格名单交给真正更新沙箱配置的地方。

函数细节1
grant_read_root_non_elevated9–37 ↗
fn grant_read_root_non_elevated(
    permission_profile: &PermissionProfile,
    workspace_roots: &[AbsolutePathBuf],
    command_cwd: &Path,
    env_map: &HashMap<String, String>,
    codex_home: &Pa

作用:这个函数把一个指定目录加入 Windows 沙箱的“允许读取”名单里。它会先严格检查目录是否合法,避免把危险或无效的路径交给沙箱配置。

数据流:进去的是当前权限配置、工作区根目录列表、命令运行目录、环境变量、Codex 的家目录,以及想开放读取的目录路径。它先确认这个路径是绝对路径、存在、并且是目录;不满足就直接返回错误。然后它把路径规范化成系统标准写法,再调用沙箱设置刷新函数,把这个目录作为额外可读根目录加入进去。出来的是规范化后的目录路径;同时,Windows 沙箱的读取规则也被刷新过。

调用关系:它处在“申请额外读取权限”的流程中,像一个审核员。它自己负责做基础安全检查和路径整理;真正刷新沙箱设置的活交给 run_setup_refresh_with_extra_read_roots。检查路径时会用到系统路径方法,比如判断是否绝对、是否存在、是否是目录;遇到不合格情况会用错误返回中断流程。

调用图:调用 1 个内部函数(run_setup_refresh_with_extra_read_roots);外部调用 6 个(exists, is_absolute, is_dir, bail!, canonicalize, vec!)。

windows-sandbox-rs/src/deny_read_state.rs源码 ↗
domain_logicsandbox ACL setup / cross-run state sync

这个文件解决的是一个很现实的问题:有些沙箱进程退出后,它启动的子进程可能还活着,所以不能在命令结束时马上把 Windows 的访问控制规则删掉。这里的“ACL”就是访问控制列表,像门口的名单;“deny-read ACE”就是名单里一条“这个人不许读这个地方”的规则。文件会把每个沙箱身份,也就是 SID(一串代表 Windows 用户或主体身份的编号)对应加过哪些禁止读取路径,保存到一个 JSON 文件里。下次运行时,它先读出旧记录,再把这次真正想要的禁止读取规则加上去,最后才删除那些已经不需要的旧规则。这个顺序很重要:像换门禁规则时,先贴上新禁入名单,再撕掉旧名单,避免中间有人钻空子。它还会把最终状态重新写回磁盘,让下一次运行继续知道自己以前做过什么。

函数细节3
sync_persistent_deny_read_acls32–68 ↗
fn sync_persistent_deny_read_acls(
    codex_home: &Path,
    principal_sid: &str,
    desired_paths: &[PathBuf],
    psid: *mut c_void,
) -> Result<Vec<PathBuf>>

作用:这个函数把“这次应该禁止读取的路径”和“以前已经禁止读取的路径”对齐。它会先加上新规则,再撤掉不该再存在的旧规则,最后把结果保存下来。

数据流:进去的是 Codex 的主目录、当前沙箱身份的 SID 字符串、希望禁止读取的路径列表,以及一个 Windows 用的 SID 指针。它先通过 sandbox_dir 找到状态文件,调用 load_state 读出旧 JSON 记录;再调用 apply_deny_read_acls 给目标路径真正加上禁止读取规则;然后用 lexical_path_key 把路径变成可比较的标准键,找出旧记录里现在不需要的路径,并对这些路径调用 revoke_ace 撤销旧规则。最后它更新内存里的状态:如果这次没有成功应用任何路径,就删掉这个 SID 的记录;否则写入新路径列表,再用 store_state 保存到磁盘。出来的是实际成功应用了禁止读取规则的路径列表,同时磁盘状态文件和 Windows ACL 都被同步过。

调用关系:它是这个文件的主流程,由 apply_legacy_session_acl_rules 在应用旧式会话 ACL 规则时调用。它自己不直接解析 JSON 细节,也不直接实现加规则的底层动作,而是把读状态交给 load_state,把写状态交给 store_state,把真正加禁止读取规则交给 apply_deny_read_acls,把撤销旧规则交给 revoke_ace。这样它像一个调度员,负责按正确顺序把各步串起来。

调用图:调用 6 个内部函数(revoke_ace, apply_deny_read_acls, lexical_path_key, load_state, store_state, sandbox_dir);被 1 处调用(apply_legacy_session_acl_rules)。

load_state70–81 ↗
fn load_state(path: &Path) -> Result<PersistentDenyReadAclState>

作用:这个函数负责从磁盘读取以前保存的禁止读取规则状态。没有状态文件时,它不会报错,而是当作第一次运行,返回一个空状态。

数据流:进去的是状态文件路径。它尝试用文件读取函数 read 拿到原始字节;如果读到了,就用 JSON 解析函数 from_slice 把字节变成 PersistentDenyReadAclState,也就是“每个 SID 对应哪些路径”的表。如果文件不存在,它返回默认的空表;如果是别的读取错误,或者 JSON 内容坏了,它会带上更清楚的错误说明返回。出来的是可供同步流程使用的状态对象。

调用关系:它只被 sync_persistent_deny_read_acls 调用,位置在同步流程的一开始。因为同步新旧 ACL 之前必须先知道过去加过什么,所以它相当于先翻旧账本;如果账本不存在,就给一本空账本。

调用图:被 1 处调用(sync_persistent_deny_read_acls);外部调用 3 个(from_slice, read, default)。

store_state83–87 ↗
fn store_state(path: &Path, state: &PersistentDenyReadAclState) -> Result<()>

作用:这个函数负责把最新的禁止读取规则状态写回磁盘。它让下一次沙箱运行时还能知道这一次留下了哪些 ACL 规则。

数据流:进去的是状态文件路径和内存里的状态表。它先用 JSON 序列化函数 to_vec_pretty 把状态变成格式清楚、容易查看的 JSON 字节;然后用 write 写入指定文件。成功时没有额外结果,只表示写好了;如果转换 JSON 或写文件失败,它会带上说明返回错误。

调用关系:它只被 sync_persistent_deny_read_acls 调用,位置在同步流程的最后。前面新规则已应用、旧规则已撤销之后,它负责把最终账本落到磁盘,供下一次 load_state 再读回来。

调用图:被 1 处调用(sync_persistent_deny_read_acls);外部调用 2 个(to_vec_pretty, write)。

windows-sandbox-rs/src/workspace_acl.rs源码 ↗
domain_logicsession setup / ACL application

这个文件解决的是一个很实际的安全问题:在 Windows 沙箱里运行外部命令时,命令可能会看到工作区里的隐藏目录,比如 .codex.agents。这些目录通常放着工具自己的状态或代理信息,不希望被随便写坏。这里的做法像是在文件夹门口贴一张“禁止写入”的保安条:如果目录存在,就调用底层的 ACL(访问控制列表,也就是 Windows 用来规定谁能读写文件的权限表)函数,给某个用户身份加一条“拒绝写入”的规则。文件也提供一个小判断:把根目录路径规范化后,看看命令的当前目录是不是工作区根。需要注意的是,几个保护函数是 unsafe(不安全调用,意思是调用者必须保证传进来的系统指针真的有效),因为它们直接接触 Windows 的 SID(安全标识符,用来代表某个用户或账户)。

函数细节4
is_command_cwd_root7–9 ↗
fn is_command_cwd_root(root: &Path, canonical_command_cwd: &Path) -> bool

作用:这个函数判断命令运行时所在的目录,是不是工作区的根目录。上层会用这个结果来决定要不要按旧规则给工作区套安全限制。

数据流:输入是工作区根目录 root,以及已经规范化过的命令当前目录 canonical_command_cwd。它先把 root 也变成规范路径,也就是去掉路径写法上的差异,再和命令当前目录比较;最后输出 truefalse,不改动任何文件。

调用关系:它被 apply_legacy_session_acl_rules 调用,通常发生在准备沙箱权限规则时。它自己只把路径交给 canonicalize_path 做标准化,然后把判断结果还给上层。

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

protect_workspace_codex_dir13–15 ↗
fn protect_workspace_codex_dir(cwd: &Path, psid: *mut c_void) -> Result<bool>

作用:这个函数尝试保护当前工作区里的 .codex 目录,防止指定的 Windows 用户身份往里面写东西。有人会在套用沙箱权限时用它,避免工具自己的内部目录被命令改坏。

数据流:输入是工作区目录 cwd 和一个 SID 指针 psid,这个 SID 代表要被限制写入的用户或账户。它不自己做具体权限修改,而是把目录名 .codex 交给通用的 protect_workspace_subdir;输出是一个结果:成功时告诉调用者是否真的添加了保护,失败时返回错误。

调用关系:它被 apply_legacy_session_acl_rules 调用,是旧版会话权限规则的一部分。它把实际检查目录、添加 ACL 的工作交给 protect_workspace_subdir

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

protect_workspace_agents_dir19–21 ↗
fn protect_workspace_agents_dir(cwd: &Path, psid: *mut c_void) -> Result<bool>

作用:这个函数尝试保护当前工作区里的 .agents 目录,让指定的 Windows 用户身份不能写入。它的作用是把代理相关的内部文件和普通命令隔开。

数据流:输入是工作区目录 cwd 和一个 SID 指针 psid。它把子目录名 .agents 传给 protect_workspace_subdir,由后者判断目录是否存在并尝试加拒绝写入规则;最后返回是否加了保护,或者返回遇到的错误。

调用关系:它同样由 apply_legacy_session_acl_rules 在设置旧版沙箱权限时调用。它只是一个面向 .agents 的包装函数,真正的权限操作交给 protect_workspace_subdir

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

protect_workspace_subdir23–30 ↗
fn protect_workspace_subdir(cwd: &Path, psid: *mut c_void, subdir: &str) -> Result<bool>

作用:这是实际干活的通用函数:给工作区下某个指定子目录加“拒绝写入”的 Windows 权限规则。它让 .codex.agents 这类目录可以共用同一套保护逻辑。

数据流:输入是工作区目录 cwd、代表受限用户的 SID 指针 psid、以及子目录名 subdir。它先把工作区路径和子目录名拼起来,得到完整路径;如果这个路径确实是一个目录,就调用 add_deny_write_ace 添加拒绝写入规则;如果目录不存在,就什么也不改并返回 false

调用关系:它被 protect_workspace_codex_dirprotect_workspace_agents_dir 调用,是这两个公开保护函数背后的共用零件。它会使用路径拼接能力生成目标目录路径,并把真正修改 Windows ACL 的动作交给 add_deny_write_ace

调用图:调用 1 个内部函数(add_deny_write_ace);被 2 处调用(protect_workspace_agents_dir, protect_workspace_codex_dir);外部调用 1 个(join)。

WFP 网络阻止设置

这些文件安装并安全封装持久化的 Windows Filtering Platform 保护,用于执行受批准控制的网络限制。

windows-sandbox-rs/src/wfp.rs源码 ↗
domain_logicsetup / elevated install

这个文件像是在 Windows 防火墙里给 Codex 沙箱单独开了一个“规则文件夹”,然后把一批固定的拦截规则放进去。它先打开 WFP 引擎,也就是 Windows 管网络过滤规则的后台服务;再确保 Codex 自己的 provider(规则归属身份)和 sublayer(规则分组层)存在;接着把规则限制到指定账户,只拦这个用户的网络行为;最后逐条删除旧规则并重新添加新规则。这样做的好处是安装可以重复执行,不会因为规则已存在而乱套。文件里还特别用 transaction(事务,一次性提交的一组改动)保护安装过程:成功就提交,失败就撤销,避免只装了一半规则。

函数细节21
install_wfp_filters_for_account79–95 ↗
fn install_wfp_filters_for_account(account: &str) -> Result<usize>

作用:给指定 Windows 账户安装 Codex 沙箱需要的 WFP 网络拦截规则。外部 setup 程序通常会调用它,失败时可以把错误当成非致命问题处理。

数据流:输入是账户名字符串。它先打开 WFP 引擎,开启一次事务,确认 Codex 的 provider 和 sublayer 已存在;再把账户名变成 WFP 能识别的用户匹配条件;然后遍历内置规则列表,先删掉同 key 的旧规则,再添加新规则。输出是成功安装的规则数量;同时 Windows 系统里的持久防火墙规则会被改动。

调用关系:这是本文件的总入口。它把活儿分给 Engine::open、UserMatchCondition::for_account、ensure_provider、ensure_sublayer、delete_filter_if_present 和 add_filter;这些小零件分别负责打开系统服务、识别用户、准备规则空间、清理旧规则和写入新规则。

调用图:调用 6 个内部函数(open, for_account, add_filter, delete_filter_if_present, ensure_provider, ensure_sublayer)。

Engine::open103–124 ↗
fn open() -> Result<Self>

作用:打开 Windows 的 WFP 引擎,拿到一个后续操作都要用的系统句柄。可以把它理解成先拿到“防火墙后台办公室的门卡”。

数据流:它读取固定的会话名称,把名称转换成 Windows 需要的宽字符格式,组装一个 WFP 会话结构,然后调用 Windows API 打开引擎。成功后输出一个 Engine 对象,里面保存系统句柄;失败就返回带说明的错误。

调用关系:install_wfp_filters_for_account 一开始就会调用它。它内部把 Windows API 的返回码交给 ensure_success 检查,确保后面的 provider、sublayer 和 filter 操作都建立在有效连接上。

调用图:调用 1 个内部函数(ensure_success);被 1 处调用(install_wfp_filters_for_account);外部调用 7 个(default, new, to_wide, zeroed, null, null_mut, FwpmEngineOpen0)。

Engine::begin_transaction126–133 ↗
fn begin_transaction(&self) -> Result<Transaction<'_>>

作用:在 WFP 引擎里开启一次事务。事务就是“先暂存一批改动,最后一起确认”,避免规则只装了一半。

数据流:输入是已经打开的 Engine。它调用 Windows 的事务开始接口;如果成功,就返回一个 Transaction 对象,并标记为尚未提交;如果失败,就返回错误。

调用关系:安装规则时需要用它把多个 WFP 改动包起来。它调用 ensure_success 检查系统返回码,后续由 Transaction::commit 决定提交,或者由 Transaction::drop 自动撤销。

调用图:调用 1 个内部函数(ensure_success);外部调用 1 个(FwpmTransactionBegin0)。

Engine::drop137–141 ↗
fn drop(&mut self)

作用:在 Engine 不再使用时自动关闭 WFP 引擎句柄。这样可以防止系统资源一直被占着。

数据流:输入是即将销毁的 Engine,里面有一个打开的系统句柄。它调用 Windows API 关闭这个句柄;没有普通返回值,只是释放资源。

调用关系:这是 Rust 的 Drop 自动清理机制触发的函数。调用者不需要手动关门,Engine 生命周期结束时它会把 WFP 引擎连接收尾。

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

Transaction::commit151–156 ↗
fn commit(&mut self) -> Result<()>

作用:提交已经开启的 WFP 事务,让前面准备的一批规则改动真正生效。

数据流:输入是一个 Transaction。它调用 Windows 的提交接口;成功后把 committed 标记改成 true,表示不要再自动撤销;失败则返回错误。

调用关系:安装流程在所有 provider、sublayer 和 filter 都处理完后会用它收尾。它调用 ensure_success 检查提交是否成功,并和 Transaction::drop 配合,确保失败时不会留下半成品。

调用图:调用 1 个内部函数(ensure_success);外部调用 1 个(FwpmTransactionCommit0)。

Transaction::drop160–166 ↗
fn drop(&mut self)

作用:如果事务还没提交就结束了,它会自动撤销事务。它是安装失败时的安全网。

数据流:输入是即将销毁的 Transaction。它检查 committed 标记;如果还没提交,就调用 Windows 的事务撤销接口;如果已经提交,就什么也不做。

调用关系:这是 Rust 自动清理时触发的函数。它和 Transaction::commit 是一对:commit 成功表示正式落地,drop 则负责在异常路径上帮忙回滚。

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

UserMatchCondition::for_account176–213 ↗
fn for_account(account: &str) -> Result<Self>

作用:把一个 Windows 账户名转换成 WFP 可以用来匹配“这个用户”的条件数据。没有它,规则可能会影响所有人,而不是只限制沙箱账户。

数据流:输入是账户名。它先把账户名转成 Windows 宽字符,再构造一条访问匹配描述,接着让 Windows 生成安全描述符(security descriptor,可理解为描述某个用户权限身份的一块系统数据)。输出是 UserMatchCondition,里面保存这块描述符和给 WFP 使用的字节块;失败则返回错误。

调用关系:install_wfp_filters_for_account 会先调用它得到用户条件,然后把这个条件交给 add_filter;add_filter 再通过 build_conditions 把它嵌进每条网络规则里。

调用图:调用 1 个内部函数(ensure_success);被 1 处调用(install_wfp_filters_for_account);外部调用 7 个(new, to_wide, zeroed, null, null_mut, BuildExplicitAccessWithNameW, BuildSecurityDescriptorW)。

UserMatchCondition::drop217–223 ↗
fn drop(&mut self)

作用:释放 UserMatchCondition 里由 Windows 分配的安全描述符内存。它防止安装过程反复运行时泄漏内存。

数据流:输入是即将销毁的 UserMatchCondition。它检查 security_descriptor 指针是不是空;如果不是,就调用 LocalFree 归还给 Windows;没有输出。

调用关系:这是 UserMatchCondition 的自动收尾动作。UserMatchCondition::for_account 分配了系统内存,这里负责按 Windows 要求释放。

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

ensure_provider227–243 ↗
fn ensure_provider(engine: HANDLE) -> Result<()>

作用:确保 WFP 里存在 Codex 专用的 provider。provider 可以理解为“这些规则属于 Codex”的身份标签。

数据流:输入是 WFP 引擎句柄。它用固定名称、描述和固定 GUID(全局唯一编号)组装 provider,然后调用 Windows API 添加它。如果已经存在,也算成功;其他错误会返回。

调用关系:install_wfp_filters_for_account 在添加具体规则前会调用它。它使用 empty_blob 填空数据,并用 ensure_success_or 允许“已经存在”这种正常情况。

调用图:调用 2 个内部函数(empty_blob, ensure_success_or);被 1 处调用(install_wfp_filters_for_account);外部调用 4 个(new, to_wide, null_mut, FwpmProviderAdd0)。

ensure_sublayer246–264 ↗
fn ensure_sublayer(engine: HANDLE) -> Result<()>

作用:确保 WFP 里存在 Codex 专用的 sublayer,也就是规则分组层。这样 Codex 的过滤规则不会散落在系统规则堆里。

数据流:输入是 WFP 引擎句柄。它用固定名称、描述、provider key 和 sublayer key 组装 sublayer,然后调用 Windows API 添加。已经存在时不报错;真正失败时返回错误。

调用关系:install_wfp_filters_for_account 会在安装规则前调用它。它依赖 ensure_provider 所代表的 Codex provider,并用 ensure_success_or 处理“重复创建也没关系”的情况。

调用图:调用 2 个内部函数(empty_blob, ensure_success_or);被 1 处调用(install_wfp_filters_for_account);外部调用 4 个(new, to_wide, null_mut, FwpmSubLayerAdd0)。

add_filter267–305 ↗
fn add_filter(
    engine: HANDLE,
    spec: &FilterSpec,
    user_condition: &UserMatchCondition,
) -> Result<()>

作用:把一条内置规则真正写进 WFP,并把动作设为阻止网络流量。它是“把规则贴到防火墙上”的那一步。

数据流:输入是 WFP 引擎句柄、一条 FilterSpec 规则说明、以及用户匹配条件。它把规则名和描述转成 Windows 格式,用 build_conditions 生成具体匹配条件,填好 provider、sublayer、网络层、动作等字段,然后调用 Windows API 添加规则。成功后系统里多出一条持久拦截规则;失败则返回错误。

调用关系:install_wfp_filters_for_account 对每条内置规则都会调用它。在调用它之前通常会先用 delete_filter_if_present 清掉旧版本;它内部还会调用 empty_blob、empty_value 和 zero_guid 准备 WFP 要求的空字段。

调用图:调用 5 个内部函数(build_conditions, empty_blob, empty_value, ensure_success, zero_guid);被 1 处调用(install_wfp_filters_for_account);外部调用 5 个(new, to_wide, format!, null_mut, FwpmFilterAdd0)。

build_conditions308–343 ↗
fn build_conditions(
    specs: &[ConditionSpec],
    user_condition: &UserMatchCondition,
) -> Vec<FWPM_FILTER_CONDITION0>

作用:把项目里简写的规则条件转换成 WFP 需要的详细条件结构。它像翻译员,把“用户、协议、远端端口”翻译成 Windows 能执行的格式。

数据流:输入是一组 ConditionSpec 和用户匹配条件。它逐个查看条件类型:用户条件变成 ALE_USER_ID 匹配,协议变成 IP_PROTOCOL 匹配,远端端口变成 IP_REMOTE_PORT 匹配。输出是一组 FWPM_FILTER_CONDITION0,供 WFP 添加规则时使用。

调用关系:add_filter 在创建每条过滤规则时调用它。它不直接接触 Windows API,只负责把内部规则说明加工成 add_filter 能塞进 WFP 结构体的数据。

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

delete_filter_if_present346–353 ↗
fn delete_filter_if_present(engine: HANDLE, key: &GUID) -> Result<()>

作用:在重新添加规则前,先按固定 key 删除旧规则。这样重复安装时不会因为同一条规则已经存在而失败。

数据流:输入是 WFP 引擎句柄和规则 GUID。它调用 Windows API 删除对应规则;如果规则不存在,也当作成功;如果是其他错误,就返回错误。

调用关系:install_wfp_filters_for_account 在每次 add_filter 之前调用它。它用 ensure_success_or 明确允许“没找到规则”这种情况,因为第一次安装时本来就没有旧规则。

调用图:调用 1 个内部函数(ensure_success_or);被 1 处调用(install_wfp_filters_for_account);外部调用 1 个(FwpmFilterDeleteByKey0)。

ensure_success355–357 ↗
fn ensure_success(result: u32, operation: &str) -> Result<()>

作用:检查 Windows API 返回码是不是成功。它是最常用的错误检查小工具。

数据流:输入是一个返回码和操作名称。它把这些交给 ensure_success_or,并且不额外允许任何错误码。输出是成功的空结果,或者带操作名和错误码的错误。

调用关系:Engine::open、Engine::begin_transaction、Transaction::commit、UserMatchCondition::for_account 和 add_filter 都会用它。它把不同 Windows API 的成败判断统一成 Rust 的 Result。

调用图:调用 1 个内部函数(ensure_success_or);被 5 处调用(begin_transaction, open, commit, for_account, add_filter)。

ensure_success_or359–368 ↗
fn ensure_success_or(result: u32, operation: &str, allowed: &[u32]) -> Result<()>

作用:检查 Windows API 返回码,但允许某些指定错误码也算正常。比如“已经存在”和“没找到”在安装流程里有时不是问题。

数据流:输入是返回码、操作名称、以及允许忽略的错误码列表。返回码为 0 或在允许列表里时输出成功;否则生成一条包含格式化错误码的错误信息。

调用关系:ensure_provider、ensure_sublayer 和 delete_filter_if_present 会直接用它,因为它们都需要容忍某些可预期情况;ensure_success 也通过它实现最严格的检查。

调用图:被 4 处调用(delete_filter_if_present, ensure_provider, ensure_sublayer, ensure_success);外部调用 1 个(anyhow!)。

format_error_code370–372 ↗
fn format_error_code(result: u32) -> String

作用:把 Windows 返回的数字错误码变成更容易搜索和辨认的十六进制字符串。

数据流:输入是一个 u32 数字错误码。它把它格式化成类似 0x80070005 的字符串。输出是这段字符串,不改动任何外部状态。

调用关系:它服务于错误报告路径,通常由 ensure_success_or 在构造错误信息时使用。这样日志里看到的错误码更接近 Windows 文档和排障资料里的写法。

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

empty_blob374–379 ↗
fn empty_blob() -> FWP_BYTE_BLOB

作用:生成一个 WFP 需要的“空字节块”。有些 WFP 结构体字段必须填,但这里没有额外数据,就用它来占位。

数据流:没有业务输入。它返回一个 size 为 0、data 为空指针的 FWP_BYTE_BLOB;不改动外部状态。

调用关系:ensure_provider、ensure_sublayer 和 add_filter 都会用它填 providerData 等空字段。它让这些地方不用重复写底层空指针细节。

调用图:被 3 处调用(add_filter, ensure_provider, ensure_sublayer);外部调用 1 个(null_mut)。

empty_value381–386 ↗
fn empty_value() -> FWP_VALUE0

作用:生成一个 WFP 需要的“空值”。例如过滤规则权重让系统默认处理时,就需要这种空值占位。

数据流:没有业务输入。它返回一个类型为 FWP_EMPTY 的 FWP_VALUE0,内部数据清零;不改动外部状态。

调用关系:add_filter 用它填写 weight 和 effectiveWeight 这类字段。它把 WFP 的低层结构初始化细节藏起来,避免主流程变乱。

调用图:被 1 处调用(add_filter);外部调用 1 个(zeroed)。

zero_guid388–390 ↗
fn zero_guid() -> GUID

作用:生成一个全零 GUID。这里用于 WFP 动作结构里不需要指定额外 filter type 的位置。

数据流:没有输入。它用 0 创建一个 GUID,并返回这个全零标识;不改动外部状态。

调用关系:add_filter 在组装阻止动作时调用它。它只是一个小占位工具,保证结构体字段有合法但无特殊含义的值。

调用图:被 1 处调用(add_filter);外部调用 1 个(from_u128)。

tests::filter_keys_are_unique399–412 ↗
fn filter_keys_are_unique()

作用:测试所有内置过滤规则的 key 是否互不重复。key 重复会导致规则覆盖、删除错对象或安装失败。

数据流:输入来自静态 FILTER_SPECS 列表。它取出每条规则的 GUID 各部分,放进有序集合去重,然后比较去重后的数量和原规则数量是否一致。输出是测试通过或失败。

调用关系:这是测试阶段运行的安全检查。它保护 add_filter 和 delete_filter_if_present 依赖的固定规则 key,避免开发者以后新增规则时不小心撞号。

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

tests::filter_names_are_unique415–421 ↗
fn filter_names_are_unique()

作用:测试所有内置过滤规则的名字是否互不重复。名字重复不一定让系统崩掉,但会让排查和查看规则时很混乱。

数据流:输入来自静态 FILTER_SPECS 列表。它收集每条规则的 name,放进集合去重,再比较去重数量和原数量。输出是测试通过或失败。

调用关系:这是测试阶段运行的可维护性检查。它帮助确保安装到 WFP 里的规则在工具或日志里能被人清楚地区分。

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

windows-sandbox-rs/src/wfp_setup.rs源码 ↗
orchestrationelevated setup

这个文件解决的是一个很实际的问题:沙箱要安全,就需要提前把目标用户的网络过滤规则装好;但这一步可能失败,甚至底层代码可能直接崩掉。如果不兜底,整个提权安装流程可能被一次网络规则安装失败拖垮。这里的做法像“施工队加安全员”:install_wfp_filters 先尝试安装 WFP 过滤器,不管成功、失败还是发生 panic(程序级崩溃),都会记录日志,并整理成一条结果。然后它会尽量把结果发到指标系统 Statsig,方便以后知道有多少机器装成功、哪里失败。指标上报本身也被保护起来:即使上报失败或崩溃,也只写日志,不影响主安装流程继续走。文件还会把用户名、错误信息这类标签清洗一下,避免把不适合上报的内容直接发出去。

函数细节5
panic_payload_to_string28–36 ↗
fn panic_payload_to_string(panic_payload: Box<dyn std::any::Any + Send>) -> String

作用:把 Rust 的 panic 内容转换成普通字符串。panic 可以理解成程序遇到严重异常后“突然摔倒”,它携带的内容不一定是普通文本,所以这里负责尽量读懂它。

数据流:进去的是一个 panic 携带的原始内容包 → 函数先试着把它当成 String,再试着当成静态字符串 &str → 如果都不是,就给出“unknown panic payload”这个兜底文字。出来的是一段可以写进日志或指标的错误说明。

调用关系:它是错误兜底的小工具。install_wfp_filters 在安装 WFP 过滤器崩溃时会用它把崩溃原因变成人能看的文字;emit_wfp_setup_metric_safely 在指标上报崩溃时也会用它,保证日志里能留下线索。

调用图:被 2 处调用(emit_wfp_setup_metric_safely, install_wfp_filters)。

build_wfp_metrics_provider38–62 ↗
fn build_wfp_metrics_provider(
    codex_home: &Path,
    otel: Option<&StatsigMetricsSettings>,
) -> Result<Option<OtelProvider>>

作用:创建一个专门用于上报 WFP 安装结果的指标发送器。这里的指标发送器会把“成功了几次、失败了几次”这类数字发到 Statsig。

数据流:进去的是 codex_home 路径和可选的 Statsig 指标设置 → 如果没有指标设置,就直接返回“不要上报” → 如果有,就拼出一份 OpenTelemetry 设置,OpenTelemetry 是一套通用的遥测标准,用来发指标、日志、追踪等信息 → 出来的是可用的 OtelProvider,或者初始化失败的错误。

调用关系:它只被 emit_wfp_setup_metric 调用。emit_wfp_setup_metric 需要真正发指标时,先请它搭好发送通道;如果父流程没有传入 Statsig 配置,它就让后续流程安静跳过上报。

调用图:调用 1 个内部函数(from);被 1 处调用(emit_wfp_setup_metric);外部调用 3 个(new, to_path_buf, env!)。

emit_wfp_setup_metric64–98 ↗
fn emit_wfp_setup_metric(
    codex_home: &Path,
    otel: Option<&StatsigMetricsSettings>,
    metric: &WfpSetupMetric,
) -> Result<()>

作用:把一次 WFP 过滤器安装的结果发成指标。成功时记录成功次数和装了多少条过滤规则;失败时记录失败次数和错误信息。

数据流:进去的是 codex_home、可选的指标设置,以及一份 WfpSetupMetric 结果记录 → 它先建立指标发送器,再清洗用户名和错误信息这些标签 → 如果结果是成功,就给成功指标加 1,并带上目标用户和安装数量;如果结果是失败,就给失败指标加 1,并带上目标用户和可用的错误消息 → 最后关闭指标发送器,避免资源一直开着。

调用关系:它是实际发指标的地方。emit_wfp_setup_metric_safely 会包着调用它,因为发指标不应该影响主安装流程;它内部会调用 build_wfp_metrics_provider 来拿发送通道,也会调用 sanitize_setup_metric_tag_value 来清洗上报标签。

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

emit_wfp_setup_metric_safely100–124 ↗
fn emit_wfp_setup_metric_safely(
    codex_home: &Path,
    otel: Option<&StatsigMetricsSettings>,
    offline_username: &str,
    metric: &WfpSetupMetric,
    log: &mut F,
)

作用:安全地尝试上报 WFP 安装指标。它的重点不是“保证一定上报成功”,而是“就算上报失败或崩溃,也别影响安装流程”。

数据流:进去的是路径、指标设置、用户名、安装结果记录,以及一个写日志的函数 → 它用 catch_unwind 包住真正的上报动作,catch_unwind 可以理解成给可能崩溃的代码套一层防护网 → 如果上报成功,就什么也不做;如果返回错误,就写一条失败日志;如果直接 panic,就把 panic 内容转成文字再写日志 → 不返回业务结果,只留下必要日志。

调用关系:它被 install_wfp_filters 在最后调用。主安装流程已经得到成功或失败结果后,会把结果交给它尝试上报;它再调用 emit_wfp_setup_metric 做实际发送,必要时调用 panic_payload_to_string 解释崩溃内容。

调用图:调用 1 个内部函数(panic_payload_to_string);被 1 处调用(install_wfp_filters);外部调用 3 个(format!, AssertUnwindSafe, catch_unwind)。

install_wfp_filters126–175 ↗
fn install_wfp_filters(
    codex_home: &Path,
    offline_username: &str,
    otel: Option<&StatsigMetricsSettings>,
    mut log: F,
)

作用:这是外部调用的主入口:给指定的离线用户安装 WFP 网络过滤规则,并记录日志和指标。它特别强调“失败也继续”,因为 WFP 安装不应该让整个提权设置流程完全中断。

数据流:进去的是 codex_home 路径、目标用户名、可选的 Statsig 指标设置,以及一个日志函数 → 它用防护网包住 install_wfp_filters_for_account 这一步,尝试真正安装过滤器 → 如果成功,就记录安装了多少条过滤器,并生成成功指标;如果返回错误,就记录失败原因,并生成失败指标;如果发生 panic,就把崩溃内容变成文字,也生成失败指标 → 最后把整理好的结果交给安全上报函数尝试发送。

调用关系:它是这个文件对外最重要的流程函数,通常在 Windows 沙箱的提权设置阶段被调用。它把真正安装过滤器的工作交给 install_wfp_filters_for_account,把指标上报交给 emit_wfp_setup_metric_safely;中间负责兜住错误、写日志,并把各种结果统一整理成 WfpSetupMetric。

调用图:调用 2 个内部函数(emit_wfp_setup_metric_safely, panic_payload_to_string);外部调用 3 个(format!, AssertUnwindSafe, catch_unwind)。