fix(tool): Windows UNC 路径规范 — PathValidator 路径比较一致性
Some checks are pending
CI / Lint & TypeCheck (push) Waiting to run
CI / Unit Tests (push) Waiting to run
CI / Build Frontend (push) Waiting to run
CI / Rust Check (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / E2E Tests (push) Blocked by required conditions
Some checks are pending
CI / Lint & TypeCheck (push) Waiting to run
CI / Unit Tests (push) Waiting to run
CI / Build Frontend (push) Waiting to run
CI / Rust Check (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / E2E Tests (push) Blocked by required conditions
- with_workspace() 对 workspace_root 做 canonicalize,确保与 resolve_and_validate 产出的 canonical 路径格式一致 - 新增 normalize_windows_path() 剥离 \?\ 前缀,解决 Windows 上 starts_with 比较失败问题 - check_blocked/check_allowed 统一使用规范化路径比较
This commit is contained in:
@@ -97,6 +97,17 @@ fn default_blocked_paths() -> Vec<PathBuf> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalize Windows UNC path prefix for consistent comparison.
|
||||||
|
/// `\\?\C:\Users\...` → `C:\Users\...`
|
||||||
|
fn normalize_windows_path(path: &Path) -> std::borrow::Cow<'_, Path> {
|
||||||
|
let s = path.to_string_lossy();
|
||||||
|
if s.starts_with(r"\\?\") {
|
||||||
|
std::borrow::Cow::Owned(PathBuf::from(&s[4..]))
|
||||||
|
} else {
|
||||||
|
std::borrow::Cow::Borrowed(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Expand tilde in path to home directory
|
/// Expand tilde in path to home directory
|
||||||
fn expand_tilde(path: &str) -> PathBuf {
|
fn expand_tilde(path: &str) -> PathBuf {
|
||||||
if path.starts_with('~') {
|
if path.starts_with('~') {
|
||||||
@@ -154,9 +165,16 @@ impl PathValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the workspace root directory
|
/// Set the workspace root directory.
|
||||||
|
/// Canonicalizes the path to ensure consistent comparison on Windows
|
||||||
|
/// (where canonicalize() returns `\\?\C:\...` UNC paths).
|
||||||
pub fn with_workspace(mut self, workspace: PathBuf) -> Self {
|
pub fn with_workspace(mut self, workspace: PathBuf) -> Self {
|
||||||
self.workspace_root = Some(workspace);
|
let canonical = if workspace.exists() {
|
||||||
|
workspace.canonicalize().unwrap_or(workspace)
|
||||||
|
} else {
|
||||||
|
workspace
|
||||||
|
};
|
||||||
|
self.workspace_root = Some(canonical);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,10 +305,14 @@ impl PathValidator {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if path is in blocked list
|
/// Check if path is in blocked list.
|
||||||
|
/// Normalizes Windows UNC prefix (`\\?\`) for consistent comparison.
|
||||||
fn check_blocked(&self, path: &Path) -> Result<()> {
|
fn check_blocked(&self, path: &Path) -> Result<()> {
|
||||||
|
// Strip Windows UNC prefix for consistent matching
|
||||||
|
let normalized = normalize_windows_path(path);
|
||||||
for blocked in &self.config.blocked_paths {
|
for blocked in &self.config.blocked_paths {
|
||||||
if path.starts_with(blocked) || path == blocked {
|
let blocked_norm = normalize_windows_path(blocked);
|
||||||
|
if normalized.starts_with(&*blocked_norm) || normalized == blocked_norm {
|
||||||
return Err(ZclawError::InvalidInput(format!(
|
return Err(ZclawError::InvalidInput(format!(
|
||||||
"Access to this path is blocked: {}",
|
"Access to this path is blocked: {}",
|
||||||
path.display()
|
path.display()
|
||||||
@@ -310,11 +332,15 @@ impl PathValidator {
|
|||||||
/// - This prevents accidental exposure of the entire filesystem
|
/// - This prevents accidental exposure of the entire filesystem
|
||||||
/// when the validator is misconfigured or used without setup
|
/// when the validator is misconfigured or used without setup
|
||||||
fn check_allowed(&self, path: &Path) -> Result<()> {
|
fn check_allowed(&self, path: &Path) -> Result<()> {
|
||||||
|
let path_norm = normalize_windows_path(path);
|
||||||
|
|
||||||
// If no allowed paths specified, check workspace
|
// If no allowed paths specified, check workspace
|
||||||
if self.config.allowed_paths.is_empty() {
|
if self.config.allowed_paths.is_empty() {
|
||||||
if let Some(ref workspace) = self.workspace_root {
|
if let Some(ref workspace) = self.workspace_root {
|
||||||
// Workspace is configured - validate path is within it
|
// Workspace is configured - validate path is within it
|
||||||
if !path.starts_with(workspace) {
|
// Both sides are canonicalized (workspace via with_workspace, path via resolve_and_validate)
|
||||||
|
let ws_norm = normalize_windows_path(workspace);
|
||||||
|
if !path_norm.starts_with(&*ws_norm) {
|
||||||
return Err(ZclawError::InvalidInput(format!(
|
return Err(ZclawError::InvalidInput(format!(
|
||||||
"Path outside workspace: {} (workspace: {})",
|
"Path outside workspace: {} (workspace: {})",
|
||||||
path.display(),
|
path.display(),
|
||||||
@@ -336,7 +362,8 @@ impl PathValidator {
|
|||||||
|
|
||||||
// Check against allowed paths
|
// Check against allowed paths
|
||||||
for allowed in &self.config.allowed_paths {
|
for allowed in &self.config.allowed_paths {
|
||||||
if path.starts_with(allowed) {
|
let allowed_norm = normalize_windows_path(allowed);
|
||||||
|
if path_norm.starts_with(&*allowed_norm) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user