Skip to content
Closed
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
65 changes: 46 additions & 19 deletions src/cortex-cli/src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@ pub fn get_cortex_home() -> PathBuf {
/// // Returns: /home/user/documents/file.txt
/// ```
pub fn expand_tilde(path: &str) -> String {
if path.starts_with("~/")
&& let Some(home) = dirs::home_dir()
{
return home.join(&path[2..]).to_string_lossy().to_string();
if path == "~" {
// Handle bare "~" - return home directory
if let Some(home) = dirs::home_dir() {
return home.to_string_lossy().to_string();
}
} else if path.starts_with("~/") {
// Handle "~/" prefix - expand to home directory + rest of path
if let Some(home) = dirs::home_dir() {
return home.join(&path[2..]).to_string_lossy().to_string();
}
}
path.to_string()
}
Expand All @@ -58,8 +64,12 @@ pub fn expand_tilde(path: &str) -> String {
pub fn validate_path_safety(path: &Path, base_dir: Option<&Path>) -> Result<(), String> {
let path_str = path.to_string_lossy();

// Check for path traversal attempts
if path_str.contains("..") {
// Check for path traversal attempts by examining path components
// This correctly handles filenames containing ".." like "file..txt"
if path
.components()
.any(|c| matches!(c, std::path::Component::ParentDir))
{
return Err("Path contains traversal sequence '..'".to_string());
}

Expand Down Expand Up @@ -257,8 +267,15 @@ mod tests {

#[test]
fn test_expand_tilde_with_tilde_only() {
// Test tilde alone - should remain unchanged (not "~/")
assert_eq!(expand_tilde("~"), "~");
// Test bare "~" - should expand to home directory
let result = expand_tilde("~");
if let Some(home) = dirs::home_dir() {
let expected = home.to_string_lossy().to_string();
assert_eq!(result, expected);
} else {
// If no home dir, original is returned
assert_eq!(result, "~");
}
}

#[test]
Expand Down Expand Up @@ -320,20 +337,30 @@ mod tests {

#[test]
fn test_validate_path_safety_detects_various_traversal_patterns() {
// Different traversal patterns
let patterns = ["foo/../bar", "...", "foo/bar/../baz", "./foo/../../../etc"];
// Patterns that ARE path traversal (contain ".." as a component)
let traversal_patterns = ["foo/../bar", "foo/bar/../baz", "./foo/../../../etc", ".."];

for pattern in patterns {
for pattern in traversal_patterns {
let path = Path::new(pattern);
let result = validate_path_safety(path, None);
// Only patterns containing ".." should fail
if pattern.contains("..") {
assert!(
result.is_err(),
"Expected traversal detection for: {}",
pattern
);
}
assert!(
result.is_err(),
"Expected traversal detection for: {}",
pattern
);
}

// Patterns that are NOT path traversal (contain ".." in filenames only)
let safe_patterns = ["file..txt", "..hidden", "test...file", "foo/bar..baz/file"];

for pattern in safe_patterns {
let path = Path::new(pattern);
let result = validate_path_safety(path, None);
assert!(
result.is_ok(),
"False positive: '{}' should not be detected as traversal",
pattern
);
}
}

Expand Down
20 changes: 17 additions & 3 deletions src/cortex-compact/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//! Compaction configuration.

use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};

/// Configuration for auto-compaction.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompactionConfig {
/// Whether auto-compaction is enabled.
#[serde(default = "default_true")]
pub enabled: bool,
/// Token threshold to trigger compaction (percentage of max).
#[serde(default = "default_threshold")]
/// Token threshold to trigger compaction (ratio 0.0-1.0 of max context).
#[serde(default = "default_threshold", deserialize_with = "deserialize_threshold_percent")]
pub threshold_percent: f32,
/// Minimum tokens to keep after compaction.
#[serde(default = "default_min_tokens")]
Expand All @@ -25,6 +25,20 @@ pub struct CompactionConfig {
pub preserve_recent_turns: usize,
}

/// Deserialize threshold_percent with validation (must be 0.0-1.0).
fn deserialize_threshold_percent<'de, D>(deserializer: D) -> Result<f32, D::Error>
where
D: Deserializer<'de>,
{
let value = f32::deserialize(deserializer)?;
if !(0.0..=1.0).contains(&value) {
return Err(serde::de::Error::custom(
"threshold_percent must be between 0.0 and 1.0",
));
}
Ok(value)
}

fn default_true() -> bool {
true
}
Expand Down
4 changes: 2 additions & 2 deletions src/cortex-engine/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ pub struct Config {
/// Provider configuration.
pub model_provider: ModelProviderInfo,
/// Context window size.
pub model_context_window: Option<i64>,
pub model_context_window: Option<u64>,
/// Auto-compact token limit.
pub model_auto_compact_token_limit: Option<i64>,
pub model_auto_compact_token_limit: Option<u64>,
/// Approval policy.
pub approval_policy: AskForApproval,
/// Sandbox policy.
Expand Down
4 changes: 2 additions & 2 deletions src/cortex-engine/src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ pub struct PermissionConfig {
pub struct ConfigToml {
pub model: Option<String>,
pub model_provider: Option<String>,
pub model_context_window: Option<i64>,
pub model_auto_compact_token_limit: Option<i64>,
pub model_context_window: Option<u64>,
pub model_auto_compact_token_limit: Option<u64>,
pub approval_policy: Option<AskForApproval>,
pub sandbox_mode: Option<SandboxMode>,
pub sandbox_workspace_write: Option<SandboxWorkspaceWrite>,
Expand Down
5 changes: 5 additions & 0 deletions src/cortex-engine/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub mod artifacts;
pub mod context;
pub mod handlers;
pub mod registry;
pub mod response_store;
pub mod router;
pub mod spec;
pub mod unified_executor;
Expand All @@ -45,6 +46,10 @@ pub use artifacts::{
pub use context::ToolContext;
pub use handlers::*;
pub use registry::{PluginTool, ToolRegistry};
pub use response_store::{
CLEANUP_INTERVAL, DEFAULT_TTL, MAX_STORE_SIZE, StoreInfo, StoreStats, StoredResponse,
ToolResponseStore, ToolResponseStoreConfig, create_shared_store, create_shared_store_with_config,
};
pub use router::ToolRouter;
pub use spec::{ToolCall, ToolDefinition, ToolHandler, ToolResult};
pub use unified_executor::{ExecutorConfig, UnifiedToolExecutor};
Loading
Loading