Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/fluent-agent/src/action.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use log::info;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, SystemTime};
Expand Down Expand Up @@ -621,8 +622,8 @@ impl ComprehensiveActionExecutor {
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("Message not specified for communication"))?;

// For now, just log the communication
println!("Agent Communication: {}", message);
// Log the communication using structured logging
info!("Agent communication: {}", message);

let mut metadata = HashMap::new();
metadata.insert(
Expand Down
9 changes: 5 additions & 4 deletions crates/fluent-agent/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
use fluent_core::config::load_engine_config;
use fluent_core::traits::Engine;
use fluent_engines::create_engine;
use log::warn;
Copy link

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! to warn! 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:

#!/bin/bash
# Check for remaining println calls in config.rs
rg "println!" crates/fluent-agent/src/config.rs -n -A1 -B1

Length of output: 150


Standardize informational logging in config.rs

The only remaining println! call is at lines 237–240 in crates/fluent-agent/src/config.rs. For consistency with the rest of the module’s structured logging, replace it with an info! macro:

--- a/crates/fluent-agent/src/config.rs
+++ b/crates/fluent-agent/src/config.rs
@@ 237,240c237,240
-        println!(
-            "πŸ”§ Creating default {} engine with model {}",
-            engine_name, model_id
-        );
+        info!(
+            "πŸ”§ Creating default {} engine with model {}",
+            engine_name, model_id
+        );

No other println! usages were found.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use log::warn;
info!(
"πŸ”§ Creating default {} engine with model {}",
engine_name, model_id
);
πŸ€– Prompt for AI Agents
In crates/fluent-agent/src/config.rs around lines 237 to 240, replace the
existing println! call with the info! macro from the log crate to standardize
informational logging. This involves changing the println! statement to an info!
statement while preserving the original message content and formatting.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
Expand Down Expand Up @@ -132,17 +133,17 @@ impl AgentEngineConfig {
match create_engine(&engine_config).await {
Ok(engine) => Ok(engine),
Err(e) => {
eprintln!(
"Warning: Failed to create engine '{}' with config: {}",
warn!(
"Failed to create engine '{}' with config: {}",
engine_name, e
);
self.create_default_engine(engine_name, credentials).await
}
}
}
Err(e) => {
eprintln!(
"Warning: Engine '{}' not found in config: {}",
warn!(
"Engine '{}' not found in config: {}",
engine_name, e
);
self.create_default_engine(engine_name, credentials).await
Expand Down
61 changes: 60 additions & 1 deletion crates/fluent-agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Copy link

Choose a reason for hiding this comment

The 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 tools/mod.rs. This inconsistency could lead to security gaps where commands blocked in one module are allowed in another.

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
In crates/fluent-agent/src/lib.rs around lines 128 to 133, the list of dangerous
patterns used to validate commands is shorter and inconsistent compared to the
comprehensive list in tools/mod.rs, which risks security gaps. To fix this,
extract the dangerous patterns into a shared constant or module (e.g.,
crates/fluent-agent/src/security/patterns.rs) containing the full list from
tools/mod.rs, then import and use this shared constant in lib.rs to ensure
consistent validation across the codebase.


// 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?;
Expand Down
4 changes: 2 additions & 2 deletions crates/fluent-agent/src/mcp_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ impl ServerHandler for FluentMcpAdapter {
let result = match params.name.as_ref() {
"read_file" => {
if let Some(path) = tool_args.get("path") {
match std::fs::read_to_string(path.as_str().unwrap_or("")) {
match tokio::fs::read_to_string(path.as_str().unwrap_or("")).await {
Ok(content) => format!("File content: {}", content),
Err(e) => format!("Error reading file: {}", e),
}
Expand All @@ -375,7 +375,7 @@ impl ServerHandler for FluentMcpAdapter {
"write_file" => {
if let Some(path) = tool_args.get("path") {
if let Some(content) = tool_args.get("content") {
match std::fs::write(path.as_str().unwrap_or(""), content.as_str().unwrap_or("")) {
match tokio::fs::write(path.as_str().unwrap_or(""), content.as_str().unwrap_or("")).await {
Ok(_) => "File written successfully".to_string(),
Err(e) => format!("Error writing file: {}", e),
}
Expand Down
3 changes: 2 additions & 1 deletion crates/fluent-agent/src/mcp_client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::{anyhow, Result};
use log::warn;
Copy link

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 logging standardization, but verify consistency across the file.

The change from eprintln! to warn! for connection retry warnings is appropriate and aligns with structured logging best practices.

However, there are other eprintln! calls in this file that remain unchanged. Please verify if this is intentional:

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 -n

Length of output: 586


Standardize logging in mcp_client.rs

There are remaining eprintln! calls in this file that should be converted to structured logging (warn!) for consistency:

β€’ crates/fluent-agent/src/mcp_client.rs:298
β€’ crates/fluent-agent/src/mcp_client.rs:531
β€’ crates/fluent-agent/src/mcp_client.rs:538
β€’ crates/fluent-agent/src/mcp_client.rs:542
β€’ crates/fluent-agent/src/mcp_client.rs:545
β€’ crates/fluent-agent/src/mcp_client.rs:576

Suggested diff for each occurrence:

-    eprintln!("…", …);
+    warn!("…", …);

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In crates/fluent-agent/src/mcp_client.rs at lines 298, 531, 538, 542, 545, and
576, replace all eprintln! calls with warn! macro calls from the log crate to
standardize logging. Modify each eprintln! statement to use warn! with the same
message content, ensuring consistent structured logging throughout the file.

use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
Expand Down Expand Up @@ -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
);
Expand Down
29 changes: 15 additions & 14 deletions crates/fluent-agent/src/performance/utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use log::{debug, info};
use std::time::{Duration, Instant};
use std::sync::{Arc, Mutex};
use tokio::sync::Semaphore;
Expand Down Expand Up @@ -218,17 +219,17 @@ impl PerformanceTestUtils {
let memory_tracker = MemoryTracker::new();
let start_time = Instant::now();

println!("Running performance test: {}", name);
info!("Running performance test: {}", name);

for i in 0..num_operations {
let op_start = Instant::now();
let result = operation(i).await;
let op_duration = op_start.elapsed();

counter.record_request(op_duration, result.is_err());

if i % (num_operations / 10).max(1) == 0 {
println!(" Progress: {}/{}", i + 1, num_operations);
debug!(" Progress: {}/{}", i + 1, num_operations);
}
}

Expand Down Expand Up @@ -314,15 +315,15 @@ pub struct PerformanceTestResult {

impl PerformanceTestResult {
pub fn print_summary(&self) {
println!("=== Performance Test Results: {} ===", self.test_name);
println!(" Total Duration: {:?}", self.total_duration);
println!(" Total Operations: {}", self.stats.total_requests);
println!(" Successful Operations: {}", self.stats.total_requests - self.stats.total_errors);
println!(" Failed Operations: {}", self.stats.total_errors);
println!(" Success Rate: {:.2}%", (1.0 - self.stats.error_rate) * 100.0);
println!(" Operations per Second: {:.2}", self.operations_per_second);
println!(" Average Operation Time: {:?}", self.stats.average_duration);
println!(" Min Operation Time: {:?}", self.stats.min_duration.unwrap_or_default());
info!("=== Performance Test Results: {} ===", self.test_name);
info!(" Total Duration: {:?}", self.total_duration);
info!(" Total Operations: {}", self.stats.total_requests);
info!(" Successful Operations: {}", self.stats.total_requests - self.stats.total_errors);
info!(" Failed Operations: {}", self.stats.total_errors);
info!(" Success Rate: {:.2}%", (1.0 - self.stats.error_rate) * 100.0);
info!(" Operations per Second: {:.2}", self.operations_per_second);
info!(" Average Operation Time: {:?}", self.stats.average_duration);
info!(" Min Operation Time: {:?}", self.stats.min_duration.unwrap_or_default());
println!(" Max Operation Time: {:?}", self.stats.max_duration.unwrap_or_default());
println!(" Peak Memory Usage: {} bytes ({:.2} MB)",
self.peak_memory_usage,
Expand Down
48 changes: 45 additions & 3 deletions crates/fluent-agent/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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", ".",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: The "." pattern is too broad and will block legitimate commands

The pattern "." will match any command containing a period character, which will incorrectly block many legitimate use cases such as:

  • File names with extensions (e.g., script.sh, data.json)
  • Version numbers (e.g., 1.0, 2.5.1)
  • Domain names or URLs
  • Decimal numbers

If the intent is to block the shell source/dot command, use a more specific pattern like ". " (dot followed by space) or "source ".

-            "eval", "exec", "source", ".",
+            "eval", "exec", "source ", ". ",
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"eval", "exec", "source", ".",
"eval", "exec", "source ", ". ",
πŸ€– Prompt for AI Agents
In crates/fluent-agent/src/tools/mod.rs at line 338, the pattern "." is too
broad and blocks many legitimate commands containing periods. Replace the "."
pattern with a more specific one like ". " (dot followed by space) or "source "
to only block the shell source/dot command without affecting other valid
commands.

];
Comment on lines +323 to +339
Copy link

Choose a reason for hiding this comment

The 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:

  • Patterns without trailing spaces (e.g., "python", "bash") will block legitimate strings containing these as substrings
  • Patterns with trailing spaces (e.g., "rm ", "su ") won't catch these commands at the end of a line

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
In crates/fluent-agent/src/tools/mod.rs around lines 323 to 339, the current
dangerous_patterns array uses inconsistent spacing which causes false positives
and negatives in command detection. Replace the simple string array with regex
patterns that use word boundaries to precisely match whole commands. Use a
RegexSet to compile these patterns and check the input command string against
it. This approach ensures commands are detected accurately regardless of their
position or surrounding characters, improving security and reducing incorrect
matches.


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
))
}

Expand Down
Loading
Loading