-
Notifications
You must be signed in to change notification settings - Fork 143
Port Antigravity CLI override safety #615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| use std::path::PathBuf; | ||
|
|
||
| use crate::core::ProviderError; | ||
|
|
||
| pub(super) fn locate_agy_binary() -> Result<Option<PathBuf>, ProviderError> { | ||
| resolve_agy_binary( | ||
| std::env::var_os("ANTIGRAVITY_CLI_PATH").map(PathBuf::from), | ||
| || { | ||
| agy_binary_candidates( | ||
| which::which("agy").ok(), | ||
| std::env::var_os("LOCALAPPDATA").map(PathBuf::from), | ||
| dirs::home_dir(), | ||
| ) | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| fn validate_agy_binary_override( | ||
| explicit: Option<PathBuf>, | ||
| ) -> Result<Option<PathBuf>, ProviderError> { | ||
| let Some(path) = explicit else { | ||
| return Ok(None); | ||
| }; | ||
| if path.is_file() { | ||
| Ok(Some(path)) | ||
| } else { | ||
| Err(ProviderError::NotInstalled(format!( | ||
| "ANTIGRAVITY_CLI_PATH is set but does not point to a usable agy file: {}. Fix or unset the variable; automatic CLI discovery is disabled while it is set.", | ||
| path.display() | ||
| ))) | ||
| } | ||
| } | ||
|
|
||
| fn resolve_agy_binary<F>( | ||
| explicit: Option<PathBuf>, | ||
| discover: F, | ||
| ) -> Result<Option<PathBuf>, ProviderError> | ||
| where | ||
| F: FnOnce() -> Vec<PathBuf>, | ||
| { | ||
| if let Some(path) = validate_agy_binary_override(explicit)? { | ||
| return Ok(Some(path)); | ||
| } | ||
| Ok(discover().into_iter().find(|path| path.is_file())) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '45,69p' rust/src/providers/antigravity/cli_resolution.rs
rg -n 'which::|ANTIGRAVITY_CLI_PATH|agy/bin|local/bin/agy' rust/src/providers/antigravity rust/Cargo.tomlRepository: nesszer/Win-CodexBar Length of output: 1568 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- cli_resolution.rs ---'
cat -n rust/src/providers/antigravity/cli_resolution.rs | sed -n '1,155p'
printf '%s\n' '--- provider environment and install references ---'
rg -n -C 3 'local_app_data|LOCALAPPDATA|\.local/bin|agy\.exe|which::which|ANTIGRAVITY_CLI_PATH|install|PATH' rust/src/providers/antigravity rust/Cargo.toml Cargo.lock README.md .github 2>/dev/null | sed -n '1,260p'
printf '%s\n' '--- which dependency declarations ---'
rg -n -C 3 '(^|[[:space:]])which([[:space:]]|=)|which =' rust/Cargo.toml Cargo.lockRepository: nesszer/Win-CodexBar Length of output: 24788 🌐 Web query:
💡 Result: Filter non-runnable fixed-path candidates.
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| fn agy_binary_candidates( | ||
| path_lookup: Option<PathBuf>, | ||
| local_app_data: Option<PathBuf>, | ||
| home: Option<PathBuf>, | ||
| ) -> Vec<PathBuf> { | ||
| let mut candidates = Vec::new(); | ||
| if let Some(path) = path_lookup { | ||
| candidates.push(path); | ||
| } | ||
| if let Some(root) = local_app_data { | ||
| candidates.push(root.join("agy").join("bin").join("agy.exe")); | ||
| } | ||
| if let Some(root) = home { | ||
| candidates.push(root.join(".local").join("bin").join(if cfg!(windows) { | ||
| "agy.exe" | ||
| } else { | ||
| "agy" | ||
| })); | ||
| } | ||
| candidates | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn candidates_prefer_path_then_known_installs() { | ||
| let path_lookup = PathBuf::from(r"C:\path\agy.exe"); | ||
| let local_app_data = PathBuf::from(r"C:\Users\test\AppData\Local"); | ||
| let home = PathBuf::from(r"C:\Users\test"); | ||
|
|
||
| let candidates = agy_binary_candidates( | ||
| Some(path_lookup.clone()), | ||
| Some(local_app_data.clone()), | ||
| Some(home.clone()), | ||
| ); | ||
|
|
||
| assert_eq!(candidates[0], path_lookup); | ||
| assert_eq!( | ||
| candidates[1], | ||
| local_app_data.join("agy").join("bin").join("agy.exe") | ||
| ); | ||
| assert_eq!( | ||
| candidates[2], | ||
| home.join(".local") | ||
| .join("bin") | ||
| .join(if cfg!(windows) { "agy.exe" } else { "agy" }) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn usable_override_is_selected_without_discovery() { | ||
| let temp = tempfile::tempdir().expect("temporary directory"); | ||
| let override_path = temp.path().join("configured-agy.exe"); | ||
| std::fs::write(&override_path, b"test executable placeholder").expect("write fixture"); | ||
|
|
||
| let resolved = resolve_agy_binary(Some(override_path.clone()), || { | ||
| panic!("a configured override must not trigger automatic discovery") | ||
| }) | ||
| .expect("usable override should resolve"); | ||
|
|
||
| assert_eq!(resolved, Some(override_path)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn unusable_override_fails_without_automatic_discovery() { | ||
| let temp = tempfile::tempdir().expect("temporary directory"); | ||
| let missing_override = temp.path().join("missing-agy.exe"); | ||
|
|
||
| let error = resolve_agy_binary(Some(missing_override), || { | ||
| panic!("an invalid configured override must block automatic discovery") | ||
| }) | ||
| .expect_err("invalid override should fail closed"); | ||
|
|
||
| let message = error.to_string(); | ||
| assert!(message.contains("ANTIGRAVITY_CLI_PATH is set")); | ||
| assert!(message.contains("automatic CLI discovery is disabled")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn unset_override_preserves_automatic_discovery() { | ||
| let temp = tempfile::tempdir().expect("temporary directory"); | ||
| let discovered_path = temp.path().join("discovered-agy.exe"); | ||
| std::fs::write(&discovered_path, b"test executable placeholder").expect("write fixture"); | ||
|
|
||
| let resolved = resolve_agy_binary(None, || { | ||
| vec![ | ||
| temp.path().join("missing-first.exe"), | ||
| discovered_path.clone(), | ||
| ] | ||
| }) | ||
| .expect("automatic discovery should resolve"); | ||
|
|
||
| assert_eq!(resolved, Some(discovered_path)); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 6652
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 38154
🌐 Web query:
official Rust std::process::Command current_dir relative program path platform behavior💡 Result:
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 15698
🏁 Script executed:
Repository: nesszer/Win-CodexBar
Length of output: 10565
Resolve a configured relative path before returning it.
If
ANTIGRAVITY_CLI_PATHis a bare filename in the current directory,is_file()accepts that file, but the structured fallback launches it with a private working directory. The managed Windows launcher also passes the relative name toCreateProcessWwith a separatecwd. Relative executable lookup is platform-specific, so launch can resolve a differentagyfromPATHor fail. Convert the override to an absolute path before returning it.Suggested fix
if path.is_file() { - Ok(Some(path)) + Ok(Some(path.canonicalize().map_err(|error| { + ProviderError::NotInstalled(format!( + "ANTIGRAVITY_CLI_PATH could not be resolved: {error}" + )) + })?)) } else {🤖 Prompt for AI Agents