mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-16 07:16:36 +08:00
fix(sandbox): grant Windows temp write access
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
> upstream: `/Users/coso/Documents/dev/rust/codex`(当前观测 HEAD:`99660ab3c7b861c916e467581fa9b8723504d66b`)
|
||||
> checklist progress: `44/46 (95.7%)`;这是未加权任务数,不等于交付完成度,剩余项包含 Windows 平台安全、live/verifier 与正式分发硬门槛。
|
||||
|
||||
> 当前主目标(2026-08-26):关闭 C-3 Windows restricted execution 的真机证据。Codex current 的 per-user Windows Firewall 非 loopback/loopback 出站阻断、offline/online sandbox identity、`LimeSandboxUsers` 普通 token ACL、短生命周期 capability SID restricted ACL、clean-runner setup/read-back、原始 DACL rollback、sandbox-account command runner 与七项 evidence collector 已进入唯一 current owner。真机已证明 runner transport/MSVC 编译和内置 `Users` setup/read-back 成功,但 restricted child 仍在无输出前 exit `1`;当前下一刀是补齐 Codex 对 Windows 平台目录与当前用户 profile 非敏感子目录的默认 RX ACL 刷新/read-back,再重跑真实 Windows/MSVC 矩阵。在证据通过前保持 `Planned/enforced=false`。
|
||||
> 当前主目标(2026-08-26):关闭 C-3 Windows restricted execution 的真机证据。Codex current 的 per-user Windows Firewall 非 loopback/loopback 出站阻断、offline/online sandbox identity、`LimeSandboxUsers` 普通 token ACL、短生命周期 capability SID restricted ACL、clean-runner setup/read-back、原始 DACL rollback、sandbox-account command runner 与七项 evidence collector 已进入唯一 current owner。真机已证明 runner transport/MSVC 编译、内置 `Users` setup/read-back 和默认平台/profile RX ACL setup/read-back 成功,但 restricted PowerShell 仍在首条输出前 exit `1`;当前下一刀是对齐 Codex `workspace_write` 默认将 child `%TEMP%/%TMP%` 纳入可写 ACL lease 的语义,再重跑真实 Windows/MSVC 矩阵。在证据通过前保持 `Planned/enforced=false`。
|
||||
> 参考原则:`/Users/coso/Documents/dev/rust/codex` 是协议、Thread/Turn/Item、runtime lifecycle、tool/approval 和 GUI 语义的直接实现参考源;Lime 只在产品范围、provider 和桌面宿主边界做显式适配,不以旧矩阵或同名 legacy API 代替对齐。
|
||||
> 目标:把 Lime 的产品主链、协议字段、GUI 工作区、执行安全和真实 Desktop 证据推进到当前 Codex Desktop 语义;不恢复已退役 runtime、v0 或 `agentSession/*` 兼容主链。
|
||||
|
||||
@@ -68,7 +68,7 @@ Renderer typed gateway
|
||||
| MCP resource origin | Workspace MCP App 从 canonical Item 的 `appContext`/resource URI 建立 WebContentsView surface,并以 `originCallId` 请求同一 Thread authority;`codex_apps` 不依赖本地 Plugin 安装态 | 已完成 canonical producer/cold restore、bounded provenance、link/account revalidation、稳定组件回归和独立 Electron Gate B;证据见 `.lime/qc/gui-evidence/mcp-resource-origin-electron-gate-b/` |
|
||||
| Environment lifecycle | `TaskCenterEnvironmentPanel` + `useThreadEnvironmentLifecycleStatus` 已消费 `environment/status` 与 typed connected/disconnected notification,稳定 DOM 覆盖环境 identity/status | 已完成五语言、组件回归和独立 Electron Gate B:真实远端 Environment 断线与有限重连在同一 canonical Thread 可见,mock/console/page/invoke error 为 0 |
|
||||
|
||||
当前执行顺序:Provider capability、Queue(含重复正文视觉收口)、Project、Strict Review、D-2 MCP event stream/resource origin、D-3 Environment、Thread Revert、Browser/Computer Use requirements 产品裁决、Thread/Item current 字段/恢复复核、Agent current 聚合门禁、本机 macOS packaged candidate、`1.135.0` 正式 macOS 分发和 Windows Squirrel N-1 packaged Gate B 已关闭;C-3 Windows restricted-token、ACL/Job、ConPTY、elevated setup、offline/online account、Firewall/WFP、readiness read-back、本地 fail-closed 单测和真实 packaged evidence collector 已进入 current owner。当前先对齐默认平台/profile read roots 并重跑七项 Windows/MSVC 安全矩阵;矩阵 `7/7` 后再验证 packaged runner digest。live provider/Pier/DeepSWE verifier 和覆盖当前工作树的正式分发同样等待外部环境。每完成一项即更新本计划与运行证据,不批量提前勾选。
|
||||
当前执行顺序:Provider capability、Queue(含重复正文视觉收口)、Project、Strict Review、D-2 MCP event stream/resource origin、D-3 Environment、Thread Revert、Browser/Computer Use requirements 产品裁决、Thread/Item current 字段/恢复复核、Agent current 聚合门禁、本机 macOS packaged candidate、`1.135.0` 正式 macOS 分发和 Windows Squirrel N-1 packaged Gate B 已关闭;C-3 Windows restricted-token、ACL/Job、ConPTY、elevated setup、offline/online account、Firewall/WFP、readiness read-back、本地 fail-closed 单测和真实 packaged evidence collector 已进入 current owner。当前先对齐 workspace-write 的 Windows Temp 可写根并重跑七项 Windows/MSVC 安全矩阵;矩阵 `7/7` 后再验证 packaged runner digest。live provider/Pier/DeepSWE verifier 和覆盖当前工作树的正式分发同样等待外部环境。每完成一项即更新本计划与运行证据,不批量提前勾选。
|
||||
|
||||
> 最新 Queue/CI 收口状态(2026-08-26):Queue 状态条已删除 submission preview 与 `thread-queue-items` 正文列表,canonical 时间线成为唯一正文 owner;五语言只保留标题、loading、数量和刷新失败状态。组件、Gate B 与 Sherpa 定向回归 25/25,`npm run i18n:check` 保持 100%。真实 Queue Gate B 已通过:`timelineMarkerCount=1`、`queueStatusContainsMarker=false`、`queueItemsPresent=false`、47 次真实 Electron IPC、`mockFallbackHitCount=0`、console/page/invoke error 均为 0,证据见 `.lime/qc/gui-evidence/thread-queue-electron-gate-b/thread-queue-electron-gate-b-summary.json`。根 `package.json`/lock importer 已声明直接 `ws@8.18.3`,修复 Quality CI Frontend Full 的 `Failed to resolve import "ws"`;Windows Shell Runtime 上限调为 60 分钟,restricted security matrix 已前移到 Sherpa/sidecar 重型阶段之前,Sherpa 清理/解压前后输出阶段日志。`smoke:agent-runtime-current-fixture` 完整通过,`npm run verify:gui-smoke` Shell-01 21/21、contracts 299 checks、typecheck、scripts/legacy governance 均通过;App Server Windows sandbox fail-closed 定向单测 4/4 通过。上述本机测试不替代 Windows restricted matrix 或 live verifier;正式 macOS 与 Squirrel 证据由独立 Release run `32864956267` 证明。
|
||||
|
||||
@@ -395,6 +395,7 @@ Renderer typed gateway
|
||||
- 2026-08-26:Windows Quality run `32957785741`(SHA `a2a6caa85`,artifact `9602756033`)在 setup 阶段编译 `tool-runtime` 失败,七项矩阵未执行。MSVC `E0277` 明确指向 `NullDeviceLease` 持有 `LocalSid(*mut c_void)`,因此无法跨 supervisor/reaper 线程移动;修复为 lease 仅保存 capability SID 字符串,安装/撤销 ACE 时在当前线程短暂解析 `LocalSid`,不添加 `unsafe impl Send`。同时移除日志暴露的 Windows-only unused import;本机 related Rust 反向依赖链与 `tool-runtime 373/373`、rustfmt、`git diff --check` 通过。该 run 继续是 `failedStage=setup`、矩阵 `0/7`,不改变 `44/46 (95.7%)` 或 `Planned/enforced=false`。
|
||||
- 2026-08-26:Windows Quality run `32958651645`(SHA `37f385db6`,artifact `9603104216`)已通过 setup 和 MSVC 编译,schema v3 七项矩阵完整执行为 `1/7`:Everyone-write audit 通过,其余六项 restricted child 仍在无输出前 exit `1`,terminate 场景因 runner 提前结束返回 `ControlClosed`。该 run 证明 NUL lease 的 `Send` 编译问题已关闭,但 NUL ACE 未解决 child 启动。继续对照 Codex `setup_main::sandbox_users` 发现 Lime 遗漏将 sandbox account 加入 Windows 内置 `Users`(SID `S-1-5-32-545`);当前修复对本地化组名执行 setup 幂等加入、readiness 与每次 runner 启动 read-back,避免子进程缺少仅授权给内置 Users 的系统资源访问。完成度继续为 `44/46 (95.7%)`,readiness 继续 `Planned/enforced=false`。
|
||||
- 2026-08-26:Windows Quality run `32960057901`(SHA `f1bec2328`,artifact `9603664649`)再次通过 setup/MSVC 编译并完整执行 schema v3,但矩阵仍为 `1/7`;六项 restricted child 继续无有效输出 exit `1`,说明内置 `Users` 成员身份已正确写入/read-back,却不是唯一缺口。继续逐段对照 Codex `gather_read_roots -> run_setup_refresh -> apply_read_acls` 后确认 Lime 尚未刷新 `C:\Windows`、Program Files/ProgramData 与当前用户 profile 非敏感子目录的默认 RX ACL;这会让已创建的 restricted PowerShell 在加载 DLL、模块或 AppData 时静默失败。当前修复将该行为收归 Windows setup、补 read-back 并提升 setup marker version 强制旧安装重跑;完成度/readiness 不变。
|
||||
- 2026-08-26:Windows Quality run `32961870012`(SHA `740d5b61b`,artifact `9604315141`)证明默认平台/profile RX ACL 已通过 setup/read-back 与 MSVC 编译,但 schema v3 仍为 `1/7`;六项 PowerShell child 全部在首条输出前 exit `1`,因此 read roots 不是最后一个缺口。继续对照 Codex `PermissionProfile::workspace_write -> FileSystemSpecialPath::Tmpdir -> windows_temp_env_roots` 后确认 Lime ACL plan 只给 cwd/显式 grant 可写,遗漏 child 继承的 `%TEMP%/%TMP%`;sandbox account 对宿主 Temp 只有 RX,符合 PowerShell 初始化阶段无输出失败。当前修复把已过滤 child environment 中的 Windows Temp 根纳入 workspace-write ACL lease,保持原始 DACL rollback、metadata deny、完成度 `44/46 (95.7%)` 与 `Planned/enforced=false` 不变。
|
||||
|
||||
## 7. 阻塞与避让
|
||||
|
||||
|
||||
@@ -112,7 +112,12 @@ pub(super) fn start_windows_restricted_execution_process(
|
||||
.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
}
|
||||
let acl_plan = build_acl_plan(&cwd, policy, sandbox.granted_permissions.as_ref())?;
|
||||
let acl_plan = build_acl_plan(
|
||||
&cwd,
|
||||
policy,
|
||||
sandbox.granted_permissions.as_ref(),
|
||||
&request.env,
|
||||
)?;
|
||||
let sandbox_group_sid = windows_sandbox_users_group_sid()?;
|
||||
let capability_sid = capability_sid();
|
||||
let acl_lease = AclLease::acquire(&sandbox_group_sid, &capability_sid, acl_plan)?;
|
||||
|
||||
@@ -4,6 +4,7 @@ use app_server_protocol::protocol::v2::{AdditionalFileSystemPermissions, FileSys
|
||||
use app_server_protocol::protocol::v2::{
|
||||
FileSystemAccessMode, FileSystemPath, FileSystemSpecialPath, GrantedPermissionProfile,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
|
||||
@@ -35,6 +36,7 @@ pub(super) fn build_acl_plan(
|
||||
cwd: &Path,
|
||||
policy: RequestedSandboxPolicy,
|
||||
permissions: Option<&GrantedPermissionProfile>,
|
||||
environment: &HashMap<String, String>,
|
||||
) -> io::Result<AclPlan> {
|
||||
let mut read_grants = vec![canonical_existing(cwd)?];
|
||||
let mut write_grants = Vec::new();
|
||||
@@ -42,6 +44,7 @@ pub(super) fn build_acl_plan(
|
||||
let mut access_denies = Vec::new();
|
||||
if policy == RequestedSandboxPolicy::WorkspaceWrite {
|
||||
write_grants.push(canonical_existing(cwd)?);
|
||||
write_grants.extend(windows_temp_write_roots(environment)?);
|
||||
for name in PROTECTED_METADATA_NAMES {
|
||||
let path = cwd.join(name);
|
||||
if path.exists() {
|
||||
@@ -71,12 +74,11 @@ pub(super) fn build_acl_plan(
|
||||
}
|
||||
read_grants.sort();
|
||||
read_grants.dedup();
|
||||
write_grants.sort();
|
||||
write_grants.dedup();
|
||||
write_grants = minimal_roots(write_grants);
|
||||
access_denies.sort();
|
||||
access_denies.dedup();
|
||||
write_grants.retain(|path| access_denies.binary_search(path).is_err());
|
||||
read_grants.retain(|path| write_grants.binary_search(path).is_err());
|
||||
read_grants.retain(|path| !write_grants.iter().any(|root| path.starts_with(root)));
|
||||
read_grants.retain(|path| access_denies.binary_search(path).is_err());
|
||||
write_denies.sort();
|
||||
write_denies.dedup();
|
||||
@@ -89,6 +91,40 @@ pub(super) fn build_acl_plan(
|
||||
})
|
||||
}
|
||||
|
||||
fn windows_temp_write_roots(environment: &HashMap<String, String>) -> io::Result<Vec<PathBuf>> {
|
||||
["TEMP", "TMP"]
|
||||
.into_iter()
|
||||
.filter_map(|key| {
|
||||
environment
|
||||
.iter()
|
||||
.find(|(name, _)| name.eq_ignore_ascii_case(key))
|
||||
.map(|(_, value)| PathBuf::from(value.as_str()))
|
||||
})
|
||||
.filter(|path| path.is_absolute())
|
||||
.map(|path| canonical_existing(&path))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn minimal_roots(mut roots: Vec<PathBuf>) -> Vec<PathBuf> {
|
||||
roots.sort_by(|left, right| {
|
||||
left.components()
|
||||
.count()
|
||||
.cmp(&right.components().count())
|
||||
.then_with(|| left.cmp(right))
|
||||
});
|
||||
let mut minimal = Vec::new();
|
||||
for root in roots {
|
||||
if !minimal
|
||||
.iter()
|
||||
.any(|parent: &PathBuf| root.starts_with(parent))
|
||||
{
|
||||
minimal.push(root);
|
||||
}
|
||||
}
|
||||
minimal.sort();
|
||||
minimal
|
||||
}
|
||||
|
||||
fn resolve_permission_path(path: &FileSystemPath, cwd: &Path) -> io::Result<Option<PathBuf>> {
|
||||
match path {
|
||||
FileSystemPath::Path { path } => canonical_absolute(Path::new(path)).map(Some),
|
||||
@@ -489,8 +525,13 @@ mod tests {
|
||||
fn workspace_acl_plan_grants_workspace_and_denies_metadata() {
|
||||
let root = tempfile::tempdir().expect("workspace");
|
||||
std::fs::create_dir(root.path().join(".git")).expect("git metadata");
|
||||
let plan = build_acl_plan(root.path(), RequestedSandboxPolicy::WorkspaceWrite, None)
|
||||
.expect("ACL plan");
|
||||
let plan = build_acl_plan(
|
||||
root.path(),
|
||||
RequestedSandboxPolicy::WorkspaceWrite,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
)
|
||||
.expect("ACL plan");
|
||||
|
||||
assert_eq!(
|
||||
plan.write_grants,
|
||||
@@ -507,8 +548,13 @@ mod tests {
|
||||
#[test]
|
||||
fn read_only_acl_plan_grants_workspace_read_access() {
|
||||
let root = tempfile::tempdir().expect("workspace");
|
||||
let plan =
|
||||
build_acl_plan(root.path(), RequestedSandboxPolicy::ReadOnly, None).expect("ACL plan");
|
||||
let plan = build_acl_plan(
|
||||
root.path(),
|
||||
RequestedSandboxPolicy::ReadOnly,
|
||||
None,
|
||||
&HashMap::new(),
|
||||
)
|
||||
.expect("ACL plan");
|
||||
|
||||
assert_eq!(
|
||||
plan.read_grants,
|
||||
@@ -552,6 +598,7 @@ mod tests {
|
||||
root.path(),
|
||||
RequestedSandboxPolicy::WorkspaceWrite,
|
||||
Some(&permissions),
|
||||
&HashMap::new(),
|
||||
)
|
||||
.expect("ACL plan");
|
||||
|
||||
@@ -571,4 +618,34 @@ mod tests {
|
||||
let root = tempfile::tempdir().expect("workspace");
|
||||
reject_reparse_components(root.path()).expect("ordinary path should be accepted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_acl_plan_grants_windows_temp_roots() {
|
||||
let root = tempfile::tempdir().expect("fixture root");
|
||||
let workspace = root.path().join("workspace");
|
||||
std::fs::create_dir(&workspace).expect("workspace directory");
|
||||
let environment = HashMap::from([
|
||||
(
|
||||
"Temp".to_string(),
|
||||
root.path().to_string_lossy().into_owned(),
|
||||
),
|
||||
(
|
||||
"TMP".to_string(),
|
||||
root.path().to_string_lossy().into_owned(),
|
||||
),
|
||||
]);
|
||||
|
||||
let plan = build_acl_plan(
|
||||
&workspace,
|
||||
RequestedSandboxPolicy::WorkspaceWrite,
|
||||
None,
|
||||
&environment,
|
||||
)
|
||||
.expect("ACL plan");
|
||||
|
||||
assert_eq!(
|
||||
plan.write_grants,
|
||||
vec![std::fs::canonicalize(root.path()).unwrap()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user