diff --git a/.github/workflows/rust-ci-full.yml b/.github/workflows/rust-ci-full.yml index 8c883a6e2d..de37faa4ca 100644 --- a/.github/workflows/rust-ci-full.yml +++ b/.github/workflows/rust-ci-full.yml @@ -44,10 +44,9 @@ jobs: - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49 with: - tool: cargo-shear - version: 1.11.2 + tool: cargo-shear@1.11.2 - name: cargo shear - run: cargo shear + run: cargo shear --deny-warnings argument_comment_lint_package: name: Argument comment lint package diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 243c208436..52322913e5 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -86,10 +86,9 @@ jobs: - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49 with: - tool: cargo-shear - version: 1.11.2 + tool: cargo-shear@1.11.2 - name: cargo shear - run: cargo shear + run: cargo shear --deny-warnings argument_comment_lint_package: name: Argument comment lint package diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 10b5cc2351..40ca5c809d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3331,7 +3331,6 @@ dependencies = [ "codex-utils-absolute-path", "codex-utils-image", "codex-utils-string", - "codex-utils-template", "encoding_rs", "globset", "http 1.4.0", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 6bda741c9c..adbcdb14ea 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -464,11 +464,8 @@ unwrap_used = "deny" [workspace.metadata.cargo-shear] ignored = [ "codex-agent-graph-store", - "codex-memories-mcp", "icu_provider", "openssl-sys", - "codex-utils-readiness", - "codex-utils-template", "codex-v8-poc", ] diff --git a/codex-rs/codex-backend-openapi-models/Cargo.toml b/codex-rs/codex-backend-openapi-models/Cargo.toml index f6ff459b0f..7baf01935d 100644 --- a/codex-rs/codex-backend-openapi-models/Cargo.toml +++ b/codex-rs/codex-backend-openapi-models/Cargo.toml @@ -22,6 +22,3 @@ workspace = true serde = { version = "1", features = ["derive"] } serde_json = "1" serde_with = "3" - -[package.metadata.cargo-shear] -ignored = ["serde_with"] diff --git a/codex-rs/core/src/tools/code_mode/execute_handler_tests.rs b/codex-rs/core/src/tools/code_mode/execute_handler_tests.rs deleted file mode 100644 index ed22b337b2..0000000000 --- a/codex-rs/core/src/tools/code_mode/execute_handler_tests.rs +++ /dev/null @@ -1,41 +0,0 @@ -use super::parse_freeform_args; -use pretty_assertions::assert_eq; - -#[test] -fn parse_freeform_args_without_pragma() { - let args = parse_freeform_args("output_text('ok');").expect("parse args"); - assert_eq!(args.code, "output_text('ok');"); - assert_eq!(args.yield_time_ms, None); - assert_eq!(args.max_output_tokens, None); -} - -#[test] -fn parse_freeform_args_with_pragma() { - let input = concat!( - "// @exec: {\"yield_time_ms\": 15000, \"max_output_tokens\": 2000}\n", - "output_text('ok');", - ); - let args = parse_freeform_args(input).expect("parse args"); - assert_eq!(args.code, "output_text('ok');"); - assert_eq!(args.yield_time_ms, Some(15_000)); - assert_eq!(args.max_output_tokens, Some(2_000)); -} - -#[test] -fn parse_freeform_args_rejects_unknown_key() { - let err = parse_freeform_args("// @exec: {\"nope\": 1}\noutput_text('ok');") - .expect_err("expected error"); - assert_eq!( - err.to_string(), - "exec pragma only supports `yield_time_ms` and `max_output_tokens`; got `nope`" - ); -} - -#[test] -fn parse_freeform_args_rejects_missing_source() { - let err = parse_freeform_args("// @exec: {\"yield_time_ms\": 10}").expect_err("expected error"); - assert_eq!( - err.to_string(), - "exec pragma must be followed by JavaScript source on subsequent lines" - ); -} diff --git a/codex-rs/core/src/tools/handlers/grep_files_tests.rs b/codex-rs/core/src/tools/handlers/grep_files_tests.rs deleted file mode 100644 index 0cc247c6f1..0000000000 --- a/codex-rs/core/src/tools/handlers/grep_files_tests.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::*; -use std::process::Command as StdCommand; -use tempfile::tempdir; - -#[test] -fn parses_basic_results() { - let stdout = b"/tmp/file_a.rs\n/tmp/file_b.rs\n"; - let parsed = parse_results(stdout, 10); - assert_eq!( - parsed, - vec!["/tmp/file_a.rs".to_string(), "/tmp/file_b.rs".to_string()] - ); -} - -#[test] -fn parse_truncates_after_limit() { - let stdout = b"/tmp/file_a.rs\n/tmp/file_b.rs\n/tmp/file_c.rs\n"; - let parsed = parse_results(stdout, 2); - assert_eq!( - parsed, - vec!["/tmp/file_a.rs".to_string(), "/tmp/file_b.rs".to_string()] - ); -} - -#[tokio::test] -async fn run_search_returns_results() -> anyhow::Result<()> { - if !rg_available() { - return Ok(()); - } - let temp = tempdir().expect("create temp dir"); - let dir = temp.path(); - std::fs::write(dir.join("match_one.txt"), "alpha beta gamma").unwrap(); - std::fs::write(dir.join("match_two.txt"), "alpha delta").unwrap(); - std::fs::write(dir.join("other.txt"), "omega").unwrap(); - - let results = run_rg_search("alpha", None, dir, 10, dir).await?; - assert_eq!(results.len(), 2); - assert!(results.iter().any(|path| path.ends_with("match_one.txt"))); - assert!(results.iter().any(|path| path.ends_with("match_two.txt"))); - Ok(()) -} - -#[tokio::test] -async fn run_search_with_glob_filter() -> anyhow::Result<()> { - if !rg_available() { - return Ok(()); - } - let temp = tempdir().expect("create temp dir"); - let dir = temp.path(); - std::fs::write(dir.join("match_one.rs"), "alpha beta gamma").unwrap(); - std::fs::write(dir.join("match_two.txt"), "alpha delta").unwrap(); - - let results = run_rg_search("alpha", Some("*.rs"), dir, 10, dir).await?; - assert_eq!(results.len(), 1); - assert!(results.iter().all(|path| path.ends_with("match_one.rs"))); - Ok(()) -} - -#[tokio::test] -async fn run_search_respects_limit() -> anyhow::Result<()> { - if !rg_available() { - return Ok(()); - } - let temp = tempdir().expect("create temp dir"); - let dir = temp.path(); - std::fs::write(dir.join("one.txt"), "alpha one").unwrap(); - std::fs::write(dir.join("two.txt"), "alpha two").unwrap(); - std::fs::write(dir.join("three.txt"), "alpha three").unwrap(); - - let results = run_rg_search("alpha", None, dir, 2, dir).await?; - assert_eq!(results.len(), 2); - Ok(()) -} - -#[tokio::test] -async fn run_search_handles_no_matches() -> anyhow::Result<()> { - if !rg_available() { - return Ok(()); - } - let temp = tempdir().expect("create temp dir"); - let dir = temp.path(); - std::fs::write(dir.join("one.txt"), "omega").unwrap(); - - let results = run_rg_search("alpha", None, dir, 5, dir).await?; - assert!(results.is_empty()); - Ok(()) -} - -fn rg_available() -> bool { - StdCommand::new("rg") - .arg("--version") - .output() - .map(|output| output.status.success()) - .unwrap_or(false) -} diff --git a/codex-rs/core/src/tools/handlers/read_file_tests.rs b/codex-rs/core/src/tools/handlers/read_file_tests.rs deleted file mode 100644 index 3921a98826..0000000000 --- a/codex-rs/core/src/tools/handlers/read_file_tests.rs +++ /dev/null @@ -1,503 +0,0 @@ -use super::indentation::read_block; -use super::slice::read; -use super::*; -use pretty_assertions::assert_eq; -use tempfile::NamedTempFile; - -#[tokio::test] -async fn reads_requested_range() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "alpha -beta -gamma -" - )?; - - let lines = read(temp.path(), 2, 2).await?; - assert_eq!(lines, vec!["L2: beta".to_string(), "L3: gamma".to_string()]); - Ok(()) -} - -#[tokio::test] -async fn errors_when_offset_exceeds_length() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - writeln!(temp, "only")?; - - let err = read(temp.path(), 3, 1) - .await - .expect_err("offset exceeds length"); - assert_eq!( - err, - FunctionCallError::RespondToModel("offset exceeds file length".to_string()) - ); - Ok(()) -} - -#[tokio::test] -async fn reads_non_utf8_lines() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - temp.as_file_mut().write_all(b"\xff\xfe\nplain\n")?; - - let lines = read(temp.path(), 1, 2).await?; - let expected_first = format!("L1: {}{}", '\u{FFFD}', '\u{FFFD}'); - assert_eq!(lines, vec![expected_first, "L2: plain".to_string()]); - Ok(()) -} - -#[tokio::test] -async fn trims_crlf_endings() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!(temp, "one\r\ntwo\r\n")?; - - let lines = read(temp.path(), 1, 2).await?; - assert_eq!(lines, vec!["L1: one".to_string(), "L2: two".to_string()]); - Ok(()) -} - -#[tokio::test] -async fn respects_limit_even_with_more_lines() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "first -second -third -" - )?; - - let lines = read(temp.path(), 1, 2).await?; - assert_eq!( - lines, - vec!["L1: first".to_string(), "L2: second".to_string()] - ); - Ok(()) -} - -#[tokio::test] -async fn truncates_lines_longer_than_max_length() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - let long_line = "x".repeat(MAX_LINE_LENGTH + 50); - writeln!(temp, "{long_line}")?; - - let lines = read(temp.path(), 1, 1).await?; - let expected = "x".repeat(MAX_LINE_LENGTH); - assert_eq!(lines, vec![format!("L1: {expected}")]); - Ok(()) -} - -#[tokio::test] -async fn indentation_mode_captures_block() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "fn outer() {{ - if cond {{ - inner(); - }} - tail(); -}} -" - )?; - - let options = IndentationArgs { - anchor_line: Some(3), - include_siblings: false, - max_levels: 1, - ..Default::default() - }; - - let lines = read_block(temp.path(), 3, 10, options).await?; - - assert_eq!( - lines, - vec![ - "L2: if cond {".to_string(), - "L3: inner();".to_string(), - "L4: }".to_string() - ] - ); - Ok(()) -} - -#[tokio::test] -async fn indentation_mode_expands_parents() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "mod root {{ - fn outer() {{ - if cond {{ - inner(); - }} - }} -}} -" - )?; - - let mut options = IndentationArgs { - anchor_line: Some(4), - max_levels: 2, - ..Default::default() - }; - - let lines = read_block(temp.path(), 4, 50, options.clone()).await?; - assert_eq!( - lines, - vec![ - "L2: fn outer() {".to_string(), - "L3: if cond {".to_string(), - "L4: inner();".to_string(), - "L5: }".to_string(), - "L6: }".to_string(), - ] - ); - - options.max_levels = 3; - let expanded = read_block(temp.path(), 4, 50, options).await?; - assert_eq!( - expanded, - vec![ - "L1: mod root {".to_string(), - "L2: fn outer() {".to_string(), - "L3: if cond {".to_string(), - "L4: inner();".to_string(), - "L5: }".to_string(), - "L6: }".to_string(), - "L7: }".to_string(), - ] - ); - Ok(()) -} - -#[tokio::test] -async fn indentation_mode_respects_sibling_flag() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "fn wrapper() {{ - if first {{ - do_first(); - }} - if second {{ - do_second(); - }} -}} -" - )?; - - let mut options = IndentationArgs { - anchor_line: Some(3), - include_siblings: false, - max_levels: 1, - ..Default::default() - }; - - let lines = read_block(temp.path(), 3, 50, options.clone()).await?; - assert_eq!( - lines, - vec![ - "L2: if first {".to_string(), - "L3: do_first();".to_string(), - "L4: }".to_string(), - ] - ); - - options.include_siblings = true; - let with_siblings = read_block(temp.path(), 3, 50, options).await?; - assert_eq!( - with_siblings, - vec![ - "L2: if first {".to_string(), - "L3: do_first();".to_string(), - "L4: }".to_string(), - "L5: if second {".to_string(), - "L6: do_second();".to_string(), - "L7: }".to_string(), - ] - ); - Ok(()) -} - -#[tokio::test] -async fn indentation_mode_handles_python_sample() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "class Foo: - def __init__(self, size): - self.size = size - def double(self, value): - if value is None: - return 0 - result = value * self.size - return result -class Bar: - def compute(self): - helper = Foo(2) - return helper.double(5) -" - )?; - - let options = IndentationArgs { - anchor_line: Some(7), - include_siblings: true, - max_levels: 1, - ..Default::default() - }; - - let lines = read_block(temp.path(), 1, 200, options).await?; - assert_eq!( - lines, - vec![ - "L2: def __init__(self, size):".to_string(), - "L3: self.size = size".to_string(), - "L4: def double(self, value):".to_string(), - "L5: if value is None:".to_string(), - "L6: return 0".to_string(), - "L7: result = value * self.size".to_string(), - "L8: return result".to_string(), - ] - ); - Ok(()) -} - -#[tokio::test] -#[ignore] -async fn indentation_mode_handles_javascript_sample() -> anyhow::Result<()> { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "export function makeThing() {{ - const cache = new Map(); - function ensure(key) {{ - if (!cache.has(key)) {{ - cache.set(key, []); - }} - return cache.get(key); - }} - const handlers = {{ - init() {{ - console.log(\"init\"); - }}, - run() {{ - if (Math.random() > 0.5) {{ - return \"heads\"; - }} - return \"tails\"; - }}, - }}; - return {{ cache, handlers }}; -}} -export function other() {{ - return makeThing(); -}} -" - )?; - - let options = IndentationArgs { - anchor_line: Some(15), - max_levels: 1, - ..Default::default() - }; - - let lines = read_block(temp.path(), 15, 200, options).await?; - assert_eq!( - lines, - vec![ - "L10: init() {".to_string(), - "L11: console.log(\"init\");".to_string(), - "L12: },".to_string(), - "L13: run() {".to_string(), - "L14: if (Math.random() > 0.5) {".to_string(), - "L15: return \"heads\";".to_string(), - "L16: }".to_string(), - "L17: return \"tails\";".to_string(), - "L18: },".to_string(), - ] - ); - Ok(()) -} - -fn write_cpp_sample() -> anyhow::Result { - let mut temp = NamedTempFile::new()?; - use std::io::Write as _; - write!( - temp, - "#include -#include - -namespace sample {{ -class Runner {{ -public: - void setup() {{ - if (enabled_) {{ - init(); - }} - }} - - // Run the code - int run() const {{ - switch (mode_) {{ - case Mode::Fast: - return fast(); - case Mode::Slow: - return slow(); - default: - return fallback(); - }} - }} - -private: - bool enabled_ = false; - Mode mode_ = Mode::Fast; - - int fast() const {{ - return 1; - }} -}}; -}} // namespace sample -" - )?; - Ok(temp) -} - -#[tokio::test] -async fn indentation_mode_handles_cpp_sample_shallow() -> anyhow::Result<()> { - let temp = write_cpp_sample()?; - - let options = IndentationArgs { - include_siblings: false, - anchor_line: Some(18), - max_levels: 1, - ..Default::default() - }; - - let lines = read_block(temp.path(), 18, 200, options).await?; - assert_eq!( - lines, - vec![ - "L15: switch (mode_) {".to_string(), - "L16: case Mode::Fast:".to_string(), - "L17: return fast();".to_string(), - "L18: case Mode::Slow:".to_string(), - "L19: return slow();".to_string(), - "L20: default:".to_string(), - "L21: return fallback();".to_string(), - "L22: }".to_string(), - ] - ); - Ok(()) -} - -#[tokio::test] -async fn indentation_mode_handles_cpp_sample() -> anyhow::Result<()> { - let temp = write_cpp_sample()?; - - let options = IndentationArgs { - include_siblings: false, - anchor_line: Some(18), - max_levels: 2, - ..Default::default() - }; - - let lines = read_block(temp.path(), 18, 200, options).await?; - assert_eq!( - lines, - vec![ - "L13: // Run the code".to_string(), - "L14: int run() const {".to_string(), - "L15: switch (mode_) {".to_string(), - "L16: case Mode::Fast:".to_string(), - "L17: return fast();".to_string(), - "L18: case Mode::Slow:".to_string(), - "L19: return slow();".to_string(), - "L20: default:".to_string(), - "L21: return fallback();".to_string(), - "L22: }".to_string(), - "L23: }".to_string(), - ] - ); - Ok(()) -} - -#[tokio::test] -async fn indentation_mode_handles_cpp_sample_no_headers() -> anyhow::Result<()> { - let temp = write_cpp_sample()?; - - let options = IndentationArgs { - include_siblings: false, - include_header: false, - anchor_line: Some(18), - max_levels: 2, - ..Default::default() - }; - - let lines = read_block(temp.path(), 18, 200, options).await?; - assert_eq!( - lines, - vec![ - "L14: int run() const {".to_string(), - "L15: switch (mode_) {".to_string(), - "L16: case Mode::Fast:".to_string(), - "L17: return fast();".to_string(), - "L18: case Mode::Slow:".to_string(), - "L19: return slow();".to_string(), - "L20: default:".to_string(), - "L21: return fallback();".to_string(), - "L22: }".to_string(), - "L23: }".to_string(), - ] - ); - Ok(()) -} - -#[tokio::test] -async fn indentation_mode_handles_cpp_sample_siblings() -> anyhow::Result<()> { - let temp = write_cpp_sample()?; - - let options = IndentationArgs { - include_siblings: true, - include_header: false, - anchor_line: Some(18), - max_levels: 2, - ..Default::default() - }; - - let lines = read_block(temp.path(), 18, 200, options).await?; - assert_eq!( - lines, - vec![ - "L7: void setup() {".to_string(), - "L8: if (enabled_) {".to_string(), - "L9: init();".to_string(), - "L10: }".to_string(), - "L11: }".to_string(), - "L12: ".to_string(), - "L13: // Run the code".to_string(), - "L14: int run() const {".to_string(), - "L15: switch (mode_) {".to_string(), - "L16: case Mode::Fast:".to_string(), - "L17: return fast();".to_string(), - "L18: case Mode::Slow:".to_string(), - "L19: return slow();".to_string(), - "L20: default:".to_string(), - "L21: return fallback();".to_string(), - "L22: }".to_string(), - "L23: }".to_string(), - ] - ); - Ok(()) -} diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index ad3280ebf0..0c91654c83 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -65,6 +65,7 @@ mod models_cache_ttl; mod models_etag_responses; mod openai_file_mcp; mod otel; +mod override_updates; mod pending_input; mod permissions_messages; mod personality; diff --git a/codex-rs/core/tests/suite/override_updates.rs b/codex-rs/core/tests/suite/override_updates.rs index 63d2b6ed43..d0e949947a 100644 --- a/codex-rs/core/tests/suite/override_updates.rs +++ b/codex-rs/core/tests/suite/override_updates.rs @@ -1,25 +1,15 @@ use anyhow::Result; use codex_core::config::Constrained; -use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::COLLABORATION_MODE_CLOSE_TAG; -use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG; -use codex_protocol::protocol::EventMsg; -use codex_protocol::protocol::Op; -use codex_protocol::protocol::RolloutItem; -use codex_protocol::protocol::RolloutLine; -use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; -use codex_protocol::models::ContentItem; -use codex_protocol::models::ResponseItem; +use codex_protocol::protocol::AskForApproval; +use codex_protocol::protocol::EventMsg; +use codex_protocol::protocol::Op; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; -use pretty_assertions::assert_eq; -use std::path::Path; -use std::time::Duration; use tempfile::TempDir; fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMode { @@ -33,77 +23,9 @@ fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMod } } -fn collab_xml(text: &str) -> String { - format!("{COLLABORATION_MODE_OPEN_TAG}{text}{COLLABORATION_MODE_CLOSE_TAG}") -} - -async fn read_rollout_text(path: &Path) -> anyhow::Result { - for _ in 0..50 { - if path.exists() - && let Ok(text) = std::fs::read_to_string(path) - && !text.trim().is_empty() - { - return Ok(text); - } - tokio::time::sleep(Duration::from_millis(20)).await; - } - Ok(std::fs::read_to_string(path)?) -} - -fn rollout_developer_texts(text: &str) -> Vec { - let mut texts = Vec::new(); - for line in text.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - let rollout: RolloutLine = match serde_json::from_str(trimmed) { - Ok(rollout) => rollout, - Err(_) => continue, - }; - if let RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. }) = - rollout.item - && role == "developer" - { - for item in content { - if let ContentItem::InputText { text } = item { - texts.push(text); - } - } - } - } - texts -} - -fn rollout_environment_texts(text: &str) -> Vec { - let mut texts = Vec::new(); - for line in text.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - let rollout: RolloutLine = match serde_json::from_str(trimmed) { - Ok(rollout) => rollout, - Err(_) => continue, - }; - if let RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. }) = - rollout.item - && role == "user" - { - for item in content { - if let ContentItem::InputText { text } = item - && text.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG) - { - texts.push(text); - } - } - } - } - texts -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_without_user_turn_does_not_record_permissions_update() -> Result<()> { +async fn override_turn_context_without_user_turn_does_not_record_permissions_update() -> Result<()> +{ skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -133,22 +55,17 @@ async fn override_turn_context_without_user_turn_does_not_record_permissions_upd wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; let rollout_path = test.codex.rollout_path().expect("rollout path"); - let rollout_text = read_rollout_text(&rollout_path).await?; - let developer_texts = rollout_developer_texts(&rollout_text); - let approval_texts: Vec<&String> = developer_texts - .iter() - .filter(|text| text.contains("`approval_policy`")) - .collect(); assert!( - approval_texts.is_empty(), - "did not expect permissions updates before a new user turn: {approval_texts:?}" + !rollout_path.exists(), + "did not expect a rollout before a new user turn" ); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_without_user_turn_does_not_record_environment_update() -> Result<()> { +async fn override_turn_context_without_user_turn_does_not_record_environment_update() -> Result<()> +{ skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -176,18 +93,17 @@ async fn override_turn_context_without_user_turn_does_not_record_environment_upd wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; let rollout_path = test.codex.rollout_path().expect("rollout path"); - let rollout_text = read_rollout_text(&rollout_path).await?; - let env_texts = rollout_environment_texts(&rollout_text); assert!( - env_texts.is_empty(), - "did not expect environment updates before a new user turn: {env_texts:?}" + !rollout_path.exists(), + "did not expect a rollout before a new user turn" ); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_without_user_turn_does_not_record_collaboration_update() -> Result<()> { +async fn override_turn_context_without_user_turn_does_not_record_collaboration_update() -> Result<()> +{ skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -216,14 +132,10 @@ async fn override_turn_context_without_user_turn_does_not_record_collaboration_u wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; let rollout_path = test.codex.rollout_path().expect("rollout path"); - let rollout_text = read_rollout_text(&rollout_path).await?; - let developer_texts = rollout_developer_texts(&rollout_text); - let collab_text = collab_xml(collab_text); - let collab_count = developer_texts - .iter() - .filter(|text| text.as_str() == collab_text.as_str()) - .count(); - assert_eq!(collab_count, 0); + assert!( + !rollout_path.exists(), + "did not expect a rollout before a new user turn" + ); Ok(()) } diff --git a/codex-rs/exec-server/src/server/jsonrpc.rs b/codex-rs/exec-server/src/server/jsonrpc.rs deleted file mode 100644 index f81abd06eb..0000000000 --- a/codex-rs/exec-server/src/server/jsonrpc.rs +++ /dev/null @@ -1,53 +0,0 @@ -use codex_app_server_protocol::JSONRPCError; -use codex_app_server_protocol::JSONRPCErrorError; -use codex_app_server_protocol::JSONRPCMessage; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::RequestId; -use serde_json::Value; - -pub(crate) fn invalid_request(message: String) -> JSONRPCErrorError { - JSONRPCErrorError { - code: -32600, - data: None, - message, - } -} - -pub(crate) fn invalid_params(message: String) -> JSONRPCErrorError { - JSONRPCErrorError { - code: -32602, - data: None, - message, - } -} - -pub(crate) fn method_not_found(message: String) -> JSONRPCErrorError { - JSONRPCErrorError { - code: -32601, - data: None, - message, - } -} - -pub(crate) fn response_message( - request_id: RequestId, - result: Result, -) -> JSONRPCMessage { - match result { - Ok(result) => JSONRPCMessage::Response(JSONRPCResponse { - id: request_id, - result, - }), - Err(error) => JSONRPCMessage::Error(JSONRPCError { - id: request_id, - error, - }), - } -} - -pub(crate) fn invalid_request_message(reason: String) -> JSONRPCMessage { - JSONRPCMessage::Error(JSONRPCError { - id: RequestId::Integer(-1), - error: invalid_request(reason), - }) -} diff --git a/codex-rs/hooks/src/user_notification.rs b/codex-rs/hooks/src/user_notification.rs deleted file mode 100644 index 97af09a3b9..0000000000 --- a/codex-rs/hooks/src/user_notification.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::process::Stdio; -use std::sync::Arc; - -use serde::Serialize; - -use crate::Hook; -use crate::HookEvent; -use crate::HookPayload; -use crate::HookResult; -use crate::command_from_argv; - -/// Legacy notify payload appended as the final argv argument for backward compatibility. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(tag = "type", rename_all = "kebab-case")] -enum UserNotification { - #[serde(rename_all = "kebab-case")] - AgentTurnComplete { - thread_id: String, - turn_id: String, - cwd: String, - #[serde(skip_serializing_if = "Option::is_none")] - client: Option, - - /// Messages that the user sent to the agent to initiate the turn. - input_messages: Vec, - - /// The last message sent by the assistant in the turn. - last_assistant_message: Option, - }, -} - -pub fn legacy_notify_json(payload: &HookPayload) -> Result { - match &payload.hook_event { - HookEvent::AfterAgent { event } => { - serde_json::to_string(&UserNotification::AgentTurnComplete { - thread_id: event.thread_id.to_string(), - turn_id: event.turn_id.clone(), - cwd: payload.cwd.display().to_string(), - client: payload.client.clone(), - input_messages: event.input_messages.clone(), - last_assistant_message: event.last_assistant_message.clone(), - }) - } - _ => Err(serde_json::Error::io(std::io::Error::other( - "legacy notify payload is only supported for after_agent", - ))), - } -} - -pub fn notify_hook(argv: Vec) -> Hook { - let argv = Arc::new(argv); - Hook { - name: "legacy_notify".to_string(), - func: Arc::new(move |payload: &HookPayload| { - let argv = Arc::clone(&argv); - Box::pin(async move { - let mut command = match command_from_argv(&argv) { - Some(command) => command, - None => return HookResult::Success, - }; - if let Ok(notify_payload) = legacy_notify_json(payload) { - command.arg(notify_payload); - } - - // Backwards-compat: match legacy notify behavior (argv + JSON arg, fire-and-forget). - command - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - - match command.spawn() { - Ok(_) => HookResult::Success, - Err(err) => HookResult::FailedContinue(err.into()), - } - }) - }), - } -} - -#[cfg(test)] -mod tests { - use anyhow::Result; - use codex_protocol::ThreadId; - use codex_utils_absolute_path::test_support::PathBufExt; - use codex_utils_absolute_path::test_support::test_path_buf; - use pretty_assertions::assert_eq; - use serde_json::Value; - use serde_json::json; - - use super::*; - - fn expected_notification_json() -> Value { - let cwd = test_path_buf("/Users/example/project"); - json!({ - "type": "agent-turn-complete", - "thread-id": "b5f6c1c2-1111-2222-3333-444455556666", - "turn-id": "12345", - "cwd": cwd.display().to_string(), - "client": "codex-tui", - "input-messages": ["Rename `foo` to `bar` and update the callsites."], - "last-assistant-message": "Rename complete and verified `cargo build` succeeds.", - }) - } - - #[test] - fn test_user_notification() -> Result<()> { - let notification = UserNotification::AgentTurnComplete { - thread_id: "b5f6c1c2-1111-2222-3333-444455556666".to_string(), - turn_id: "12345".to_string(), - cwd: test_path_buf("/Users/example/project") - .display() - .to_string(), - client: Some("codex-tui".to_string()), - input_messages: vec!["Rename `foo` to `bar` and update the callsites.".to_string()], - last_assistant_message: Some( - "Rename complete and verified `cargo build` succeeds.".to_string(), - ), - }; - let serialized = serde_json::to_string(¬ification)?; - let actual: Value = serde_json::from_str(&serialized)?; - assert_eq!(actual, expected_notification_json()); - Ok(()) - } - - #[test] - fn legacy_notify_json_matches_historical_wire_shape() -> Result<()> { - let payload = HookPayload { - session_id: ThreadId::new(), - cwd: test_path_buf("/Users/example/project").abs(), - client: Some("codex-tui".to_string()), - triggered_at: chrono::Utc::now(), - hook_event: HookEvent::AfterAgent { - event: crate::HookEventAfterAgent { - thread_id: ThreadId::from_string("b5f6c1c2-1111-2222-3333-444455556666") - .expect("valid thread id"), - turn_id: "12345".to_string(), - input_messages: vec![ - "Rename `foo` to `bar` and update the callsites.".to_string(), - ], - last_assistant_message: Some( - "Rename complete and verified `cargo build` succeeds.".to_string(), - ), - }, - }, - }; - - let serialized = legacy_notify_json(&payload)?; - let actual: Value = serde_json::from_str(&serialized)?; - assert_eq!(actual, expected_notification_json()); - - Ok(()) - } -} diff --git a/codex-rs/mcp-server/src/tool_handlers/mod.rs b/codex-rs/mcp-server/src/tool_handlers/mod.rs deleted file mode 100644 index 5863bdc288..0000000000 --- a/codex-rs/mcp-server/src/tool_handlers/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod create_conversation; -pub(crate) mod send_message; diff --git a/codex-rs/plugin/src/plugin_namespace.rs b/codex-rs/plugin/src/plugin_namespace.rs deleted file mode 100644 index 6688ae0469..0000000000 --- a/codex-rs/plugin/src/plugin_namespace.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Resolve plugin namespace from skill file paths by walking ancestors for `plugin.json`. - -use std::fs; -use std::path::Path; - -/// Relative path from a plugin root to its manifest file. -pub const PLUGIN_MANIFEST_PATH: &str = ".codex-plugin/plugin.json"; - -#[derive(serde::Deserialize)] -#[serde(rename_all = "camelCase")] -struct RawPluginManifestName { - #[serde(default)] - name: String, -} - -fn plugin_manifest_name(plugin_root: &Path) -> Option { - let manifest_path = plugin_root.join(PLUGIN_MANIFEST_PATH); - if !manifest_path.is_file() { - return None; - } - let contents = fs::read_to_string(&manifest_path).ok()?; - let RawPluginManifestName { name: raw_name } = serde_json::from_str(&contents).ok()?; - Some( - plugin_root - .file_name() - .and_then(|entry| entry.to_str()) - .filter(|_| raw_name.trim().is_empty()) - .unwrap_or(raw_name.as_str()) - .to_string(), - ) -} - -/// Returns the plugin manifest `name` for the nearest ancestor of `path` that contains a valid -/// plugin manifest (same `name` rules as full manifest loading in codex-core). -pub fn plugin_namespace_for_skill_path(path: &Path) -> Option { - for ancestor in path.ancestors() { - if let Some(name) = plugin_manifest_name(ancestor) { - return Some(name); - } - } - None -} - -#[cfg(test)] -mod tests { - use super::plugin_namespace_for_skill_path; - use std::fs; - use tempfile::tempdir; - - #[test] - fn uses_manifest_name() { - let tmp = tempdir().expect("tempdir"); - let plugin_root = tmp.path().join("plugins/sample"); - let skill_path = plugin_root.join("skills/search/SKILL.md"); - - fs::create_dir_all(skill_path.parent().expect("parent")).expect("mkdir"); - fs::create_dir_all(plugin_root.join(".codex-plugin")).expect("mkdir manifest"); - fs::write( - plugin_root.join(".codex-plugin/plugin.json"), - r#"{"name":"sample"}"#, - ) - .expect("write manifest"); - fs::write(&skill_path, "---\ndescription: search\n---\n").expect("write skill"); - - assert_eq!( - plugin_namespace_for_skill_path(&skill_path), - Some("sample".to_string()) - ); - } -} diff --git a/codex-rs/protocol/Cargo.toml b/codex-rs/protocol/Cargo.toml index 876976f3c5..23a44c48e6 100644 --- a/codex-rs/protocol/Cargo.toml +++ b/codex-rs/protocol/Cargo.toml @@ -21,7 +21,6 @@ codex-network-proxy = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-image = { workspace = true } codex-utils-string = { workspace = true } -codex-utils-template = { workspace = true } encoding_rs = { workspace = true } globset = { workspace = true } icu_decimal = { workspace = true } @@ -60,5 +59,4 @@ tempfile = { workspace = true } [package.metadata.cargo-shear] # Required because: # `icu_provider`: contains a required `sync` feature for `icu_decimal` -# `strum`: as strum_macros in non-nightly builds -ignored = ["icu_provider", "strum"] +ignored = ["icu_provider"] diff --git a/codex-rs/windows-sandbox-rs/Cargo.toml b/codex-rs/windows-sandbox-rs/Cargo.toml index 4a71a952e7..31e563f4a8 100644 --- a/codex-rs/windows-sandbox-rs/Cargo.toml +++ b/codex-rs/windows-sandbox-rs/Cargo.toml @@ -12,11 +12,11 @@ doctest = false [[bin]] name = "codex-windows-sandbox-setup" -path = "src/bin/setup_main.rs" +path = "src/bin/setup_main/main.rs" [[bin]] name = "codex-command-runner" -path = "src/bin/command_runner.rs" +path = "src/bin/command_runner/main.rs" [lints] workspace = true @@ -96,6 +96,3 @@ pretty_assertions = { workspace = true } [build-dependencies] winres = "0.1" - -[package.metadata.cargo-shear] -ignored = ["codex-utils-pty", "tokio"] diff --git a/codex-rs/windows-sandbox-rs/src/acl.rs b/codex-rs/windows-sandbox-rs/src/acl.rs index f351dba190..68a17c9799 100644 --- a/codex-rs/windows-sandbox-rs/src/acl.rs +++ b/codex-rs/windows-sandbox-rs/src/acl.rs @@ -1,39 +1,40 @@ use crate::winutil::to_wide; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use std::ffi::c_void; use std::path::Path; use windows_sys::Win32::Foundation::CloseHandle; -use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::ERROR_SUCCESS; use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; +use windows_sys::Win32::Foundation::LocalFree; +use windows_sys::Win32::Security::ACCESS_ALLOWED_ACE; +use windows_sys::Win32::Security::ACE_HEADER; +use windows_sys::Win32::Security::ACL; +use windows_sys::Win32::Security::ACL_SIZE_INFORMATION; use windows_sys::Win32::Security::AclSizeInformation; +use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W; use windows_sys::Win32::Security::Authorization::GetNamedSecurityInfoW; use windows_sys::Win32::Security::Authorization::GetSecurityInfo; use windows_sys::Win32::Security::Authorization::SetEntriesInAclW; use windows_sys::Win32::Security::Authorization::SetNamedSecurityInfoW; use windows_sys::Win32::Security::Authorization::SetSecurityInfo; -use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W; use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_SID; use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN; use windows_sys::Win32::Security::Authorization::TRUSTEE_W; +use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION; use windows_sys::Win32::Security::EqualSid; +use windows_sys::Win32::Security::GENERIC_MAPPING; use windows_sys::Win32::Security::GetAce; use windows_sys::Win32::Security::GetAclInformation; use windows_sys::Win32::Security::MapGenericMask; -use windows_sys::Win32::Security::ACCESS_ALLOWED_ACE; -use windows_sys::Win32::Security::ACE_HEADER; -use windows_sys::Win32::Security::ACL; -use windows_sys::Win32::Security::ACL_SIZE_INFORMATION; -use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION; -use windows_sys::Win32::Security::GENERIC_MAPPING; use windows_sys::Win32::Storage::FileSystem::CreateFileW; +use windows_sys::Win32::Storage::FileSystem::DELETE; use windows_sys::Win32::Storage::FileSystem::FILE_ALL_ACCESS; use windows_sys::Win32::Storage::FileSystem::FILE_APPEND_DATA; use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL; -use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS; use windows_sys::Win32::Storage::FileSystem::FILE_DELETE_CHILD; +use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ; use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE; @@ -45,7 +46,6 @@ use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA; use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_EA; use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING; use windows_sys::Win32::Storage::FileSystem::READ_CONTROL; -use windows_sys::Win32::Storage::FileSystem::DELETE; const SE_KERNEL_OBJECT: u32 = 6; const INHERIT_ONLY_ACE: u8 = 0x08; const GENERIC_WRITE_MASK: u32 = 0x4000_0000; @@ -259,12 +259,8 @@ pub unsafe fn dacl_has_write_deny_for_sid(p_dacl: *mut ACL, psid: *mut c_void) - false } -const WRITE_ALLOW_MASK: u32 = FILE_GENERIC_READ - | FILE_GENERIC_WRITE - | FILE_GENERIC_EXECUTE - | DELETE - | FILE_DELETE_CHILD; - +const WRITE_ALLOW_MASK: u32 = + FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE | FILE_DELETE_CHILD; unsafe fn ensure_allow_mask_aces_with_inheritance_impl( path: &Path, @@ -275,12 +271,7 @@ unsafe fn ensure_allow_mask_aces_with_inheritance_impl( let (p_dacl, p_sd) = fetch_dacl_handle(path)?; let mut entries: Vec = Vec::new(); for sid in sids { - if dacl_mask_allows( - p_dacl, - &[*sid], - allow_mask, - /*require_all_bits*/ true, - ) { + if dacl_mask_allows(p_dacl, &[*sid], allow_mask, /*require_all_bits*/ true) { continue; } entries.push(EXPLICIT_ACCESS_W { diff --git a/codex-rs/windows-sandbox-rs/src/allow.rs b/codex-rs/windows-sandbox-rs/src/allow.rs index 273dc8c4f2..5b6d0d617a 100644 --- a/codex-rs/windows-sandbox-rs/src/allow.rs +++ b/codex-rs/windows-sandbox-rs/src/allow.rs @@ -117,12 +117,16 @@ mod tests { let paths = compute_allow_paths(&policy, &command_cwd, &command_cwd, &HashMap::new()); - assert!(paths - .allow - .contains(&dunce::canonicalize(&command_cwd).unwrap())); - assert!(paths - .allow - .contains(&dunce::canonicalize(&extra_root).unwrap())); + assert!( + paths + .allow + .contains(&dunce::canonicalize(&command_cwd).unwrap()) + ); + assert!( + paths + .allow + .contains(&dunce::canonicalize(&extra_root).unwrap()) + ); assert!(paths.deny.is_empty(), "no deny paths expected"); } @@ -145,12 +149,16 @@ mod tests { let paths = compute_allow_paths(&policy, &command_cwd, &command_cwd, &env_map); - assert!(paths - .allow - .contains(&dunce::canonicalize(&command_cwd).unwrap())); - assert!(!paths - .allow - .contains(&dunce::canonicalize(&temp_dir).unwrap())); + assert!( + paths + .allow + .contains(&dunce::canonicalize(&command_cwd).unwrap()) + ); + assert!( + !paths + .allow + .contains(&dunce::canonicalize(&temp_dir).unwrap()) + ); assert!(paths.deny.is_empty(), "no deny paths expected"); } @@ -253,6 +261,9 @@ mod tests { let paths = compute_allow_paths(&policy, &command_cwd, &command_cwd, &HashMap::new()); assert_eq!(paths.allow.len(), 1); - assert!(paths.deny.is_empty(), "no deny when protected dirs are absent"); + assert!( + paths.deny.is_empty(), + "no deny when protected dirs are absent" + ); } } diff --git a/codex-rs/windows-sandbox-rs/src/audit.rs b/codex-rs/windows-sandbox-rs/src/audit.rs index 9ee371ce68..c45e3341b7 100644 --- a/codex-rs/windows-sandbox-rs/src/audit.rs +++ b/codex-rs/windows-sandbox-rs/src/audit.rs @@ -2,16 +2,17 @@ use crate::acl::add_deny_write_ace; use crate::acl::path_mask_allows; use crate::cap::cap_sid_file; use crate::cap::load_or_create_cap_sids; -use crate::logging::{debug_log, log_note}; +use crate::logging::debug_log; +use crate::logging::log_note; use crate::path_normalization::canonical_path_key; use crate::policy::SandboxPolicy; use crate::token::convert_string_sid_to_sid; use crate::token::world_sid; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use std::collections::HashSet; -use std::ffi::c_void; use std::ffi::OsStr; +use std::ffi::c_void; use std::path::Path; use std::path::PathBuf; use std::time::Duration; @@ -81,7 +82,12 @@ unsafe fn path_has_world_write_allow(path: &Path) -> Result { let mut world = world_sid()?; let psid_world = world.as_mut_ptr() as *mut c_void; let write_mask = FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES; - path_mask_allows(path, &[psid_world], write_mask, /*require_all_bits*/ false) + path_mask_allows( + path, + &[psid_world], + write_mask, + /*require_all_bits*/ false, + ) } pub fn audit_everyone_writable( @@ -262,9 +268,8 @@ pub fn apply_capability_denies_for_world_writable( (sid, roots) } SandboxPolicy::ReadOnly { .. } => ( - unsafe { convert_string_sid_to_sid(&caps.readonly) }.ok_or_else(|| { - anyhow!("ConvertStringSidToSidW failed for readonly capability") - })?, + unsafe { convert_string_sid_to_sid(&caps.readonly) } + .ok_or_else(|| anyhow!("ConvertStringSidToSidW failed for readonly capability"))?, Vec::new(), ), SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { diff --git a/codex-rs/windows-sandbox-rs/src/bin/command_runner.rs b/codex-rs/windows-sandbox-rs/src/bin/command_runner/main.rs similarity index 80% rename from codex-rs/windows-sandbox-rs/src/bin/command_runner.rs rename to codex-rs/windows-sandbox-rs/src/bin/command_runner/main.rs index db08324908..9fe70c8750 100644 --- a/codex-rs/windows-sandbox-rs/src/bin/command_runner.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/command_runner/main.rs @@ -1,4 +1,4 @@ -#[path = "../elevated/command_runner_win.rs"] +#[cfg(target_os = "windows")] mod win; #[cfg(target_os = "windows")] diff --git a/codex-rs/windows-sandbox-rs/src/elevated/command_runner_win.rs b/codex-rs/windows-sandbox-rs/src/bin/command_runner/win.rs similarity index 96% rename from codex-rs/windows-sandbox-rs/src/elevated/command_runner_win.rs rename to codex-rs/windows-sandbox-rs/src/bin/command_runner/win.rs index 80e67044e8..b161dd57b8 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated/command_runner_win.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/command_runner/win.rs @@ -7,9 +7,10 @@ //! accepts stdin/terminate frames, and emits a final exit frame. The legacy restricted‑token //! path spawns the child directly and does not use this runner. -#![cfg(target_os = "windows")] #![allow(unsafe_op_in_unsafe_fn)] +mod cwd_junction; + use anyhow::Context; use anyhow::Result; use codex_windows_sandbox::ErrorPayload; @@ -40,6 +41,7 @@ use codex_windows_sandbox::read_handle_loop; use codex_windows_sandbox::spawn_process_with_pipes; use codex_windows_sandbox::to_wide; use codex_windows_sandbox::write_frame; +use std::ffi::OsStr; use std::fs::File; use std::os::windows::io::FromRawHandle; use std::path::Path; @@ -48,6 +50,7 @@ use std::ptr; use std::sync::Arc; use std::sync::Mutex as StdMutex; use windows_sys::Win32::Foundation::CloseHandle; +use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND; use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; @@ -66,17 +69,13 @@ use windows_sys::Win32::System::JobObjects::SetInformationJobObject; use windows_sys::Win32::System::Threading::GetExitCodeProcess; use windows_sys::Win32::System::Threading::GetProcessId; use windows_sys::Win32::System::Threading::INFINITE; +use windows_sys::Win32::System::Threading::MUTEX_ALL_ACCESS; +use windows_sys::Win32::System::Threading::OpenMutexW; use windows_sys::Win32::System::Threading::PROCESS_INFORMATION; use windows_sys::Win32::System::Threading::TerminateProcess; use windows_sys::Win32::System::Threading::WaitForSingleObject; -#[path = "cwd_junction.rs"] -mod cwd_junction; - -#[allow(dead_code)] -#[path = "../read_acl_mutex.rs"] -mod read_acl_mutex; - +const READ_ACL_MUTEX_NAME: &str = "Local\\CodexSandboxReadAcl"; const WAIT_TIMEOUT: u32 = 0x0000_0102; struct IpcSpawnedProcess { @@ -196,9 +195,25 @@ fn read_spawn_request(reader: &mut File) -> Result { } } +fn read_acl_mutex_exists() -> Result { + let name = to_wide(OsStr::new(READ_ACL_MUTEX_NAME)); + let handle = unsafe { OpenMutexW(MUTEX_ALL_ACCESS, 0, name.as_ptr()) }; + if handle == 0 { + let err = unsafe { GetLastError() }; + if err == ERROR_FILE_NOT_FOUND { + return Ok(false); + } + return Err(anyhow::anyhow!("OpenMutexW failed: {err}")); + } + unsafe { + CloseHandle(handle); + } + Ok(true) +} + /// Pick an effective CWD, using a junction if the ACL helper is active. fn effective_cwd(req_cwd: &Path, log_dir: Option<&Path>) -> PathBuf { - let use_junction = match read_acl_mutex::read_acl_mutex_exists() { + let use_junction = match read_acl_mutex_exists() { Ok(exists) => exists, Err(err) => { log_note( diff --git a/codex-rs/windows-sandbox-rs/src/elevated/cwd_junction.rs b/codex-rs/windows-sandbox-rs/src/bin/command_runner/win/cwd_junction.rs similarity index 99% rename from codex-rs/windows-sandbox-rs/src/elevated/cwd_junction.rs rename to codex-rs/windows-sandbox-rs/src/bin/command_runner/win/cwd_junction.rs index 4765686b34..7ddd391c34 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated/cwd_junction.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/command_runner/win/cwd_junction.rs @@ -1,5 +1,3 @@ -#![cfg(target_os = "windows")] - use codex_windows_sandbox::log_note; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; diff --git a/codex-rs/windows-sandbox-rs/src/bin/setup_main.rs b/codex-rs/windows-sandbox-rs/src/bin/setup_main/main.rs similarity index 85% rename from codex-rs/windows-sandbox-rs/src/bin/setup_main.rs rename to codex-rs/windows-sandbox-rs/src/bin/setup_main/main.rs index c3bc5724f3..262460775e 100644 --- a/codex-rs/windows-sandbox-rs/src/bin/setup_main.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/setup_main/main.rs @@ -1,4 +1,4 @@ -#[path = "../setup_main_win.rs"] +#[cfg(target_os = "windows")] mod win; #[cfg(target_os = "windows")] diff --git a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win.rs similarity index 99% rename from codex-rs/windows-sandbox-rs/src/setup_main_win.rs rename to codex-rs/windows-sandbox-rs/src/bin/setup_main/win.rs index 5df1e37a07..549eb2426d 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_main_win.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win.rs @@ -1,6 +1,5 @@ -#![cfg(target_os = "windows")] - mod firewall; +mod read_acl_mutex; use anyhow::Context; use anyhow::Result; @@ -67,9 +66,7 @@ use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE; const DENY_ACCESS: i32 = 3; -mod read_acl_mutex; mod sandbox_users; -#[path = "setup_runtime_bin.rs"] mod setup_runtime_bin; use read_acl_mutex::acquire_read_acl_mutex; use read_acl_mutex::read_acl_mutex_exists; diff --git a/codex-rs/windows-sandbox-rs/src/firewall.rs b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win/firewall.rs similarity index 99% rename from codex-rs/windows-sandbox-rs/src/firewall.rs rename to codex-rs/windows-sandbox-rs/src/bin/setup_main/win/firewall.rs index b5dfb2ef49..caa1780437 100644 --- a/codex-rs/windows-sandbox-rs/src/firewall.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win/firewall.rs @@ -1,5 +1,3 @@ -#![cfg(target_os = "windows")] - use anyhow::Result; use std::fs::File; use std::io::Write; diff --git a/codex-rs/windows-sandbox-rs/src/read_acl_mutex.rs b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win/read_acl_mutex.rs similarity index 89% rename from codex-rs/windows-sandbox-rs/src/read_acl_mutex.rs rename to codex-rs/windows-sandbox-rs/src/bin/setup_main/win/read_acl_mutex.rs index d77129467a..a96b6a5024 100644 --- a/codex-rs/windows-sandbox-rs/src/read_acl_mutex.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win/read_acl_mutex.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use codex_windows_sandbox::to_wide; use std::ffi::OsStr; use windows_sys::Win32::Foundation::CloseHandle; use windows_sys::Win32::Foundation::ERROR_ALREADY_EXISTS; @@ -10,11 +11,9 @@ use windows_sys::Win32::System::Threading::MUTEX_ALL_ACCESS; use windows_sys::Win32::System::Threading::OpenMutexW; use windows_sys::Win32::System::Threading::ReleaseMutex; -use super::to_wide; - const READ_ACL_MUTEX_NAME: &str = "Local\\CodexSandboxReadAcl"; -pub struct ReadAclMutexGuard { +pub(super) struct ReadAclMutexGuard { handle: HANDLE, } @@ -27,7 +26,7 @@ impl Drop for ReadAclMutexGuard { } } -pub fn read_acl_mutex_exists() -> Result { +pub(super) fn read_acl_mutex_exists() -> Result { let name = to_wide(OsStr::new(READ_ACL_MUTEX_NAME)); let handle = unsafe { OpenMutexW(MUTEX_ALL_ACCESS, 0, name.as_ptr()) }; if handle == 0 { @@ -43,7 +42,7 @@ pub fn read_acl_mutex_exists() -> Result { Ok(true) } -pub fn acquire_read_acl_mutex() -> Result> { +pub(super) fn acquire_read_acl_mutex() -> Result> { let name = to_wide(OsStr::new(READ_ACL_MUTEX_NAME)); let handle = unsafe { CreateMutexW(std::ptr::null_mut(), 1, name.as_ptr()) }; if handle == 0 { diff --git a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win/sandbox_users.rs similarity index 99% rename from codex-rs/windows-sandbox-rs/src/sandbox_users.rs rename to codex-rs/windows-sandbox-rs/src/bin/setup_main/win/sandbox_users.rs index 13460b9444..de76b2413b 100644 --- a/codex-rs/windows-sandbox-rs/src/sandbox_users.rs +++ b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win/sandbox_users.rs @@ -1,5 +1,3 @@ -#![cfg(target_os = "windows")] - use anyhow::Result; use base64::Engine; use base64::engine::general_purpose::STANDARD as BASE64; diff --git a/codex-rs/windows-sandbox-rs/src/setup_runtime_bin.rs b/codex-rs/windows-sandbox-rs/src/bin/setup_main/win/setup_runtime_bin.rs similarity index 100% rename from codex-rs/windows-sandbox-rs/src/setup_runtime_bin.rs rename to codex-rs/windows-sandbox-rs/src/bin/setup_main/win/setup_runtime_bin.rs diff --git a/codex-rs/windows-sandbox-rs/src/cap.rs b/codex-rs/windows-sandbox-rs/src/cap.rs index 79f3b2f266..85f7e2c9d6 100644 --- a/codex-rs/windows-sandbox-rs/src/cap.rs +++ b/codex-rs/windows-sandbox-rs/src/cap.rs @@ -1,15 +1,15 @@ +use crate::path_normalization::canonical_path_key; use anyhow::Context; use anyhow::Result; -use rand::rngs::SmallRng; use rand::RngCore; use rand::SeedableRng; +use rand::rngs::SmallRng; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; use std::fs; use std::path::Path; use std::path::PathBuf; -use crate::path_normalization::canonical_path_key; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct CapSids { @@ -106,7 +106,12 @@ mod tests { std::fs::create_dir_all(&workspace).expect("create workspace root"); let canonical = dunce::canonicalize(&workspace).expect("canonical workspace root"); - let alt_spelling = PathBuf::from(canonical.to_string_lossy().replace('\\', "/").to_ascii_uppercase()); + let alt_spelling = PathBuf::from( + canonical + .to_string_lossy() + .replace('\\', "/") + .to_ascii_uppercase(), + ); let first_sid = workspace_cap_sid_for_cwd(&codex_home, canonical.as_path()).expect("first sid"); diff --git a/codex-rs/windows-sandbox-rs/src/desktop.rs b/codex-rs/windows-sandbox-rs/src/desktop.rs index d2aa129b92..918278828d 100644 --- a/codex-rs/windows-sandbox-rs/src/desktop.rs +++ b/codex-rs/windows-sandbox-rs/src/desktop.rs @@ -7,13 +7,13 @@ use anyhow::Result; use rand::Rng; use rand::SeedableRng; use rand::rngs::SmallRng; -use std::path::Path; use std::ffi::c_void; +use std::path::Path; use std::ptr; use windows_sys::Win32::Foundation::CloseHandle; +use windows_sys::Win32::Foundation::ERROR_SUCCESS; use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::HLOCAL; -use windows_sys::Win32::Foundation::ERROR_SUCCESS; use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W; use windows_sys::Win32::Security::Authorization::GRANT_ACCESS; @@ -25,16 +25,16 @@ use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN; use windows_sys::Win32::Security::Authorization::TRUSTEE_W; use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION; use windows_sys::Win32::System::StationsAndDesktops::CloseDesktop; -use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_CREATEMENU; use windows_sys::Win32::System::StationsAndDesktops::CreateDesktopW; +use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_CREATEMENU; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_CREATEWINDOW; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_DELETE; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_ENUMERATE; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_HOOKCONTROL; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_JOURNALPLAYBACK; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_JOURNALRECORD; -use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_READOBJECTS; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_READ_CONTROL; +use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_READOBJECTS; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_SWITCHDESKTOP; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_WRITE_DAC; use windows_sys::Win32::System::StationsAndDesktops::DESKTOP_WRITE_OWNER; diff --git a/codex-rs/windows-sandbox-rs/src/dpapi.rs b/codex-rs/windows-sandbox-rs/src/dpapi.rs index c4dcc79fa6..3a90704127 100644 --- a/codex-rs/windows-sandbox-rs/src/dpapi.rs +++ b/codex-rs/windows-sandbox-rs/src/dpapi.rs @@ -1,13 +1,13 @@ -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use windows_sys::Win32::Foundation::GetLastError; -use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::HLOCAL; -use windows_sys::Win32::Security::Cryptography::CryptProtectData; -use windows_sys::Win32::Security::Cryptography::CryptUnprotectData; +use windows_sys::Win32::Foundation::LocalFree; +use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB; use windows_sys::Win32::Security::Cryptography::CRYPTPROTECT_LOCAL_MACHINE; use windows_sys::Win32::Security::Cryptography::CRYPTPROTECT_UI_FORBIDDEN; -use windows_sys::Win32::Security::Cryptography::CRYPT_INTEGER_BLOB; +use windows_sys::Win32::Security::Cryptography::CryptProtectData; +use windows_sys::Win32::Security::Cryptography::CryptUnprotectData; fn make_blob(data: &[u8]) -> CRYPT_INTEGER_BLOB { CRYPT_INTEGER_BLOB { diff --git a/codex-rs/windows-sandbox-rs/src/elevated/mod.rs b/codex-rs/windows-sandbox-rs/src/elevated/mod.rs new file mode 100644 index 0000000000..3c8084bb62 --- /dev/null +++ b/codex-rs/windows-sandbox-rs/src/elevated/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod ipc_framed; +pub(crate) mod runner_client; +pub(crate) mod runner_pipe; diff --git a/codex-rs/windows-sandbox-rs/src/env.rs b/codex-rs/windows-sandbox-rs/src/env.rs index 93275e1abd..bdf7a76ab2 100644 --- a/codex-rs/windows-sandbox-rs/src/env.rs +++ b/codex-rs/windows-sandbox-rs/src/env.rs @@ -1,10 +1,13 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; +use anyhow::anyhow; use dirs_next::home_dir; use std::collections::HashMap; use std::env; -use std::fs::{self, File}; +use std::fs::File; +use std::fs::{self}; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::Path; +use std::path::PathBuf; pub fn normalize_null_device_env(env_map: &mut HashMap) { let keys: Vec = env_map.keys().cloned().collect(); diff --git a/codex-rs/windows-sandbox-rs/src/helper_materialization.rs b/codex-rs/windows-sandbox-rs/src/helper_materialization.rs index b7d00e1dd9..8f0b5cb83b 100644 --- a/codex-rs/windows-sandbox-rs/src/helper_materialization.rs +++ b/codex-rs/windows-sandbox-rs/src/helper_materialization.rs @@ -1,6 +1,6 @@ -use anyhow::anyhow; use anyhow::Context; use anyhow::Result; +use anyhow::anyhow; use std::collections::HashMap; use std::fs; use std::io::Write; @@ -89,10 +89,7 @@ pub(crate) fn resolve_helper_for_launch( } } -pub fn resolve_current_exe_for_launch( - codex_home: &Path, - fallback_executable: &str, -) -> PathBuf { +pub fn resolve_current_exe_for_launch(codex_home: &Path, fallback_executable: &str) -> PathBuf { let source = match std::env::current_exe() { Ok(path) => path, Err(_) => return PathBuf::from(fallback_executable), @@ -242,11 +239,7 @@ fn dev_build_suffix(source: &Path) -> Result { let duration = modified .duration_since(UNIX_EPOCH) .with_context(|| format!("convert helper source mtime {}", source.display()))?; - Ok(format!( - "{}-{:x}", - metadata.len(), - duration.as_secs(), - )) + Ok(format!("{}-{:x}", metadata.len(), duration.as_secs(),)) } fn copy_from_source_if_needed(source: &Path, destination: &Path) -> Result { @@ -254,9 +247,12 @@ fn copy_from_source_if_needed(source: &Path, destination: &Path) -> Result Result { Ok(meta) => meta, Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false), Err(err) => { - return Err(err) - .with_context(|| format!("read helper destination metadata {}", destination.display())); + return Err(err).with_context(|| { + format!("read helper destination metadata {}", destination.display()) + }); } }; @@ -348,16 +345,16 @@ fn destination_is_fresh(source: &Path, destination: &Path) -> Result { #[cfg(test)] mod tests { - use super::copy_from_source_if_needed; use super::CopyOutcome; - use super::dev_build_suffix; + use super::DEV_BUILD_VERSION_SENTINEL; + use super::HelperExecutable; + use super::RESOURCES_DIRNAME; + use super::copy_from_source_if_needed; use super::destination_is_fresh; + use super::dev_build_suffix; use super::helper_bin_dir; use super::helper_version_suffix; use super::materialized_file_name; - use super::HelperExecutable; - use super::DEV_BUILD_VERSION_SENTINEL; - use super::RESOURCES_DIRNAME; use super::source_path_for_exe; use pretty_assertions::assert_eq; use std::fs; @@ -376,7 +373,10 @@ mod tests { let outcome = copy_from_source_if_needed(&source, &destination).expect("copy helper"); assert_eq!(CopyOutcome::ReCopied, outcome); - assert_eq!(b"runner-v1".as_slice(), fs::read(&destination).expect("read destination")); + assert_eq!( + b"runner-v1".as_slice(), + fs::read(&destination).expect("read destination") + ); } #[test] @@ -403,11 +403,13 @@ mod tests { fs::write(&source, b"runner-v1").expect("write source"); copy_from_source_if_needed(&source, &destination).expect("initial copy"); - let outcome = - copy_from_source_if_needed(&source, &destination).expect("revalidate helper"); + let outcome = copy_from_source_if_needed(&source, &destination).expect("revalidate helper"); assert_eq!(CopyOutcome::Reused, outcome); - assert_eq!(b"runner-v1".as_slice(), fs::read(&destination).expect("read destination")); + assert_eq!( + b"runner-v1".as_slice(), + fs::read(&destination).expect("read destination") + ); } #[test] @@ -429,8 +431,10 @@ mod tests { let runner_source = source_dir.join("codex-command-runner.exe"); fs::write(&runner_source, b"runner").expect("runner"); let runner_suffix = helper_version_suffix(&runner_source).expect("runner suffix"); - let runner_destination = helper_bin_dir(&codex_home) - .join(materialized_file_name(HelperExecutable::CommandRunner, &runner_suffix)); + let runner_destination = helper_bin_dir(&codex_home).join(materialized_file_name( + HelperExecutable::CommandRunner, + &runner_suffix, + )); let runner_outcome = copy_from_source_if_needed(&runner_source, &runner_destination).expect("runner copy"); @@ -453,8 +457,8 @@ mod tests { fs::write(&exe, b"codex").expect("write exe"); fs::write(&helper, b"runner").expect("write helper"); - let resolved = - source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe").expect("helper path"); + let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe") + .expect("helper path"); assert_eq!(resolved, helper); } @@ -472,8 +476,8 @@ mod tests { fs::write(&sibling_helper, b"sibling runner").expect("write sibling helper"); fs::write(&resource_helper, b"resource runner").expect("write resource helper"); - let resolved = - source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe").expect("helper path"); + let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe") + .expect("helper path"); assert_eq!(resolved, sibling_helper); } diff --git a/codex-rs/windows-sandbox-rs/src/hide_users.rs b/codex-rs/windows-sandbox-rs/src/hide_users.rs index 10964d1c13..b919440022 100644 --- a/codex-rs/windows-sandbox-rs/src/hide_users.rs +++ b/codex-rs/windows-sandbox-rs/src/hide_users.rs @@ -1,5 +1,3 @@ -#![cfg(target_os = "windows")] - use crate::logging::log_note; use crate::winutil::format_last_error; use crate::winutil::to_wide; @@ -8,19 +6,19 @@ use std::ffi::OsStr; use std::path::Path; use std::path::PathBuf; use windows_sys::Win32::Foundation::GetLastError; -use windows_sys::Win32::Storage::FileSystem::GetFileAttributesW; -use windows_sys::Win32::Storage::FileSystem::SetFileAttributesW; use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_HIDDEN; use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_SYSTEM; +use windows_sys::Win32::Storage::FileSystem::GetFileAttributesW; use windows_sys::Win32::Storage::FileSystem::INVALID_FILE_ATTRIBUTES; -use windows_sys::Win32::System::Registry::RegCloseKey; -use windows_sys::Win32::System::Registry::RegCreateKeyExW; -use windows_sys::Win32::System::Registry::RegSetValueExW; +use windows_sys::Win32::Storage::FileSystem::SetFileAttributesW; use windows_sys::Win32::System::Registry::HKEY; use windows_sys::Win32::System::Registry::HKEY_LOCAL_MACHINE; use windows_sys::Win32::System::Registry::KEY_WRITE; use windows_sys::Win32::System::Registry::REG_DWORD; use windows_sys::Win32::System::Registry::REG_OPTION_NON_VOLATILE; +use windows_sys::Win32::System::Registry::RegCloseKey; +use windows_sys::Win32::System::Registry::RegCreateKeyExW; +use windows_sys::Win32::System::Registry::RegSetValueExW; const USERLIST_KEY_PATH: &str = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"; diff --git a/codex-rs/windows-sandbox-rs/src/identity.rs b/codex-rs/windows-sandbox-rs/src/identity.rs index 84e72341e2..6e2d392b3a 100644 --- a/codex-rs/windows-sandbox-rs/src/identity.rs +++ b/codex-rs/windows-sandbox-rs/src/identity.rs @@ -1,6 +1,10 @@ use crate::dpapi; use crate::logging::debug_log; use crate::policy::SandboxPolicy; +use crate::setup::SandboxNetworkIdentity; +use crate::setup::SandboxUserRecord; +use crate::setup::SandboxUsersFile; +use crate::setup::SetupMarker; use crate::setup::gather_read_roots; use crate::setup::gather_write_roots; use crate::setup::offline_proxy_settings_from_env; @@ -8,15 +12,11 @@ use crate::setup::run_elevated_setup; use crate::setup::run_setup_refresh_with_overrides; use crate::setup::sandbox_users_path; use crate::setup::setup_marker_path; -use crate::setup::SandboxNetworkIdentity; -use crate::setup::SandboxUserRecord; -use crate::setup::SandboxUsersFile; -use crate::setup::SetupMarker; -use anyhow::anyhow; use anyhow::Context; use anyhow::Result; -use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; +use anyhow::anyhow; use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use std::collections::HashMap; use std::fs; use std::path::Path; @@ -150,8 +150,7 @@ pub fn require_logon_sandbox_creds( .map(<[PathBuf]>::to_vec) .unwrap_or_else(|| gather_write_roots(policy, policy_cwd, command_cwd, env_map)); let network_identity = SandboxNetworkIdentity::from_policy(policy, proxy_enforced); - let desired_offline_proxy_settings = - offline_proxy_settings_from_env(env_map, network_identity); + let desired_offline_proxy_settings = offline_proxy_settings_from_env(env_map, network_identity); // NOTE: Do not add CODEX_HOME/.sandbox to `needed_write`; it must remain non-writable by the // restricted capability token. The setup helper's `lock_sandbox_dir` is responsible for // granting the sandbox group access to this directory without granting the capability SID. @@ -167,8 +166,9 @@ pub fn require_logon_sandbox_creds( } else { let selected = select_identity(network_identity, codex_home)?; if selected.is_none() { - setup_reason = - Some("sandbox users missing or incompatible with marker version".to_string()); + setup_reason = Some( + "sandbox users missing or incompatible with marker version".to_string(), + ); } selected } diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index 522f8926d5..6d1d8b2135 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -5,75 +5,70 @@ #[cfg(any(target_os = "windows", test))] mod ssh_config_dependencies; -macro_rules! windows_modules { - ($($name:ident),+ $(,)?) => { - $(#[cfg(target_os = "windows")] mod $name;)+ - }; -} - -windows_modules!( - acl, - allow, - audit, - cap, - desktop, - dpapi, - env, - helper_materialization, - hide_users, - identity, - logging, - path_normalization, - policy, - process, - token, - wfp, - wfp_setup, - winutil, - workspace_acl -); +#[cfg(target_os = "windows")] +mod acl; +#[cfg(target_os = "windows")] +mod allow; +#[cfg(target_os = "windows")] +mod audit; +#[cfg(target_os = "windows")] +mod cap; +#[cfg(target_os = "windows")] +mod desktop; +#[cfg(target_os = "windows")] +mod dpapi; +#[cfg(target_os = "windows")] +mod elevated; +#[cfg(target_os = "windows")] +mod elevated_impl; +#[cfg(target_os = "windows")] +mod env; +#[cfg(target_os = "windows")] +mod helper_materialization; +#[cfg(target_os = "windows")] +mod hide_users; +#[cfg(target_os = "windows")] +mod identity; +#[cfg(target_os = "windows")] +mod logging; +#[cfg(target_os = "windows")] +mod path_normalization; +#[cfg(target_os = "windows")] +mod policy; +#[cfg(target_os = "windows")] +mod proc_thread_attr; +#[cfg(target_os = "windows")] +mod process; +#[cfg(target_os = "windows")] +mod sandbox_utils; +#[cfg(target_os = "windows")] +mod setup; +#[cfg(target_os = "windows")] +mod setup_error; +#[cfg(target_os = "windows")] +mod spawn_prep; +#[cfg(target_os = "windows")] +mod token; +#[cfg(target_os = "windows")] +mod unified_exec; +#[cfg(target_os = "windows")] +mod wfp; +#[cfg(target_os = "windows")] +mod wfp_setup; +#[cfg(target_os = "windows")] +mod winutil; +#[cfg(target_os = "windows")] +mod workspace_acl; #[cfg(target_os = "windows")] -#[path = "conpty/mod.rs"] mod conpty; #[cfg(target_os = "windows")] -#[path = "proc_thread_attr.rs"] -mod proc_thread_attr; - +pub(crate) use elevated::ipc_framed; #[cfg(target_os = "windows")] -#[path = "elevated/ipc_framed.rs"] -pub(crate) mod ipc_framed; - +pub(crate) use elevated::runner_client; #[cfg(target_os = "windows")] -#[path = "setup_orchestrator.rs"] -mod setup; - -#[cfg(target_os = "windows")] -mod elevated_impl; - -#[cfg(target_os = "windows")] -#[path = "elevated/runner_pipe.rs"] -mod runner_pipe; - -#[cfg(target_os = "windows")] -#[path = "elevated/runner_client.rs"] -mod runner_client; - -#[cfg(target_os = "windows")] -mod setup_error; - -#[cfg(target_os = "windows")] -#[path = "sandbox_utils.rs"] -mod sandbox_utils; - -#[cfg(target_os = "windows")] -#[path = "spawn_prep.rs"] -mod spawn_prep; - -#[cfg(target_os = "windows")] -#[path = "unified_exec/session.rs"] -mod session; +pub(crate) use elevated::runner_pipe; #[cfg(target_os = "windows")] pub use acl::add_deny_write_ace; @@ -169,10 +164,6 @@ pub use process::read_handle_loop; #[cfg(target_os = "windows")] pub use process::spawn_process_with_pipes; #[cfg(target_os = "windows")] -pub use session::spawn_windows_sandbox_session_elevated; -#[cfg(target_os = "windows")] -pub use session::spawn_windows_sandbox_session_legacy; -#[cfg(target_os = "windows")] pub use setup::SETUP_VERSION; #[cfg(target_os = "windows")] pub use setup::SandboxSetupRequest; @@ -222,6 +213,10 @@ pub use token::create_workspace_write_token_with_caps_from; #[cfg(target_os = "windows")] pub use token::get_current_token_for_restriction; #[cfg(target_os = "windows")] +pub use unified_exec::spawn_windows_sandbox_session_elevated; +#[cfg(target_os = "windows")] +pub use unified_exec::spawn_windows_sandbox_session_legacy; +#[cfg(target_os = "windows")] pub use wfp::install_wfp_filters_for_account; #[cfg(target_os = "windows")] pub use wfp_setup::install_wfp_filters; diff --git a/codex-rs/windows-sandbox-rs/src/path_normalization.rs b/codex-rs/windows-sandbox-rs/src/path_normalization.rs index fe6a932306..1735b745a2 100644 --- a/codex-rs/windows-sandbox-rs/src/path_normalization.rs +++ b/codex-rs/windows-sandbox-rs/src/path_normalization.rs @@ -23,6 +23,9 @@ mod tests { let windows_style = Path::new(r"C:\Users\Dev\Repo"); let slash_style = Path::new("c:/users/dev/repo"); - assert_eq!(canonical_path_key(windows_style), canonical_path_key(slash_style)); + assert_eq!( + canonical_path_key(windows_style), + canonical_path_key(slash_style) + ); } } diff --git a/codex-rs/windows-sandbox-rs/src/policy.rs b/codex-rs/windows-sandbox-rs/src/policy.rs index 3cee37cdf7..cfaa9689ee 100644 --- a/codex-rs/windows-sandbox-rs/src/policy.rs +++ b/codex-rs/windows-sandbox-rs/src/policy.rs @@ -5,9 +5,9 @@ pub fn parse_policy(value: &str) -> Result { match value { "read-only" => Ok(SandboxPolicy::new_read_only_policy()), "workspace-write" => Ok(SandboxPolicy::new_workspace_write_policy()), - "danger-full-access" | "external-sandbox" => anyhow::bail!( - "DangerFullAccess and ExternalSandbox are not supported for sandboxing" - ), + "danger-full-access" | "external-sandbox" => { + anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing") + } other => { let parsed: SandboxPolicy = serde_json::from_str(other)?; if matches!( @@ -31,23 +31,24 @@ mod tests { #[test] fn rejects_external_sandbox_preset() { let err = parse_policy("external-sandbox").unwrap_err(); - assert!(err - .to_string() - .contains("DangerFullAccess and ExternalSandbox are not supported")); + assert!( + err.to_string() + .contains("DangerFullAccess and ExternalSandbox are not supported") + ); } #[test] fn rejects_external_sandbox_json() { - let payload = serde_json::to_string( - &codex_protocol::protocol::SandboxPolicy::ExternalSandbox { + let payload = + serde_json::to_string(&codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access: codex_protocol::protocol::NetworkAccess::Enabled, - }, - ) - .unwrap(); + }) + .unwrap(); let err = parse_policy(&payload).unwrap_err(); - assert!(err - .to_string() - .contains("DangerFullAccess and ExternalSandbox are not supported")); + assert!( + err.to_string() + .contains("DangerFullAccess and ExternalSandbox are not supported") + ); } #[test] diff --git a/codex-rs/windows-sandbox-rs/src/process.rs b/codex-rs/windows-sandbox-rs/src/process.rs index 1571bb8ccf..0899d34c2f 100644 --- a/codex-rs/windows-sandbox-rs/src/process.rs +++ b/codex-rs/windows-sandbox-rs/src/process.rs @@ -4,26 +4,26 @@ use crate::proc_thread_attr::ProcThreadAttributeList; use crate::winutil::argv_to_command_line; use crate::winutil::format_last_error; use crate::winutil::to_wide; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use std::collections::HashMap; use std::ffi::c_void; use std::path::Path; use std::ptr; -use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::CloseHandle; -use windows_sys::Win32::Foundation::SetHandleInformation; +use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT; use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; +use windows_sys::Win32::Foundation::SetHandleInformation; use windows_sys::Win32::Storage::FileSystem::ReadFile; use windows_sys::Win32::System::Console::GetStdHandle; use windows_sys::Win32::System::Console::STD_ERROR_HANDLE; use windows_sys::Win32::System::Console::STD_INPUT_HANDLE; use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE; use windows_sys::Win32::System::Pipes::CreatePipe; -use windows_sys::Win32::System::Threading::CreateProcessAsUserW; use windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT; +use windows_sys::Win32::System::Threading::CreateProcessAsUserW; use windows_sys::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT; use windows_sys::Win32::System::Threading::PROCESS_INFORMATION; use windows_sys::Win32::System::Threading::STARTF_USESTDHANDLES; diff --git a/codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs b/codex-rs/windows-sandbox-rs/src/setup.rs similarity index 100% rename from codex-rs/windows-sandbox-rs/src/setup_orchestrator.rs rename to codex-rs/windows-sandbox-rs/src/setup.rs diff --git a/codex-rs/windows-sandbox-rs/src/token.rs b/codex-rs/windows-sandbox-rs/src/token.rs index 71a4b1dd7c..aabf77469f 100644 --- a/codex-rs/windows-sandbox-rs/src/token.rs +++ b/codex-rs/windows-sandbox-rs/src/token.rs @@ -1,18 +1,18 @@ use crate::winutil::to_wide; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use std::ffi::c_void; use windows_sys::Win32::Foundation::CloseHandle; -use windows_sys::Win32::Foundation::GetLastError; -use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::ERROR_SUCCESS; +use windows_sys::Win32::Foundation::GetLastError; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Foundation::LUID; +use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Security::AdjustTokenPrivileges; -use windows_sys::Win32::Security::Authorization::SetEntriesInAclW; use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W; use windows_sys::Win32::Security::Authorization::GRANT_ACCESS; +use windows_sys::Win32::Security::Authorization::SetEntriesInAclW; use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_SID; use windows_sys::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN; use windows_sys::Win32::Security::Authorization::TRUSTEE_W; @@ -24,9 +24,6 @@ use windows_sys::Win32::Security::GetTokenInformation; use windows_sys::Win32::Security::LookupPrivilegeValueW; use windows_sys::Win32::Security::SetTokenInformation; -use windows_sys::Win32::Security::TokenDefaultDacl; -use windows_sys::Win32::Security::TokenGroups; -use windows_sys::Win32::Security::TokenUser; use windows_sys::Win32::Security::ACL; use windows_sys::Win32::Security::SID_AND_ATTRIBUTES; use windows_sys::Win32::Security::TOKEN_ADJUST_DEFAULT; @@ -37,6 +34,9 @@ use windows_sys::Win32::Security::TOKEN_DUPLICATE; use windows_sys::Win32::Security::TOKEN_PRIVILEGES; use windows_sys::Win32::Security::TOKEN_QUERY; use windows_sys::Win32::Security::TOKEN_USER; +use windows_sys::Win32::Security::TokenDefaultDacl; +use windows_sys::Win32::Security::TokenGroups; +use windows_sys::Win32::Security::TokenUser; use windows_sys::Win32::System::Threading::GetCurrentProcess; const DISABLE_MAX_PRIVILEGE: u32 = 0x01; @@ -136,11 +136,7 @@ pub unsafe fn convert_string_sid_to_sid(s: &str) -> Option<*mut c_void> { } let mut psid: *mut c_void = std::ptr::null_mut(); let ok = unsafe { ConvertStringSidToSidW(to_wide(s).as_ptr(), &mut psid) }; - if ok != 0 { - Some(psid) - } else { - None - } + if ok != 0 { Some(psid) } else { None } } /// # Safety @@ -276,7 +272,10 @@ unsafe fn get_user_sid_bytes(h_token: HANDLE) -> Result> { let token_user: TOKEN_USER = std::ptr::read_unaligned(user_buf.as_ptr() as *const TOKEN_USER); let sid_len = GetLengthSid(token_user.User.Sid); if sid_len == 0 { - return Err(anyhow!("GetLengthSid(TokenUser) failed: {}", GetLastError())); + return Err(anyhow!( + "GetLengthSid(TokenUser) failed: {}", + GetLastError() + )); } let mut user_sid_bytes = vec![0u8; sid_len as usize]; if CopySid( diff --git a/codex-rs/windows-sandbox-rs/src/unified_exec/session.rs b/codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs similarity index 100% rename from codex-rs/windows-sandbox-rs/src/unified_exec/session.rs rename to codex-rs/windows-sandbox-rs/src/unified_exec/mod.rs diff --git a/codex-rs/windows-sandbox-rs/src/wfp.rs b/codex-rs/windows-sandbox-rs/src/wfp.rs index 36d72e6377..9291b6eb99 100644 --- a/codex-rs/windows-sandbox-rs/src/wfp.rs +++ b/codex-rs/windows-sandbox-rs/src/wfp.rs @@ -1,6 +1,3 @@ -#![cfg(target_os = "windows")] - -#[path = "wfp_filter_specs.rs"] mod filter_specs; use crate::to_wide; @@ -9,23 +6,22 @@ use std::ffi::OsStr; use std::mem::zeroed; use std::ptr::null; use std::ptr::null_mut; -use windows_sys::core::GUID; use windows_sys::Win32::Foundation::FWP_E_ALREADY_EXISTS; use windows_sys::Win32::Foundation::FWP_E_FILTER_NOT_FOUND; use windows_sys::Win32::Foundation::FWP_E_NOT_FOUND; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::HLOCAL; use windows_sys::Win32::Foundation::LocalFree; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_ACTRL_MATCH_FILTER; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_ACTION_BLOCK; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_ACTRL_MATCH_FILTER; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_BYTE_BLOB; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_EMPTY; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_MATCH_EQUAL; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_SECURITY_DESCRIPTOR_TYPE; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_UINT16; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_UINT8; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_UINT16; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_VALUE0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_ACTION0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_ACTION0_0; @@ -33,15 +29,15 @@ use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_CONDIT use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_CONDITION_IP_PROTOCOL; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_CONDITION_IP_REMOTE_PORT; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_DISPLAY_DATA0; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER0; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER0_0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER_CONDITION0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER_FLAG_PERSISTENT; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_PROVIDER0; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER0; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER0_0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_PROVIDER_FLAG_PERSISTENT; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_PROVIDER0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_SESSION0; -use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_SUBLAYER0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_SUBLAYER_FLAG_PERSISTENT; +use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_SUBLAYER0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmEngineClose0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmEngineOpen0; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmFilterAdd0; @@ -58,6 +54,7 @@ use windows_sys::Win32::Security::Authorization::GRANT_ACCESS; use windows_sys::Win32::Security::PSECURITY_DESCRIPTOR; use windows_sys::Win32::System::Rpc::RPC_C_AUTHN_DEFAULT; use windows_sys::Win32::System::Threading::INFINITE; +use windows_sys::core::GUID; use filter_specs::ConditionSpec; use filter_specs::FILTER_SPECS; @@ -267,7 +264,11 @@ fn ensure_sublayer(engine: HANDLE) -> Result<()> { } /// Adds one blocking WFP filter from the static filter spec list. -fn add_filter(engine: HANDLE, spec: &FilterSpec, user_condition: &UserMatchCondition) -> Result<()> { +fn add_filter( + engine: HANDLE, + spec: &FilterSpec, + user_condition: &UserMatchCondition, +) -> Result<()> { let filter_name = to_wide(OsStr::new(spec.name)); let filter_description = to_wide(OsStr::new(spec.description)); let mut filter_conditions = build_conditions(spec.conditions, user_condition); @@ -288,7 +289,9 @@ fn add_filter(engine: HANDLE, spec: &FilterSpec, user_condition: &UserMatchCondi filterCondition: filter_conditions.as_mut_ptr(), action: FWPM_ACTION0 { r#type: FWP_ACTION_BLOCK, - Anonymous: FWPM_ACTION0_0 { filterType: zero_guid() }, + Anonymous: FWPM_ACTION0_0 { + filterType: zero_guid(), + }, }, Anonymous: FWPM_FILTER0_0 { rawContext: 0 }, reserved: null_mut(), @@ -396,14 +399,24 @@ mod tests { fn filter_keys_are_unique() { let keys = FILTER_SPECS .iter() - .map(|spec| (spec.key.data1, spec.key.data2, spec.key.data3, spec.key.data4)) + .map(|spec| { + ( + spec.key.data1, + spec.key.data2, + spec.key.data3, + spec.key.data4, + ) + }) .collect::>(); assert_eq!(keys.len(), FILTER_SPECS.len()); } #[test] fn filter_names_are_unique() { - let names = FILTER_SPECS.iter().map(|spec| spec.name).collect::>(); + let names = FILTER_SPECS + .iter() + .map(|spec| spec.name) + .collect::>(); assert_eq!(names.len(), FILTER_SPECS.len()); } } diff --git a/codex-rs/windows-sandbox-rs/src/wfp_filter_specs.rs b/codex-rs/windows-sandbox-rs/src/wfp/filter_specs.rs similarity index 90% rename from codex-rs/windows-sandbox-rs/src/wfp_filter_specs.rs rename to codex-rs/windows-sandbox-rs/src/wfp/filter_specs.rs index 2953200253..d86df4ac1e 100644 --- a/codex-rs/windows-sandbox-rs/src/wfp_filter_specs.rs +++ b/codex-rs/windows-sandbox-rs/src/wfp/filter_specs.rs @@ -1,12 +1,10 @@ -#![cfg(target_os = "windows")] - -use windows_sys::core::GUID; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_LAYER_ALE_AUTH_CONNECT_V4; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_LAYER_ALE_AUTH_CONNECT_V6; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4; use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6; use windows_sys::Win32::Networking::WinSock::IPPROTO_ICMP; use windows_sys::Win32::Networking::WinSock::IPPROTO_ICMPV6; +use windows_sys::core::GUID; #[derive(Clone, Copy)] pub(super) enum ConditionSpec { @@ -30,28 +28,40 @@ pub(super) const FILTER_SPECS: &[FilterSpec] = &[ name: "codex_wfp_icmp_connect_v4", description: "Block sandbox-account ICMP connect v4", layer_key: FWPM_LAYER_ALE_AUTH_CONNECT_V4, - conditions: &[ConditionSpec::User, ConditionSpec::Protocol(IPPROTO_ICMP as u8)], + conditions: &[ + ConditionSpec::User, + ConditionSpec::Protocol(IPPROTO_ICMP as u8), + ], }, FilterSpec { key: GUID::from_u128(0x87498484_45ab_4510_845e_ece8b791b3bc), name: "codex_wfp_icmp_connect_v6", description: "Block sandbox-account ICMP connect v6", layer_key: FWPM_LAYER_ALE_AUTH_CONNECT_V6, - conditions: &[ConditionSpec::User, ConditionSpec::Protocol(IPPROTO_ICMPV6 as u8)], + conditions: &[ + ConditionSpec::User, + ConditionSpec::Protocol(IPPROTO_ICMPV6 as u8), + ], }, FilterSpec { key: GUID::from_u128(0xaf4751de_f874_4a7b_a34d_f0d0f22d1d9b), name: "codex_wfp_icmp_assign_v4", description: "Block sandbox-account ICMP resource assignment v4", layer_key: FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4, - conditions: &[ConditionSpec::User, ConditionSpec::Protocol(IPPROTO_ICMP as u8)], + conditions: &[ + ConditionSpec::User, + ConditionSpec::Protocol(IPPROTO_ICMP as u8), + ], }, FilterSpec { key: GUID::from_u128(0xea10db66_a928_4b2e_a82e_a376a54f93ba), name: "codex_wfp_icmp_assign_v6", description: "Block sandbox-account ICMP resource assignment v6", layer_key: FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V6, - conditions: &[ConditionSpec::User, ConditionSpec::Protocol(IPPROTO_ICMPV6 as u8)], + conditions: &[ + ConditionSpec::User, + ConditionSpec::Protocol(IPPROTO_ICMPV6 as u8), + ], }, // NAME_RESOLUTION_CACHE filters are intentionally omitted because ordinary // static filter shapes returned FWP_E_OUT_OF_BOUNDS during validation. diff --git a/codex-rs/windows-sandbox-rs/src/wfp_setup.rs b/codex-rs/windows-sandbox-rs/src/wfp_setup.rs index 89d29118c2..351d2edd2f 100644 --- a/codex-rs/windows-sandbox-rs/src/wfp_setup.rs +++ b/codex-rs/windows-sandbox-rs/src/wfp_setup.rs @@ -171,11 +171,5 @@ pub fn install_wfp_filters( } }; - emit_wfp_setup_metric_safely( - codex_home, - otel, - offline_username, - &metric, - &mut log, - ); + emit_wfp_setup_metric_safely(codex_home, otel, offline_username, &metric, &mut log); } diff --git a/codex-rs/windows-sandbox-rs/src/winutil.rs b/codex-rs/windows-sandbox-rs/src/winutil.rs index f014e06072..45378b4bb5 100644 --- a/codex-rs/windows-sandbox-rs/src/winutil.rs +++ b/codex-rs/windows-sandbox-rs/src/winutil.rs @@ -3,18 +3,18 @@ use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER; use windows_sys::Win32::Foundation::GetLastError; -use windows_sys::Win32::Foundation::LocalFree; use windows_sys::Win32::Foundation::HLOCAL; +use windows_sys::Win32::Foundation::LocalFree; +use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW; use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW; use windows_sys::Win32::Security::CopySid; use windows_sys::Win32::Security::GetLengthSid; use windows_sys::Win32::Security::LookupAccountNameW; use windows_sys::Win32::Security::SID_NAME_USE; -use windows_sys::Win32::System::Diagnostics::Debug::FormatMessageW; use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_ALLOCATE_BUFFER; use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_FROM_SYSTEM; use windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_IGNORE_INSERTS; -use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW; +use windows_sys::Win32::System::Diagnostics::Debug::FormatMessageW; pub fn to_wide>(s: S) -> Vec { let mut v: Vec = s.as_ref().encode_wide().collect(); @@ -107,7 +107,10 @@ pub fn string_from_sid_bytes(sid: &[u8]) -> Result { let mut str_ptr: *mut u16 = std::ptr::null_mut(); let ok = ConvertSidToStringSidW(sid.as_ptr() as *mut std::ffi::c_void, &mut str_ptr); if ok == 0 || str_ptr.is_null() { - return Err(format!("ConvertSidToStringSidW failed: {}", std::io::Error::last_os_error())); + return Err(format!( + "ConvertSidToStringSidW failed: {}", + std::io::Error::last_os_error() + )); } let mut len = 0; while *str_ptr.add(len) != 0 { @@ -158,7 +161,9 @@ pub fn resolve_sid(name: &str) -> Result> { domain.resize(domain_len as usize, 0); continue; } - return Err(anyhow::anyhow!("LookupAccountNameW failed for {name}: {err}")); + return Err(anyhow::anyhow!( + "LookupAccountNameW failed for {name}: {err}" + )); } }