diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..00c00ef --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,5 @@ +## 2024-05-05 - Unencoded URI Parameters in API Service + +**Vulnerability:** HTTP Parameter Pollution (HPP) and potential Path Traversal risks due to directly interpolating unencoded dynamic user inputs (e.g., `id`, `query`) into API URL strings in `src/services/apiService.js`. +**Learning:** In frontend API services constructed with Axios/fetch, injecting unsanitized input directly into template literals for URL paths or queries leaves endpoints vulnerable to injection. For instance, a search query containing `?` or `&` could alter the intended API request parameters, or an ID containing `../` could lead to traversing the backend's route structure. +**Prevention:** Always wrap dynamic user inputs used in URL paths or query string construction with `encodeURIComponent()` to ensure the input is treated as data, not as control characters or path separators. \ No newline at end of file diff --git a/src/services/apiService.js b/src/services/apiService.js index b8e3b23..c9cf9d5 100644 --- a/src/services/apiService.js +++ b/src/services/apiService.js @@ -3,25 +3,25 @@ import api from './api'; // Roadmaps API export const roadmapsApi = { getAll: () => api.get('/roadmaps'), - getById: (id) => api.get(`/roadmaps/${id}`), + getById: (id) => api.get(`/roadmaps/${encodeURIComponent(id)}`), }; // Topics API export const topicsApi = { - getByRoadmapId: (roadmapId) => api.get(`/topics/roadmap/${roadmapId}`), - getById: (id) => api.get(`/topics/${id}`), + getByRoadmapId: (roadmapId) => api.get(`/topics/roadmap/${encodeURIComponent(roadmapId)}`), + getById: (id) => api.get(`/topics/${encodeURIComponent(id)}`), }; // Lessons API export const lessonsApi = { - getByTopicId: (topicId) => api.get(`/lessons/topic/${topicId}`), - getById: (id) => api.get(`/lessons/${id}`), + getByTopicId: (topicId) => api.get(`/lessons/topic/${encodeURIComponent(topicId)}`), + getById: (id) => api.get(`/lessons/${encodeURIComponent(id)}`), }; // Progress API export const progressApi = { getAll: () => api.get('/progress'), - getByRoadmapId: (roadmapId) => api.get(`/progress/roadmap/${roadmapId}`), + getByRoadmapId: (roadmapId) => api.get(`/progress/roadmap/${encodeURIComponent(roadmapId)}`), markComplete: (lessonId) => api.post('/progress/complete', { lessonId }), markIncomplete: (lessonId) => api.post('/progress/incomplete', { lessonId }), }; @@ -29,23 +29,23 @@ export const progressApi = { // Notes API export const notesApi = { getAll: () => api.get('/notes'), - getByLessonId: (lessonId) => api.get(`/notes/lesson/${lessonId}`), + getByLessonId: (lessonId) => api.get(`/notes/lesson/${encodeURIComponent(lessonId)}`), create: (data) => api.post('/notes', data), - update: (id, data) => api.put(`/notes/${id}`, data), - delete: (id) => api.delete(`/notes/${id}`), + update: (id, data) => api.put(`/notes/${encodeURIComponent(id)}`, data), + delete: (id) => api.delete(`/notes/${encodeURIComponent(id)}`), }; // Comments API export const commentsApi = { - getByLessonId: (lessonId) => api.get(`/comments/lesson/${lessonId}`), + getByLessonId: (lessonId) => api.get(`/comments/lesson/${encodeURIComponent(lessonId)}`), create: (data) => api.post('/comments', data), - update: (id, data) => api.put(`/comments/${id}`, data), - delete: (id) => api.delete(`/comments/${id}`), + update: (id, data) => api.put(`/comments/${encodeURIComponent(id)}`, data), + delete: (id) => api.delete(`/comments/${encodeURIComponent(id)}`), }; // Search API export const searchApi = { - search: (query) => api.get(`/search?q=${query}`), + search: (query) => api.get(`/search?q=${encodeURIComponent(query)}`), }; // Auth API