diff --git a/.jules/sentinel.md b/.jules/sentinel.md index e0fa46b..97cf6fe 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,7 @@ **Vulnerability:** API endpoints in `backend/src/server.ts` taking user input (`projectId`, `jobId`) were directly joined with paths using `join` in `backend/src/store/localStore.ts` without proper sanitization. This allowed attackers to escape the project directory context and overwrite or read arbitrary files by sending payload containing `../` sequences. **Learning:** Even internal backend services handling project resources must securely sanitize all parameter values used for file operations to prevent path traversal outside expected boundaries. **Prevention:** Always use safe path sanitization utilities, like the implemented `safeJoin` and `toSafeRelativePath` in `backend/src/utils/path.ts`, to securely construct file paths and ensure the final path remains within the intended boundaries. +## 2024-05-24 - [Prototype Pollution in aiRouter] +**Vulnerability:** The `mergeExtraBody` function in `backend/src/services/aiRouter.ts` shallow-merged user-supplied JSON from API requests directly into a system-constructed payload object without sanitizing standard prototype injection keys. An attacker could provide `__proto__`, `constructor`, or `prototype` in their request's `extraBody`, allowing them to mutate the prototype chain of the request payload or surrounding objects, potentially causing Denial of Service or bypassing logic. +**Learning:** Even simple shallow merges of arbitrary user JSON over non-frozen objects expose a prototype poisoning risk in Node.js. +**Prevention:** Always explicitly block known prototype keys (`__proto__`, `constructor`, `prototype`) when iterating and applying properties from user-provided objects to internal system objects. diff --git a/backend/src/services/aiRouter.ts b/backend/src/services/aiRouter.ts index cf9cb2e..8a7d981 100644 --- a/backend/src/services/aiRouter.ts +++ b/backend/src/services/aiRouter.ts @@ -68,8 +68,8 @@ function isPlainObject(value: unknown): value is Record { function mergeExtraBody(payload: JsonObject, extraBody: unknown): void { if (!isPlainObject(extraBody)) return; - // Never allow overriding required core fields. - const blocked = new Set(["model", "messages", "stream"]); + // Never allow overriding required core fields or prototype pollution keys. + const blocked = new Set(["model", "messages", "stream", "__proto__", "constructor", "prototype"]); for (const [key, value] of Object.entries(extraBody)) { if (blocked.has(key)) continue; payload[key] = value as any;