-
-
[原创]codex多平台配置分身
-
发表于: 1天前 205
-
给每个 Codex 实例一套配置:CodexMgr 的实现思路
用 Codex 时,我遇到一个实际问题:不同实例通常都会读系统里的全局配置。即使启动完成,Codex 后续也可能再次读取配置文件。这样一来,要让几个同时运行的实例分别使用不同平台的 API Key 和 base_url,就不太方便。
当然,如果当前启动方式和配置支持用 CMD 或 PowerShell 为某个实例设置环境变量,也是直接可用的办法(个人不喜)。CodexMgr 想解决的是另一种使用场景:把不同平台各自的 auth.json、config.toml 放在不同目录里,启动 Codex 时在界面选择一套配置,让这个实例之后对这两个文件的访问都落到所选目录。
图示如下:
不同平台的配置文件存放于同目录的“BranchCodex”目录中,子目录的名字就是UI界面显示的名字,如图:

从启动时选择,到文件打开重定向
这不是复制和替换全局配置文件,也不是为每个实例创建一份完整的用户目录。工具由 Windows 内核 Minifilter 驱动和 Flutter Windows 管理界面组成。驱动识别新创建的 codex.exe,向界面报告实例;用户选择“默认”或一个配置分支后,驱动把决定绑定到这个进程实例。
之后,驱动只检查这个已选分支的 Codex 实例发起的文件打开请求。如果打开的文件名是 auth.json 或 config.toml,就把目标路径重新解析到所选目录里的同名文件。其他进程、默认配置实例和其他文件照常访问原路径。
sequenceDiagram
participant C as Codex 创建
participant D as Minifilter 驱动
participant U as 管理界面
participant F as 文件系统
C->>D: 创建 codex.exe
D->>U: 报告实例和 PID
D-->>C: 等待选择期间暂缓创建完成
U->>D: 提交默认或分支选择
D-->>C: 保存策略并继续创建
C->>D: 打开 auth.json / config.toml
D->>F: 将路径重解析到所选分支
F-->>C: 返回所选目录中的文件
为什么在进程创建时等待
如果等 Codex 第一次打开配置时才让用户选择,进程可能已经读过配置,也可能在运行期间重新读取。CodexMgr 在进程创建通知中识别 codex.exe,让界面有机会先提交选择,再让新实例继续启动。每个进程只接受一次决定;决定完成后,驱动保存对应策略,后续文件请求可以据此判断。
这段等待有明确边界:UI 未就绪、断开、等待超时或请求资源不足时,驱动默认放行,避免管理界面不可用时影响 Codex 的正常启动。当前等待上限为 60 秒。若实际创建 Codex 的程序是 node.exe,Windows 11 上 UI 原生代码还会尝试在等待期间挂起该父进程,并在决定或等待结束后解除本工具持有的挂起。该联动依赖系统 Native 接口;不支持或挂起失败时,仍可以完成配置选择。它不会暂停系统时钟,也不能保证消除启动器或上层 IDE 的超时。
Minifilter 怎样换掉文件目标
驱动在 filter.c 注册 IRP_MJ_CREATE 和 IRP_MJ_NETWORK_QUERY_OPEN 两类回调。常规文件重定向从 CmPreCreate 开始:先排除非 IRP、目录、卷打开和不支持的打开形式,再用文件名做一次候选筛选,随后按请求者进程查找已确认的分支策略。关键路径可概括为:
if (!FLT_IS_IRP_OPERATION(Data))
return FLT_PREOP_SUCCESS_NO_CALLBACK;
if (FlagOn(Data->Iopb->OperationFlags, SL_OPEN_TARGET_DIRECTORY) ||
FlagOn(Data->Iopb->TargetFileObject->Flags, FO_VOLUME_OPEN) ||
FlagOn(Data->Iopb->Parameters.Create.Options,
FILE_DIRECTORY_FILE | FILE_OPEN_BY_FILE_ID) ||
!CmCandidate(Data->Iopb->TargetFileObject))
return FLT_PREOP_SUCCESS_NO_CALLBACK;
status = CmCopyTargetDirectory(FltGetRequestorProcess(Data), &directory);
if (status == STATUS_NOT_FOUND)
return FLT_PREOP_SUCCESS_NO_CALLBACK;
FltGetRequestorProcess 很关键:文件系统回调执行时的当前进程不一定就是发起 I/O 的进程。策略表以进程对象为键,因此只凭可能被复用的 PID 不会把旧实例的分支错误地交给新进程。没有 Redirect 策略时返回 STATUS_NOT_FOUND,原请求原样通过;已确认分支但目录状态异常时则返回错误,不静默改用全局配置。
CmRewrite 再查询并解析实际打开名称,校验最终文件名,只处理普通数据流。它把文件名拼到已验证的分支目录后,先检查请求是否已经指向该目标;比较同时使用打开名称和可取得的规范化名称,以识别别名并避免重入循环。核心重解析代码如下:
status = CmCountReparse(Data);
if (NT_SUCCESS(status))
status = IoReplaceFileObjectName(
Data->Iopb->TargetFileObject, Directory->Buffer, Directory->Length);
if (!NT_SUCCESS(status)) {
result = CmCompleteError(Data, status);
goto Exit;
}
FltSetCallbackDataDirty(Data);
Data->IoStatus.Status = STATUS_REPARSE;
Data->IoStatus.Information = IO_REPARSE;
return FLT_PREOP_COMPLETE;
Directory 在这一步已经是分支目录加上 \\auth.json 或 \\config.toml 的绝对 NT 路径。IoReplaceFileObjectName 更新本次创建请求的目标名称,FltSetCallbackDataDirty 标记回调数据已修改;STATUS_REPARSE 则要求 I/O 管理器按新名称重新解析。使用完整目标路径也让源文件与分支位于不同卷时仍有表达空间。若新句柄成功打开,后续读写作用于这个真实文件对象,不需要再逐个改写 Read/Write 请求。
执行上下文也会影响实现。文件名查询和替换要求在合适的 IRQL 与文件系统上下文中进行;CmPreCreate 对可延迟的 APC_LEVEL 请求使用 Filter Manager deferred I/O work item 转到 PASSIVE_LEVEL,并对取消、卸载 rundown 和排队配额做清理。非 IRP、目标目录打开、按文件 ID 打开等路径不会套用普通文件名重定向;驱动不能在任意 IRQL 直接查询名称或修改文件对象。
快速查询是另一条容易漏掉的路径。CmPreQueryOpen 对已选分支的进程返回 FLT_PREOP_DISALLOW_FASTIO,让查询回落到可检查和重解析的 IRP 路径。Fast I/O 回调不能照搬 STATUS_REPARSE 处理。对于已经是目标路径的请求,驱动直接放行;另外它在 ECP 中记录本驱动的重解析次数,达到上限时以 STATUS_REPARSE_POINT_NOT_RESOLVED 结束,避免驱动栈反复重入。
界面和驱动如何保持一致
管理界面负责扫描配置目录、呈现新实例和提交选择;驱动负责验证目录并执行实际文件重定向。两者通过 Filter Manager 通信端口交换版本化消息。界面提交选择后,以驱动返回的确认结果更新实例状态,避免只显示“已选择”但内核策略尚未生效。
事件序号和状态快照用于恢复通知丢失或重新连接后的状态;心跳则让驱动知道管理界面是否仍然就绪。已确认的实例保留自己的分支映射,目录刷新不会把运行中的实例悄悄切换到另一套配置。正常卸载前,需要先退出仍在使用分支的 Codex 实例。
界面从程序所在目录扫描 BranchCodex。每个有效分支需要包含普通文件 auth.json 和 config.toml。当前实现面向本地固定 NTFS 卷,并拒绝目录树中的符号链接或 junction。扫描验证文件和目录元数据,不读取凭据内容,也不把凭据内容发送给驱动。
使用范围和验证状态
这是一项针对多个配置文件的路径重定向,不是进程沙箱,十个人特质化工具。它不会隔离凭据库、环境变量、其他配置文件、继承的文件句柄,也不会自动拦截其他进程代替 Codex 执行的 I/O。也不覆盖临时文件写入后再通过 rename/replace 替换配置的方式。Codex 的配置读取方式可能随版本变化,实际使用前应针对目标版本验证。
工程的驱动和界面已完成 x64 构建,代码分析和多项协议、目录及合成进程自动化检查也已通过。不过,这些检查不能替代驱动在目标 Windows 上的签名、加载和内核运行测试;当前项目记录中的真实 Codex 启动与配置读写验收也尚未完成。遇到驱动加载问题时,应先检查目标系统要求及最终 .sys 文件的有效签名。
本文随附源码和二进制文件,有需要的同学可以下载研究,UI基于flutter,驱动基于WDM。驱动需要自行签名后再按目标系统要求安装和测试;有扩展需求,欢迎留言交流。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。