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:
parent
1129d1bca5
commit
db70d51c65
@ -191,12 +191,29 @@ def _resolve_safe_path(vault_root: Path, relative_path: str) -> Path:
|
|||||||
Raises:
|
Raises:
|
||||||
HTTPException(403): When the resolved path escapes the vault root.
|
HTTPException(403): When the resolved path escapes the vault root.
|
||||||
"""
|
"""
|
||||||
resolved = (vault_root / relative_path).resolve()
|
# Construct the full path without resolving symlinks first
|
||||||
vault_resolved = vault_root.resolve()
|
full_path = vault_root / relative_path
|
||||||
|
|
||||||
|
# Resolve both paths to handle symlinks
|
||||||
try:
|
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)
|
resolved.relative_to(vault_resolved)
|
||||||
except ValueError:
|
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
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
@ -529,10 +546,18 @@ async def api_file(vault_name: str, path: str = Query(..., description="Relative
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
raw = file_path.read_text(encoding="utf-8", errors="replace")
|
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}")
|
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:
|
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)}")
|
raise HTTPException(status_code=500, detail=f"Error reading file: {str(e)}")
|
||||||
|
|
||||||
ext = file_path.suffix.lower()
|
ext = file_path.suffix.lower()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user