Summary
The learning-path APIs use the client-chosen X-Learning-Path-Token as the only authorization secret, but the API accepts any token value with no length/entropy requirement. The route docstring merely advises callers to use "a random UUID or similar" — it is not enforced. Anyone who can guess or reuse a weak token (e.g. "test", "1234", an empty-ish string, or a token the client also echoes in URLs) can read and overwrite the path's data, which contains the user's full learning-path state.
Evidence
src/routes/main_routes.py:940-974 (create_learning_path route):
token = _extract_token(request)
if not token:
return jsonify({"error": f"'{_TOKEN_HEADER}' header is required."}), 400
...
Only the header's presence is checked. The docstring itself concedes the weakness:
X-Learning-Path-Token (required) - the secret token chosen by the
client (should be a random UUID or similar).
Read/update/progress routes (main_routes.py:993, 1026, 1164) likewise accept any non-empty token and compare it with secrets.compare_digest against the stored owner token (learning_path.py). There is no server-side token generation, no uuid validation, and no entropy floor.
Impact
- The security of every learning path rests entirely on an unvalidated client-supplied string.
- Paths created with short/guessable tokens are trivially readable and overwritable by third parties (the
GET route is rate-limited at 20/min but guessing a 4-char token is far below that threshold).
Suggested Fix
- Validate the token server-side: require a minimum length (e.g. >= 16) and optionally require UUID format (
uuid.UUID(token)), or generate the token server-side and return it to the client.
- Document the requirement in the route docstring and add a route-level test rejecting weak tokens.
Summary
The learning-path APIs use the client-chosen
X-Learning-Path-Tokenas the only authorization secret, but the API accepts any token value with no length/entropy requirement. The route docstring merely advises callers to use "a random UUID or similar" — it is not enforced. Anyone who can guess or reuse a weak token (e.g."test","1234", an empty-ish string, or a token the client also echoes in URLs) can read and overwrite the path's data, which contains the user's full learning-path state.Evidence
src/routes/main_routes.py:940-974(create_learning_pathroute):Only the header's presence is checked. The docstring itself concedes the weakness:
Read/update/progress routes (
main_routes.py:993,1026,1164) likewise accept any non-empty token and compare it withsecrets.compare_digestagainst the stored owner token (learning_path.py). There is no server-side token generation, nouuidvalidation, and no entropy floor.Impact
GETroute is rate-limited at 20/min but guessing a 4-char token is far below that threshold).Suggested Fix
uuid.UUID(token)), or generate the token server-side and return it to the client.