fix(security): reduce code duplication and fix security issues in OAuth and file handling
- Extract common OAuth token exchange logic to shared utility (oauth_helper.py) - Remove sensitive data logging (client_secret, authorization codes) - Add path traversal validation in resolve_file_path() - Add input validation for rclone destination parameter - Replace bare Exception catches with specific exception types - Use RuntimeError instead of generic Exception for better error handling Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+38
-8
@@ -3,8 +3,9 @@ Common utilities for API routes
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, HTTPException, status
|
||||
|
||||
from app.database import SessionLocal
|
||||
from app.config import settings
|
||||
@@ -22,15 +23,44 @@ def get_db():
|
||||
|
||||
def resolve_file_path(file_path: str, subfolder: str = None) -> str:
|
||||
"""
|
||||
Resolves a file path to an absolute path.
|
||||
Resolves a file path to an absolute path with path traversal protection.
|
||||
If the path is not absolute, it will be joined with the workdir path.
|
||||
Optionally, can include a subfolder like 'processed'.
|
||||
|
||||
Returns the absolute file path.
|
||||
Security: Validates that the resolved path stays within the workdir
|
||||
to prevent path traversal attacks (e.g., ../../etc/passwd).
|
||||
|
||||
Args:
|
||||
file_path: The file path to resolve
|
||||
subfolder: Optional subfolder within workdir
|
||||
|
||||
Returns:
|
||||
The validated absolute file path
|
||||
|
||||
Raises:
|
||||
HTTPException: If the path attempts to escape the workdir
|
||||
"""
|
||||
# Build the base directory
|
||||
if subfolder:
|
||||
base_dir = Path(settings.workdir) / subfolder
|
||||
else:
|
||||
base_dir = Path(settings.workdir)
|
||||
|
||||
# Resolve the file path
|
||||
if not os.path.isabs(file_path):
|
||||
if subfolder:
|
||||
file_path = os.path.join(settings.workdir, subfolder, file_path)
|
||||
else:
|
||||
file_path = os.path.join(settings.workdir, file_path)
|
||||
return file_path
|
||||
resolved_path = (base_dir / file_path).resolve()
|
||||
else:
|
||||
resolved_path = Path(file_path).resolve()
|
||||
|
||||
# Ensure the resolved path is within the base directory (path traversal protection)
|
||||
try:
|
||||
resolved_path.relative_to(base_dir.resolve())
|
||||
except ValueError:
|
||||
# Path is outside the base directory - potential path traversal attack
|
||||
logger.warning(f"Path traversal attempt detected: {file_path} -> {resolved_path}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid file path: path traversal not allowed"
|
||||
)
|
||||
|
||||
return str(resolved_path)
|
||||
|
||||
Reference in New Issue
Block a user