-
Notifications
You must be signed in to change notification settings - Fork 4
π§ Fix Critical CLI Functionality Issues and Restore Missing Subcommands #69
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
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 |
|---|---|---|
|
|
@@ -89,12 +89,17 @@ impl Agent { | |
| fs::write(path, content).await.map_err(Into::into) | ||
| } | ||
|
|
||
| /// Run a shell command and capture stdout and stderr. | ||
| /// Run a shell command and capture stdout and stderr with security validation. | ||
| pub async fn run_command(&self, cmd: &str, args: &[&str]) -> Result<String> { | ||
| // Validate command against security policies | ||
| Self::validate_command_security(cmd, args)?; | ||
|
|
||
| let output = Command::new(cmd) | ||
| .args(args) | ||
| .stdout(Stdio::piped()) | ||
| .stderr(Stdio::piped()) | ||
| .env_clear() // Clear environment for security | ||
| .env("PATH", "/usr/bin:/bin:/usr/local/bin") // Minimal PATH | ||
| .output() | ||
| .await?; | ||
| let mut result = String::from_utf8_lossy(&output.stdout).to_string(); | ||
|
|
@@ -104,6 +109,60 @@ impl Agent { | |
| Ok(result) | ||
| } | ||
|
|
||
| /// Validate command and arguments against security policies | ||
| fn validate_command_security(cmd: &str, args: &[&str]) -> Result<()> { | ||
| // Get allowed commands from environment or use defaults | ||
| let allowed_commands = Self::get_allowed_commands(); | ||
|
|
||
| // Check if command is in whitelist | ||
| if !allowed_commands.contains(&cmd) { | ||
| return Err(anyhow!("Command '{}' not in allowed list", cmd)); | ||
| } | ||
|
|
||
| // Validate command name | ||
| if cmd.len() > 100 { | ||
| return Err(anyhow!("Command name too long")); | ||
| } | ||
|
|
||
| // Check for dangerous patterns in command | ||
| let dangerous_patterns = ["../", "./", "/", "~", "$", "`", ";", "&", "|", ">", "<"]; | ||
| for pattern in &dangerous_patterns { | ||
| if cmd.contains(pattern) { | ||
| return Err(anyhow!("Command contains dangerous pattern: {}", pattern)); | ||
| } | ||
| } | ||
|
Comment on lines
+128
to
+133
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. π οΈ Refactor suggestion Dangerous patterns list differs from tools/mod.rs The dangerous patterns list here is much shorter than the comprehensive list in Consider extracting the dangerous patterns to a shared constant or module to ensure consistency across the codebase: // In a shared module like crates/fluent-agent/src/security/patterns.rs
pub const DANGEROUS_PATTERNS: &[&str] = &[
// Command injection patterns
"$(", "`", ";", "&&", "||", "|", ">", ">>", "<", "<<",
// Path traversal
"../", "./", "~", "/etc/", "/proc/", "/sys/", "/dev/",
// ... rest of the comprehensive list from tools/mod.rs
];π€ Prompt for AI Agents |
||
|
|
||
| // Validate arguments | ||
| for arg in args { | ||
| if arg.len() > 1000 { | ||
| return Err(anyhow!("Argument too long")); | ||
| } | ||
|
|
||
| // Check for dangerous patterns in arguments | ||
| for pattern in &dangerous_patterns { | ||
| if arg.contains(pattern) { | ||
| return Err(anyhow!("Argument contains dangerous pattern: {}", pattern)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Get allowed commands from environment or defaults | ||
| fn get_allowed_commands() -> Vec<&'static str> { | ||
| // Check environment variable for custom allowed commands | ||
| if let Ok(custom_commands) = std::env::var("FLUENT_ALLOWED_COMMANDS") { | ||
| log::info!("Custom allowed commands: {}", custom_commands); | ||
| // TODO: Parse and return custom commands with proper lifetime management | ||
| } | ||
|
|
||
| // Default allowed commands for agent operations | ||
| vec![ | ||
| "cargo", "rustc", "git", "ls", "cat", "echo", "pwd", "which", "find" | ||
| ] | ||
| } | ||
|
|
||
| /// Commit changes in the current git repository. | ||
| pub async fn git_commit(&self, message: &str) -> Result<()> { | ||
| self.run_command("git", &["add", "."]).await?; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| use anyhow::{anyhow, Result}; | ||
| use log::warn; | ||
|
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. π‘ Verification agent π§© Analysis chainGood logging standardization, but verify consistency across the file. The change from However, there are other Also applies to: 213-216 π Script executed: #!/bin/bash
# Search for remaining eprintln calls in mcp_client.rs
rg "eprintln!" crates/fluent-agent/src/mcp_client.rs -nLength of output: 586 Standardize logging in There are remaining β’ crates/fluent-agent/src/mcp_client.rs:298 Suggested diff for each occurrence: - eprintln!("β¦", β¦);
+ warn!("β¦", β¦);
π€ Prompt for AI Agents |
||
| use serde::{Deserialize, Serialize}; | ||
| use serde_json::{json, Value}; | ||
| use std::collections::HashMap; | ||
|
|
@@ -209,7 +210,7 @@ impl McpClient { | |
| Err(e) => { | ||
| last_error = Some(e); | ||
| if attempt < self.config.retry_attempts { | ||
| eprintln!( | ||
| warn!( | ||
| "MCP connection attempt {} failed, retrying in {:?}...", | ||
| attempt, self.config.retry_delay | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -303,19 +303,61 @@ pub mod validation { | |||||
| )) | ||||||
| } | ||||||
|
|
||||||
| /// Validate that a command is in the allowed list | ||||||
| /// Validate that a command is in the allowed list with enhanced security checks | ||||||
| pub fn validate_command(command: &str, allowed_commands: &[String]) -> Result<()> { | ||||||
| // Basic input validation | ||||||
| if command.is_empty() { | ||||||
| return Err(anyhow::anyhow!("Command cannot be empty")); | ||||||
| } | ||||||
|
|
||||||
| if command.len() > 1000 { | ||||||
| return Err(anyhow::anyhow!("Command too long (max 1000 characters)")); | ||||||
| } | ||||||
|
|
||||||
| // Check for null bytes and dangerous control characters | ||||||
| if command.contains('\0') || command.chars().any(|c| c.is_control() && c != '\n' && c != '\t' && c != '\r') { | ||||||
| return Err(anyhow::anyhow!("Command contains invalid control characters")); | ||||||
| } | ||||||
|
|
||||||
| // Enhanced dangerous pattern detection | ||||||
| let dangerous_patterns = [ | ||||||
| // Command injection patterns | ||||||
| "$(", "`", ";", "&&", "||", "|", ">", ">>", "<", "<<", | ||||||
| // Path traversal | ||||||
| "../", "./", "~", "/etc/", "/proc/", "/sys/", "/dev/", | ||||||
| // Privilege escalation | ||||||
| "sudo", "su ", "doas", "pkexec", | ||||||
| // Network operations | ||||||
| "curl", "wget", "nc ", "netcat", "telnet", "ssh", "scp", | ||||||
| // File operations | ||||||
| "rm ", "rmdir", "del ", "format", "mkfs", "dd ", | ||||||
| // Process control | ||||||
| "kill", "killall", "pkill", "&", "nohup", | ||||||
| // Script execution | ||||||
| "bash", "sh ", "zsh", "python", "perl", "ruby", "node", | ||||||
| "eval", "exec", "source", ".", | ||||||
|
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. Critical: The "." pattern is too broad and will block legitimate commands The pattern
If the intent is to block the shell source/dot command, use a more specific pattern like - "eval", "exec", "source", ".",
+ "eval", "exec", "source ", ". ",π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||
| ]; | ||||||
|
Comment on lines
+323
to
+339
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. π οΈ Refactor suggestion Improve pattern matching consistency for better security The dangerous patterns have inconsistent spacing which can lead to both false positives and false negatives:
Consider using word boundary checks or more sophisticated pattern matching to avoid these issues. Example approach using regex for word boundaries: use regex::RegexSet;
// Build regex patterns with word boundaries
let dangerous_commands = vec![
r"\brm\b", r"\brmdir\b", r"\bsudo\b", r"\beval\b",
// ... other commands
];
let pattern_set = RegexSet::new(&dangerous_commands)?;
if pattern_set.is_match(&command_lower) {
// Get specific matches for error reporting
let matches: Vec<_> = pattern_set.matches(&command_lower).into_iter().collect();
return Err(anyhow::anyhow!(
"Command contains dangerous pattern: {:?}",
matches.iter().map(|i| &dangerous_commands[*i]).collect::<Vec<_>>()
));
}π€ Prompt for AI Agents |
||||||
|
|
||||||
| let command_lower = command.to_lowercase(); | ||||||
| for pattern in &dangerous_patterns { | ||||||
| if command_lower.contains(pattern) { | ||||||
| return Err(anyhow::anyhow!( | ||||||
| "Command contains dangerous pattern '{}': {}", | ||||||
| pattern, command | ||||||
| )); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Check against allowed commands list | ||||||
| for allowed in allowed_commands { | ||||||
| if command_lower.starts_with(&allowed.to_lowercase()) { | ||||||
| return Ok(()); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| Err(anyhow::anyhow!( | ||||||
| "Command '{}' is not in the allowed commands list", | ||||||
| command | ||||||
| "Command '{}' is not in the allowed commands list: {:?}", | ||||||
| command, allowed_commands | ||||||
| )) | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
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.
π‘ Verification agent
π§© Analysis chain
Good error logging standardization with minor inconsistency.
The transition from
eprintln!towarn!for engine creation failures is appropriate and maintains essential error context information.There's still a
println!call on lines 237-240 for engine creation success messages. Consider if this should also use structured logging:Also applies to: 136-139, 145-148
π Script executed:
Length of output: 150
Standardize informational logging in config.rs
The only remaining
println!call is at lines 237β240 incrates/fluent-agent/src/config.rs. For consistency with the rest of the moduleβs structured logging, replace it with aninfo!macro:No other
println!usages were found.π Committable suggestion
π€ Prompt for AI Agents