From f7895258397edd008dee8b33f26f737fde06f7fd Mon Sep 17 00:00:00 2001 From: bellman Date: Thu, 14 May 2026 17:22:05 +0900 Subject: [PATCH] omx(team): auto-checkpoint worker-1 [1] --- .../b035f648d5b549aa836ea01f6727ec62.json | 8 ++ .../b234acb1eb8c486e80544ddc7e13e6d8.json | 9 +++ .../b67e062748f04e10ac5770df9285e4bd.json | 9 +++ .../bb88fd20433840a8b19237e3f306c6e3.json | 9 +++ rust/crates/runtime/src/file_ops.rs | 74 ++++++++++++++++--- 5 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 .port_sessions/b035f648d5b549aa836ea01f6727ec62.json create mode 100644 .port_sessions/b234acb1eb8c486e80544ddc7e13e6d8.json create mode 100644 .port_sessions/b67e062748f04e10ac5770df9285e4bd.json create mode 100644 .port_sessions/bb88fd20433840a8b19237e3f306c6e3.json diff --git a/.port_sessions/b035f648d5b549aa836ea01f6727ec62.json b/.port_sessions/b035f648d5b549aa836ea01f6727ec62.json new file mode 100644 index 00000000..af9e814b --- /dev/null +++ b/.port_sessions/b035f648d5b549aa836ea01f6727ec62.json @@ -0,0 +1,8 @@ +{ + "session_id": "b035f648d5b549aa836ea01f6727ec62", + "messages": [ + "review MCP tool" + ], + "input_tokens": 3, + "output_tokens": 13 +} \ No newline at end of file diff --git a/.port_sessions/b234acb1eb8c486e80544ddc7e13e6d8.json b/.port_sessions/b234acb1eb8c486e80544ddc7e13e6d8.json new file mode 100644 index 00000000..fab4c773 --- /dev/null +++ b/.port_sessions/b234acb1eb8c486e80544ddc7e13e6d8.json @@ -0,0 +1,9 @@ +{ + "session_id": "b234acb1eb8c486e80544ddc7e13e6d8", + "messages": [ + "review MCP tool", + "review MCP tool" + ], + "input_tokens": 6, + "output_tokens": 32 +} \ No newline at end of file diff --git a/.port_sessions/b67e062748f04e10ac5770df9285e4bd.json b/.port_sessions/b67e062748f04e10ac5770df9285e4bd.json new file mode 100644 index 00000000..a79c835b --- /dev/null +++ b/.port_sessions/b67e062748f04e10ac5770df9285e4bd.json @@ -0,0 +1,9 @@ +{ + "session_id": "b67e062748f04e10ac5770df9285e4bd", + "messages": [ + "review MCP tool", + "review MCP tool" + ], + "input_tokens": 6, + "output_tokens": 32 +} \ No newline at end of file diff --git a/.port_sessions/bb88fd20433840a8b19237e3f306c6e3.json b/.port_sessions/bb88fd20433840a8b19237e3f306c6e3.json new file mode 100644 index 00000000..3ce0b57f --- /dev/null +++ b/.port_sessions/bb88fd20433840a8b19237e3f306c6e3.json @@ -0,0 +1,9 @@ +{ + "session_id": "bb88fd20433840a8b19237e3f306c6e3", + "messages": [ + "review MCP tool", + "review MCP tool" + ], + "input_tokens": 6, + "output_tokens": 32 +} \ No newline at end of file diff --git a/rust/crates/runtime/src/file_ops.rs b/rust/crates/runtime/src/file_ops.rs index e78a3ac1..1d1e29c3 100644 --- a/rust/crates/runtime/src/file_ops.rs +++ b/rust/crates/runtime/src/file_ops.rs @@ -307,11 +307,23 @@ pub fn edit_file( /// Expands a glob pattern and returns matching filenames. pub fn glob_search(pattern: &str, path: Option<&str>) -> io::Result { + glob_search_impl(pattern, path, None) +} + +fn glob_search_impl( + pattern: &str, + path: Option<&str>, + workspace_root: Option<&Path>, +) -> io::Result { let started = Instant::now(); let base_dir = path .map(normalize_path) .transpose()? .unwrap_or(std::env::current_dir()?); + let canonical_root = workspace_root.map(canonicalize_workspace_root); + if let Some(root) = canonical_root.as_deref() { + validate_workspace_boundary(&base_dir, root)?; + } let search_pattern = if Path::new(pattern).is_absolute() { pattern.to_owned() } else { @@ -329,6 +341,12 @@ pub fn glob_search(pattern: &str, path: Option<&str>) -> io::Result) -> io::Result) -> io::Result io::Result { + grep_search_impl(input, None) +} + +fn grep_search_impl( + input: &GrepSearchInput, + workspace_root: Option<&Path>, +) -> io::Result { let base_path = input .path .as_deref() .map(normalize_path) .transpose()? .unwrap_or(std::env::current_dir()?); + let canonical_root = workspace_root.map(canonicalize_workspace_root); + if let Some(root) = canonical_root.as_deref() { + validate_workspace_boundary(&base_path, root)?; + } let regex = RegexBuilder::new(&input.pattern) .case_insensitive(input.case_insensitive.unwrap_or(false)) @@ -398,6 +431,10 @@ pub fn grep_search(input: &GrepSearchInput) -> io::Result { let mut total_matches = 0usize; for file_path in collect_search_files(&base_path)? { + if let Some(root) = canonical_root.as_deref() { + let canonical_file = file_path.canonicalize()?; + validate_workspace_boundary(&canonical_file, root)?; + } if !matches_optional_filters(&file_path, glob_filter.as_ref(), file_type) { continue; } @@ -475,6 +512,12 @@ pub fn grep_search(input: &GrepSearchInput) -> io::Result { }) } +fn canonicalize_workspace_root(workspace_root: &Path) -> PathBuf { + workspace_root + .canonicalize() + .unwrap_or_else(|_| workspace_root.to_path_buf()) +} + fn should_skip_glob_dir(entry: &DirEntry) -> bool { entry.file_type().is_dir() && entry @@ -625,9 +668,7 @@ pub fn read_file_in_workspace( workspace_root: &Path, ) -> io::Result { let absolute_path = normalize_path(path)?; - let canonical_root = workspace_root - .canonicalize() - .unwrap_or_else(|_| workspace_root.to_path_buf()); + let canonical_root = canonicalize_workspace_root(workspace_root); validate_workspace_boundary(&absolute_path, &canonical_root)?; read_file(path, offset, limit) } @@ -640,9 +681,7 @@ pub fn write_file_in_workspace( workspace_root: &Path, ) -> io::Result { let absolute_path = normalize_path_allow_missing(path)?; - let canonical_root = workspace_root - .canonicalize() - .unwrap_or_else(|_| workspace_root.to_path_buf()); + let canonical_root = canonicalize_workspace_root(workspace_root); validate_workspace_boundary(&absolute_path, &canonical_root)?; write_file(path, content) } @@ -657,13 +696,30 @@ pub fn edit_file_in_workspace( workspace_root: &Path, ) -> io::Result { let absolute_path = normalize_path(path)?; - let canonical_root = workspace_root - .canonicalize() - .unwrap_or_else(|_| workspace_root.to_path_buf()); + let canonical_root = canonicalize_workspace_root(workspace_root); validate_workspace_boundary(&absolute_path, &canonical_root)?; edit_file(path, old_string, new_string, replace_all) } +/// Expand a glob pattern with workspace boundary enforcement. +#[allow(dead_code)] +pub fn glob_search_in_workspace( + pattern: &str, + path: Option<&str>, + workspace_root: &Path, +) -> io::Result { + glob_search_impl(pattern, path, Some(workspace_root)) +} + +/// Search file contents with workspace boundary enforcement. +#[allow(dead_code)] +pub fn grep_search_in_workspace( + input: &GrepSearchInput, + workspace_root: &Path, +) -> io::Result { + grep_search_impl(input, Some(workspace_root)) +} + /// Check whether a path is a symlink that resolves outside the workspace. #[allow(dead_code)] pub fn is_symlink_escape(path: &Path, workspace_root: &Path) -> io::Result {