Skip to content

Commit f2f33f6

Browse files
CopilotMte90
andcommitted
Fix security vulnerabilities: prevent path injection and stack trace exposure
Co-authored-by: Mte90 <403283+Mte90@users.noreply.github.com>
1 parent 243726a commit f2f33f6

File tree

2 files changed

+51
-15
lines changed

2 files changed

+51
-15
lines changed

main.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,13 @@ def api_create_project(request: CreateProjectRequest):
7272
project = get_or_create_project(request.path, request.name)
7373
return JSONResponse(project)
7474
except ValueError as e:
75+
# ValueError is expected for invalid inputs, safe to show message
7576
logger.warning(f"Validation error creating project: {e}")
76-
return JSONResponse({"error": str(e)}, status_code=400)
77+
return JSONResponse({"error": "Invalid project path"}, status_code=400)
7778
except RuntimeError as e:
79+
# RuntimeError may contain sensitive details, use generic message
7880
logger.error(f"Runtime error creating project: {e}")
79-
return JSONResponse({"error": str(e)}, status_code=500)
81+
return JSONResponse({"error": "Database operation failed"}, status_code=500)
8082
except Exception as e:
8183
logger.exception(f"Unexpected error creating project: {e}")
8284
return JSONResponse({"error": "Internal server error"}, status_code=500)
@@ -85,38 +87,52 @@ def api_create_project(request: CreateProjectRequest):
8587
@app.get("/api/projects")
8688
def api_list_projects():
8789
"""List all projects."""
90+
import logging
91+
logger = logging.getLogger(__name__)
8892
try:
8993
projects = list_projects()
9094
return JSONResponse(projects)
9195
except Exception as e:
92-
return JSONResponse({"error": str(e)}, status_code=500)
96+
logger.exception(f"Error listing projects: {e}")
97+
return JSONResponse({"error": "Failed to list projects"}, status_code=500)
9398

9499

95100
@app.get("/api/projects/{project_id}")
96101
def api_get_project(project_id: str):
97102
"""Get project details by ID."""
103+
import logging
104+
logger = logging.getLogger(__name__)
98105
try:
99106
project = get_project_by_id(project_id)
100107
if not project:
101108
return JSONResponse({"error": "Project not found"}, status_code=404)
102109
return JSONResponse(project)
103110
except Exception as e:
104-
return JSONResponse({"error": str(e)}, status_code=500)
111+
logger.exception(f"Error getting project: {e}")
112+
return JSONResponse({"error": "Failed to retrieve project"}, status_code=500)
105113

106114

107115
@app.delete("/api/projects/{project_id}")
108116
def api_delete_project(project_id: str):
109117
"""Delete a project and its database."""
118+
import logging
119+
logger = logging.getLogger(__name__)
110120
try:
111121
delete_project(project_id)
112122
return JSONResponse({"success": True})
123+
except ValueError as e:
124+
logger.warning(f"Project not found for deletion: {e}")
125+
return JSONResponse({"error": "Project not found"}, status_code=404)
113126
except Exception as e:
114-
return JSONResponse({"error": str(e)}, status_code=400)
127+
logger.exception(f"Error deleting project: {e}")
128+
return JSONResponse({"error": "Failed to delete project"}, status_code=500)
115129

116130

117131
@app.post("/api/projects/index")
118132
def api_index_project(request: IndexProjectRequest, background_tasks: BackgroundTasks):
119133
"""Index/re-index a project in the background."""
134+
import logging
135+
logger = logging.getLogger(__name__)
120136
try:
121137
project = get_project_by_id(request.project_id)
122138
if not project:
@@ -146,12 +162,15 @@ def index_callback():
146162

147163
return JSONResponse({"status": "indexing", "project_id": request.project_id})
148164
except Exception as e:
149-
return JSONResponse({"error": str(e)}, status_code=500)
165+
logger.exception(f"Error starting project indexing: {e}")
166+
return JSONResponse({"error": "Failed to start indexing"}, status_code=500)
150167

151168

152169
@app.post("/api/query")
153170
def api_query(request: QueryRequest):
154171
"""Query a project using semantic search (PyCharm-compatible)."""
172+
import logging
173+
logger = logging.getLogger(__name__)
155174
try:
156175
project = get_project_by_id(request.project_id)
157176
if not project:
@@ -175,12 +194,15 @@ def api_query(request: QueryRequest):
175194
"query": request.query
176195
})
177196
except Exception as e:
178-
return JSONResponse({"error": str(e)}, status_code=500)
197+
logger.exception(f"Error querying project: {e}")
198+
return JSONResponse({"error": "Query failed"}, status_code=500)
179199

180200

181201
@app.post("/api/code")
182202
def api_code_completion(request: CodeCompletionRequest):
183203
"""Get code suggestions using RAG + LLM (PyCharm-compatible)."""
204+
import logging
205+
logger = logging.getLogger(__name__)
184206
try:
185207
project = get_project_by_id(request.project_id)
186208
if not project:
@@ -220,15 +242,17 @@ def api_code_completion(request: CodeCompletionRequest):
220242
try:
221243
response = call_coding_model(request.prompt, combined_context)
222244
except Exception as e:
223-
return JSONResponse({"error": f"Coding model failed: {e}"}, status_code=500)
245+
logger.error(f"Coding model call failed: {e}")
246+
return JSONResponse({"error": "Code generation failed"}, status_code=500)
224247

225248
return JSONResponse({
226249
"response": response,
227250
"used_context": used_context,
228251
"project_id": request.project_id
229252
})
230253
except Exception as e:
231-
return JSONResponse({"error": str(e)}, status_code=500)
254+
logger.exception(f"Error in code completion: {e}")
255+
return JSONResponse({"error": "Code completion failed"}, status_code=500)
232256

233257

234258
@app.get("/api/health")

projects.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,28 @@ def create_project(project_path: str, name: Optional[str] = None) -> Dict[str, A
136136
if not project_path or not isinstance(project_path, str):
137137
raise ValueError("Project path must be a non-empty string")
138138

139+
# Check for path traversal attempts
140+
if ".." in project_path or project_path.startswith("~"):
141+
raise ValueError("Path traversal not allowed in project path")
142+
139143
try:
140-
project_path = os.path.abspath(project_path)
144+
# Normalize and validate path - prevents path traversal
145+
project_path = os.path.abspath(os.path.realpath(project_path))
141146
except Exception as e:
142147
raise ValueError(f"Invalid project path: {e}")
143148

144-
if not os.path.exists(project_path):
145-
raise ValueError(f"Project path does not exist: {project_path}")
146-
147-
if not os.path.isdir(project_path):
148-
raise ValueError(f"Project path is not a directory: {project_path}")
149+
# Additional validation: path must exist and be a directory
150+
try:
151+
if not os.path.exists(project_path):
152+
raise ValueError(f"Project path does not exist: {project_path}")
153+
154+
if not os.path.isdir(project_path):
155+
raise ValueError(f"Project path is not a directory: {project_path}")
156+
except (OSError, ValueError) as e:
157+
# Re-raise ValueError, wrap OSError
158+
if isinstance(e, ValueError):
159+
raise
160+
raise ValueError(f"Cannot access project path: {str(e)}")
149161

150162
# Generate project ID and database path
151163
project_id = _get_project_id(project_path)

0 commit comments

Comments
 (0)