Add case-insensitive path validation for Windows/Docker compatibility, improve error handling in file API with binary file support and enhanced logging for path resolution and file read operations

This commit is contained in:
Bruno Charest 2026-03-22 23:58:48 -04:00
parent 1129d1bca5
commit db70d51c65

View File

@ -191,12 +191,29 @@ def _resolve_safe_path(vault_root: Path, relative_path: str) -> Path:
Raises:
HTTPException(403): When the resolved path escapes the vault root.
"""
resolved = (vault_root / relative_path).resolve()
vault_resolved = vault_root.resolve()
# Construct the full path without resolving symlinks first
full_path = vault_root / relative_path
# Resolve both paths to handle symlinks
try:
resolved = full_path.resolve(strict=False)
vault_resolved = vault_root.resolve(strict=False)
except Exception as e:
logger.error(f"Path resolution error - vault_root: {vault_root}, relative_path: {relative_path}, error: {e}")
raise HTTPException(status_code=500, detail=f"Path resolution error: {str(e)}")
# Check if resolved path is within vault using string comparison (case-insensitive on Windows)
try:
# This will raise ValueError if resolved is not relative to vault_resolved
resolved.relative_to(vault_resolved)
except ValueError:
raise HTTPException(status_code=403, detail="Access denied: path outside vault")
# Try case-insensitive comparison for Windows/Docker compatibility
resolved_str = str(resolved).lower()
vault_str = str(vault_resolved).lower()
if not resolved_str.startswith(vault_str):
logger.warning(f"Path outside vault - vault: {vault_resolved}, requested: {relative_path}, resolved: {resolved}")
raise HTTPException(status_code=403, detail="Access denied: path outside vault")
return resolved
@ -529,10 +546,18 @@ async def api_file(vault_name: str, path: str = Query(..., description="Relative
try:
raw = file_path.read_text(encoding="utf-8", errors="replace")
except PermissionError:
except PermissionError as e:
logger.error(f"Permission denied reading file {path}: {e}")
raise HTTPException(status_code=403, detail=f"Permission denied: cannot read file {path}")
except UnicodeDecodeError:
# Binary file - try to read as binary and decode with errors='replace'
try:
raw = file_path.read_bytes().decode("utf-8", errors="replace")
except Exception as e:
logger.error(f"Error reading binary file {path}: {e}")
raise HTTPException(status_code=500, detail=f"Cannot read file: {str(e)}")
except Exception as e:
logger.error(f"Error reading file {path}: {e}")
logger.error(f"Unexpected error reading file {path}: {e}")
raise HTTPException(status_code=500, detail=f"Error reading file: {str(e)}")
ext = file_path.suffix.lower()