chore: sync current workspace changes

This commit is contained in:
boos
2026-07-14 03:21:10 +08:00
parent 1512fa23ce
commit 557914fea8
745 changed files with 124917 additions and 11222 deletions
@@ -0,0 +1,99 @@
use std::path::{Path, PathBuf};
/// Sensitive file patterns that should be flagged.
const SENSITIVE_PATTERNS: &[&str] = &[
".env",
".env.local",
".env.production",
"credentials.json",
"credentials.yaml",
"credentials.yml",
".aws/credentials",
".ssh/id_rsa",
".ssh/id_ed25519",
"id_rsa",
"id_ed25519",
".npmrc",
".pypirc",
];
/// Validates filesystem paths against allowed directories.
pub struct FilesystemValidator {
allowed_dirs: Vec<PathBuf>,
read_only_dirs: Vec<PathBuf>,
}
impl FilesystemValidator {
pub fn new(working_dir: &str) -> Self {
let working = PathBuf::from(working_dir);
let home = dirs_home();
let mut allowed_dirs = vec![working.clone(), std::env::temp_dir()];
if let Some(home) = &home {
allowed_dirs.push(home.join(".codeany"));
}
Self {
allowed_dirs,
read_only_dirs: Vec::new(),
}
}
pub fn add_allowed_dir(&mut self, dir: PathBuf) {
self.allowed_dirs.push(dir);
}
pub fn add_read_only_dir(&mut self, dir: PathBuf) {
self.read_only_dirs.push(dir);
}
/// Check if a path is allowed for the given operation.
pub fn validate_path(&self, path: &str, write: bool) -> Result<(), String> {
let path = resolve_path(path);
// Check read-only dirs
if write {
for ro_dir in &self.read_only_dirs {
if path.starts_with(ro_dir) {
return Err(format!(
"Path {} is in a read-only directory",
path.display()
));
}
}
}
// Check allowed dirs
let allowed = self.allowed_dirs.iter().any(|dir| path.starts_with(dir));
if !allowed {
return Err(format!(
"Path {} is outside allowed directories",
path.display()
));
}
Ok(())
}
/// Check if a path is sensitive (credentials, keys, etc.).
pub fn is_sensitive_path(path: &str) -> bool {
let path_lower = path.to_lowercase();
SENSITIVE_PATTERNS
.iter()
.any(|pattern| path_lower.ends_with(pattern) || path_lower.contains(pattern))
}
}
fn resolve_path(path: &str) -> PathBuf {
let p = Path::new(path);
if let Ok(canonical) = p.canonicalize() {
canonical
} else {
p.to_path_buf()
}
}
fn dirs_home() -> Option<PathBuf> {
std::env::var("HOME").ok().map(PathBuf::from)
}
@@ -0,0 +1,5 @@
mod filesystem;
mod permissions;
pub use filesystem::*;
pub use permissions::*;
@@ -0,0 +1,119 @@
use crate::types::PermissionMode;
use serde_json::Value;
/// A permission rule that matches tool invocations.
#[derive(Debug, Clone)]
pub struct Rule {
pub tool_name: String,
pub pattern: Option<String>,
}
/// Permission configuration.
#[derive(Debug, Clone)]
pub struct PermissionConfig {
pub mode: PermissionMode,
pub allow_rules: Vec<Rule>,
pub deny_rules: Vec<Rule>,
pub allowed_tools: Option<Vec<String>>,
}
impl Default for PermissionConfig {
fn default() -> Self {
Self {
mode: PermissionMode::BypassPermissions,
allow_rules: Vec::new(),
deny_rules: Vec::new(),
allowed_tools: None,
}
}
}
/// Result of a permission check.
#[derive(Debug, Clone, PartialEq)]
pub enum PermissionResult {
Allow,
Deny(String),
}
/// Check if a tool invocation is allowed.
pub fn check_permission(
config: &PermissionConfig,
tool_name: &str,
input: &Value,
) -> PermissionResult {
// Bypass mode allows everything
if config.mode == PermissionMode::BypassPermissions {
return PermissionResult::Allow;
}
// Check allowed tools whitelist
if let Some(allowed) = &config.allowed_tools {
if !allowed.iter().any(|a| a == tool_name) {
return PermissionResult::Deny(format!("Tool '{}' is not in allowed list", tool_name));
}
}
// Check deny rules first
for rule in &config.deny_rules {
if matches_rule(&rule.tool_name, tool_name) {
if let Some(pattern) = &rule.pattern {
if matches_input_pattern(pattern, input) {
return PermissionResult::Deny(format!("Tool '{}' denied by rule", tool_name));
}
} else {
return PermissionResult::Deny(format!("Tool '{}' denied by rule", tool_name));
}
}
}
// Check allow rules
for rule in &config.allow_rules {
if matches_rule(&rule.tool_name, tool_name) {
return PermissionResult::Allow;
}
}
// Default behavior based on mode
match config.mode {
PermissionMode::DontAsk | PermissionMode::Auto => PermissionResult::Allow,
PermissionMode::AcceptEdits => {
// Allow read-only tools and file edits
let read_only_tools = [
"Read",
"Glob",
"Grep",
"WebFetch",
"WebSearch",
"TaskList",
"TaskGet",
"ToolSearch",
];
if read_only_tools.contains(&tool_name) {
PermissionResult::Allow
} else {
PermissionResult::Allow // acceptEdits mode allows edits
}
}
PermissionMode::Plan => PermissionResult::Deny(format!(
"Tool '{}' requires approval in plan mode",
tool_name
)),
_ => PermissionResult::Allow,
}
}
fn matches_rule(rule_name: &str, tool_name: &str) -> bool {
if rule_name == "*" {
return true;
}
if rule_name.ends_with('*') {
let prefix = &rule_name[..rule_name.len() - 1];
return tool_name.starts_with(prefix);
}
rule_name == tool_name
}
fn matches_input_pattern(pattern: &str, input: &Value) -> bool {
let input_str = input.to_string();
input_str.contains(pattern)
}