Summary
GET /api/learning-path/<path_id>/analytics returns HTTP 500 when a stored learning path contains a malformed progress value. create_path/update_path accept arbitrary client JSON (no schema validation), and the analytics handler only catches ValueError/TypeError — a non-dict progress value raises an uncaught AttributeError.
Evidence
src/routes/main_routes.py:1182-1198:
for pid_str, stats in progress.items():
try:
project = find_project_by_id(int(pid_str))
if not project:
continue
est = project.get("estimated_hours", 0)
act = stats.get("actual_hours", 0) # AttributeError if stats is not a dict
total_est += est
total_act += act
if stats.get("completed"):
comp_est += est
comp_act += act
except (ValueError, TypeError):
continue
stats comes from progress = path_data.get("progress", {}), where path_data is whatever the client stored via create_path (line 937) / update_path (line 1009) — the payload is JSON-parsed and saved without validating that each progress value is a dict. If a value is, e.g., a string or list, stats.get(...) raises AttributeError, which the except (ValueError, TypeError) does not catch → unhandled → 500.
Impact
- A single malformed entry in any stored learning path makes the analytics endpoint 500 for that path.
- The 500 leaks through the global error handler instead of returning a 4xx; the stored data came from the client, so a user can trivially trigger it for their own path (and it breaks the "Learning Velocity" UI).
Suggested Fix
- Add
AttributeError to the caught exceptions, or validate the shape of each stats value (require a dict) before calling .get().
- Validate the
progress structure at write time (create_path/update_path) so bad payloads are rejected with 400 instead of stored.
Summary
GET /api/learning-path/<path_id>/analyticsreturns HTTP 500 when a stored learning path contains a malformedprogressvalue.create_path/update_pathaccept arbitrary client JSON (no schema validation), and the analytics handler only catchesValueError/TypeError— a non-dictprogressvalue raises an uncaughtAttributeError.Evidence
src/routes/main_routes.py:1182-1198:statscomes fromprogress = path_data.get("progress", {}), wherepath_datais whatever the client stored viacreate_path(line 937) /update_path(line 1009) — the payload is JSON-parsed and saved without validating that eachprogressvalue is a dict. If a value is, e.g., a string or list,stats.get(...)raisesAttributeError, which theexcept (ValueError, TypeError)does not catch → unhandled → 500.Impact
Suggested Fix
AttributeErrorto the caught exceptions, or validate the shape of eachstatsvalue (require a dict) before calling.get().progressstructure at write time (create_path/update_path) so bad payloads are rejected with 400 instead of stored.