diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 9bd8528..4106ace 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -1,4 +1,8 @@ ## 2025-02-21 - Path Traversal in Mix Endpoint API Parameter **Vulnerability:** The `/projects/{project_id}/mix` API endpoint in `src/audioformation/server/routes.py` accepted a `music` parameter (meant to specify a filename within the `05_MUSIC/generated` directory) but directly passed it to `mix_project` without sanitization. This allowed directory traversal payloads like `../../../etc/passwd` to be used for background music resolution. **Learning:** Even internal API inputs that map strictly to filenames inside an expected directory must be sanitized. A simple check for file existence (`if not bg_music_path.exists():`) is insufficient as it confirms existence but allows looking outside the bounded directory. -**Prevention:** Always use established sanitization helpers (like `sanitize_filename`) or bound checks (like `validate_path_within`) for any user-supplied string that forms part of a filesystem path. Ensure bypass parameters like `FORCE_NO_MUSIC` are handled before and mutually exclusively from sanitization. \ No newline at end of file +**Prevention:** Always use established sanitization helpers (like `sanitize_filename`) or bound checks (like `validate_path_within`) for any user-supplied string that forms part of a filesystem path. Ensure bypass parameters like `FORCE_NO_MUSIC` are handled before and mutually exclusively from sanitization. +## 2025-02-21 - Exception and Parameter Data Leakage in API Endpoints +**Vulnerability:** Several API endpoints in `src/audioformation/server/routes.py` included raw user input or internal exception strings (`e`) directly within `HTTPException` detail responses. This could leak internal stack/state or echo unescaped user input back to the client. +**Learning:** Returning unescaped dynamic parameters or internal exception details directly via HTTP responses bypasses defense-in-depth and violates secure error handling standards. Log the specifics on the backend instead. +**Prevention:** Always log specific error details (`logger.error()`) internally, and return generic, static error messages to the client within `HTTPException`. diff --git a/src/audioformation/server/routes.py b/src/audioformation/server/routes.py index 34c9bec..0d71fd7 100644 --- a/src/audioformation/server/routes.py +++ b/src/audioformation/server/routes.py @@ -109,9 +109,8 @@ async def create_new_project(request: ProjectCreateRequest): """Create a new project.""" project_id = request.id if project_exists(project_id): - raise HTTPException( - status_code=409, detail=f"Project '{project_id}' already exists." - ) + logger.warning(f"Project '{project_id}' already exists.") + raise HTTPException(status_code=409, detail="Project already exists.") try: path = create_project(project_id) @@ -185,7 +184,8 @@ async def ingest_files( shutil.copyfileobj(file.file, buffer) except Exception as e: shutil.rmtree(tmp_dir, ignore_errors=True) - raise HTTPException(status_code=500, detail=f"Upload failed: {e}") + logger.error(f"Upload failed: {e}") + raise HTTPException(status_code=500, detail="Upload failed") background_tasks.add_task( _run_with_status, @@ -244,9 +244,8 @@ async def preview_voice(project_id: str, request: PreviewRequest): try: engine = registry.get(request.engine) except KeyError: - raise HTTPException( - status_code=400, detail=f"Engine '{request.engine}' not found" - ) + logger.warning(f"Engine '{request.engine}' not found") + raise HTTPException(status_code=400, detail="Engine not found") # Resolve reference audio if present ref_path = None @@ -739,7 +738,8 @@ async def list_engine_voices(name: str, lang: Optional[str] = None): voices = await engine.list_voices(language=lang) return voices except KeyError: - raise HTTPException(status_code=404, detail=f"Engine '{name}' not found") + logger.warning(f"Engine '{name}' not found") + raise HTTPException(status_code=404, detail="Engine not found") except Exception as e: logger.error(f"Failed to list voices for engine {name}: {e}") raise HTTPException(status_code=500, detail="Internal server error")