Opus
Path traversal in ProjectManager via unvalidated project_id
- backend/app/models/project.py:169-175
- backend/app/models/project.py:245-260
- backend/app/models/project.py:286-296
- backend/app/api/graph.py:39-53
ProjectManager methods (_get_project_dir, _get_project_meta_path, _get_project_files_dir, _get_project_text_path, delete_project, save_extracted_text, save_file_to_project) all build filesystem paths by os.path.join(PROJECTS_DIR, project_id) without any validation that project_id is a safe identifier. The route `GET /api/graph/project/<project_id>` and the POST body of `/api/graph/build` accept arbitrary project_id strings and forward them straight to ProjectManager.get_project. A caller passing `project_id='../../something'` (allowed because Flask's default `<string>` converter accepts dots and slashes? — actually `<string>` disallows `/`, but JSON bodies in `/build` have no such constraint, and `..` alone is allowed even by the URL converter) causes the server to read/write/shutil.rmtree files outside the projects directory. Unlike report.py and share.py which use `validate_simulation_id`, no analogous validate_project_id exists. delete_project is especially dangerous because it shutil.rmtree's a path derived from user input.
Recommendation
Add a `validate_project_id` helper (mirroring `utils.validation.validate_simulation_id`) that enforces a strict regex like `^proj_[a-z0-9]{12}$`, and call it at the entry of every API route that takes project_id and inside ProjectManager.get_project / delete_project / save_file_to_project as defense in depth.