diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 0ae11818..bb15b6d6 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -15,3 +15,7 @@ **Vulnerability:** Internal system error messages (`error.originalError.message`) were leaked in API 500 error responses in `apps/data-service/src/hono/routes/demo.ts`. **Learning:** Returning unhandled database or internal system errors directly to the client can leak sensitive information about the backend architecture. **Prevention:** Always sanitize error messages returned in JSON responses. Log detailed errors on the server, but return generic messages (e.g. `error.message` of a custom domain error rather than the original system error) to the client. +## 2025-03-18 - [Fix Privilege Escalation Vulnerability in updateRole] +**Vulnerability:** Tenants can modify system roles because `updateRole` lacks an `isSystemRole` check. +**Learning:** System roles (roles with `isSystemRole: true`) must be protected from modification by individual tenants to prevent privilege escalation. +**Prevention:** Always check `isSystemRole` and throw a validation error before updating or deleting a role. diff --git a/packages/data-ops/src/queries/school-admin/roles.ts b/packages/data-ops/src/queries/school-admin/roles.ts index 4d777d42..a1bf885b 100644 --- a/packages/data-ops/src/queries/school-admin/roles.ts +++ b/packages/data-ops/src/queries/school-admin/roles.ts @@ -124,6 +124,12 @@ export function updateRole( throw new DatabaseError('NOT_FOUND', SCHOOL_ERRORS.ROLE_NOT_FOUND) } + const role = roleResult.value + + if (role.isSystemRole) { + throw new DatabaseError('VALIDATION_ERROR', 'Cannot modify system roles') + } + const [updated] = await db .update(roles) .set({