aboutsummaryrefslogtreecommitdiff
path: root/src/detect
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-08-20 15:24:17 +0300
committerGitHub <noreply@github.com>2026-08-20 15:24:17 +0300
commit9d7b6c24c4d251a62a861f37c2c394078e083ca8 (patch)
treec3c35d9c332e4c35e4b8dbc49565b9c879af4274 /src/detect
parentffc4e263168f9e81d5bbc14db4b16ca9818d684a (diff)
fix(windows): detect cursor bundled node process (#3034)
* fix(windows): detect cursor bundled node process refs #3032 * docs: clarify cursor detection scope * fix(windows): constrain cursor runtime detection refs #3032 --------- Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/detect')
-rw-r--r--src/detect/mod.rs90
1 files changed, 89 insertions, 1 deletions
diff --git a/src/detect/mod.rs b/src/detect/mod.rs
index f78567f6..def440dc 100644
--- a/src/detect/mod.rs
+++ b/src/detect/mod.rs
@@ -378,7 +378,9 @@ fn wrapped_agent_name_from_runtime_argv(runtime: &str, argv: Option<&[String]>)
let runtime_name = normalized_agent_lookup_name(path_basename(runtime));
match runtime_name.as_str() {
- "node" | "bun" => script_arg_agent_name(argv, &["-e", "--eval", "-p", "--print"], &[]),
+ "node" => cursor_agent_name_from_bundled_node_argv(argv)
+ .or_else(|| script_arg_agent_name(argv, &["-e", "--eval", "-p", "--print"], &[])),
+ "bun" => script_arg_agent_name(argv, &["-e", "--eval", "-p", "--print"], &[]),
name if is_python_runtime(name) => script_arg_agent_name(argv, &["-c"], &["-m"]),
"sh" | "bash" | "zsh" | "fish" => script_arg_agent_name(argv, &["-c"], &[]),
"cmd" => windows_cmd_arg_agent_name(argv),
@@ -388,6 +390,36 @@ fn wrapped_agent_name_from_runtime_argv(runtime: &str, argv: Option<&[String]>)
}
}
+fn cursor_agent_name_from_bundled_node_argv(argv: &[String]) -> Option<String> {
+ let (runtime_parent, runtime_name) = path_parent_and_basename(argv.first()?)?;
+ let (script_parent, script_name) = path_parent_and_basename(argv.get(1)?)?;
+ if !runtime_name.eq_ignore_ascii_case("node.exe")
+ || !script_name.eq_ignore_ascii_case("index.js")
+ || !runtime_parent.eq_ignore_ascii_case(script_parent)
+ {
+ return None;
+ }
+
+ let mut tail = runtime_parent
+ .rsplit(['/', '\\'])
+ .filter(|component| !component.is_empty());
+ let (Some(version), Some(versions), Some(package)) = (tail.next(), tail.next(), tail.next())
+ else {
+ return None;
+ };
+ (package.eq_ignore_ascii_case("cursor-agent")
+ && versions.eq_ignore_ascii_case("versions")
+ && !version.trim().is_empty())
+ .then(|| agent_label(Agent::Cursor).to_string())
+}
+
+fn path_parent_and_basename(path: &str) -> Option<(&str, &str)> {
+ let split = path.rfind(['/', '\\'])?;
+ let parent = path[..split].trim_end_matches(['/', '\\']);
+ let basename = &path[split + 1..];
+ (!parent.is_empty() && !basename.is_empty()).then_some((parent, basename))
+}
+
fn windows_cmd_arg_agent_name(argv: &[String]) -> Option<String> {
let mut args = argv.iter().skip(1);
while let Some(arg) = args.next() {
@@ -895,6 +927,62 @@ mod tests {
}
#[test]
+ fn identify_agent_in_job_detects_windows_cursor_install() {
+ let job = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![foreground_process(
+ 123,
+ "node.exe",
+ &[
+ r"C:\Users\user\AppData\Local\cursor-agent\versions\2026.08.11-e8db854\node.exe",
+ r"C:\Users\user\AppData\Local\cursor-agent\versions\2026.08.11-e8db854\index.js",
+ ],
+ )],
+ };
+
+ assert_eq!(
+ identify_agent_in_job(&job),
+ Some((Agent::Cursor, "cursor".to_string()))
+ );
+ }
+
+ #[test]
+ fn identify_agent_in_job_ignores_invalid_windows_cursor_install_paths() {
+ for script in [
+ r"C:\Users\user\AppData\Local\cursor-agent\versions\2026.08.11-e8db854\scripts\postinstall.js",
+ r"C:\Users\user\AppData\Local\cursor-agent\versions\2026.08.11-e8db854\index",
+ r"C:\Users\user\AppData\Local\cursor-agent\versions\2026.08.11-e8db854\index.exe",
+ ] {
+ let job = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![foreground_process(
+ 123,
+ "node.exe",
+ &[
+ r"C:\Users\user\AppData\Local\cursor-agent\versions\2026.08.11-e8db854\node.exe",
+ script,
+ ],
+ )],
+ };
+
+ assert_eq!(identify_agent_in_job(&job), None, "script: {script}");
+ }
+
+ let lookalike = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![foreground_process(
+ 123,
+ "node.exe",
+ &[
+ r"C:\Program Files\nodejs\node.exe",
+ r"C:\workspace\cursor-agent\versions\test\index.js",
+ ],
+ )],
+ };
+ assert_eq!(identify_agent_in_job(&lookalike), None);
+ }
+
+ #[test]
fn identify_agent_in_job_prefers_recognized_process_group_leader() {
let job = crate::platform::ForegroundJob {
process_group_id: 42,