Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -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.
26 changes: 13 additions & 13 deletions src/services/apiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,49 @@ 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 }),
};

// 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
Expand Down