{"record":{"id":"1eb36c3df312df20","repo":"danny-avila/LibreChat","slug":"invalid-principal-id-principalid","errorCode":null,"errorMessage":"Invalid principal ID: ${principalId}","messagePattern":"Invalid principal ID: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/server/services/PermissionService.js","lineNumber":96,"sourceCode":"    }\n\n    if (principalType !== PrincipalType.PUBLIC && !principalId) {\n      throw new Error('Principal ID is required for user, group, and role principals');\n    }\n\n    // Validate principalId based on type\n    if (principalId && principalType === PrincipalType.ROLE) {\n      // Role IDs are strings (role names)\n      if (typeof principalId !== 'string' || principalId.trim().length === 0) {\n        throw new Error(`Invalid role ID: ${principalId}`);\n      }\n    } else if (\n      principalType &&\n      principalType !== PrincipalType.PUBLIC &&\n      !mongoose.Types.ObjectId.isValid(principalId)\n    ) {\n      // User and Group IDs must be valid ObjectIds\n      throw new Error(`Invalid principal ID: ${principalId}`);\n    }\n\n    if (!resourceId || !mongoose.Types.ObjectId.isValid(resourceId)) {\n      throw new Error(`Invalid resource ID: ${resourceId}`);\n    }\n\n    validateResourceType(resourceType);\n\n    // Get the role to determine permission bits\n    const role = await db.findRoleByIdentifier(accessRoleId);\n    if (!role) {\n      throw new Error(`Role ${accessRoleId} not found`);\n    }\n\n    // Ensure the role is for the correct resource type\n    if (role.resourceType !== resourceType) {\n      throw new Error(\n        `Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}`,","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/services/PermissionService.js#L78-L114","documentation":"grantPermission() in PermissionService.js:96 throws this for USER and GROUP principals when principalId is present but is not a valid MongoDB ObjectId (mongoose.Types.ObjectId.isValid returns false). This guards the ACL store against malformed ids before the existence lookup.","triggerScenarios":"principalType is USER or GROUP and principalId is a non-ObjectId string (e.g. an email, a username, a UUID, or a truncated id). Fires before the user/group existence checks, so it specifically catches shape errors rather than missing records.","commonSituations":"Caller passes an email or username instead of the user _id. A UUID-based external id was used where Mongo ObjectId is required. A copy/paste truncated the 24-char hex string.","solutions":["Resolve usernames/emails to the user's Mongo _id before calling grantPermission.","For USER/GROUP, pass the full 24-char ObjectId hex string from the user/group record.","Validate the id shape on the client (24 hex chars) before submitting."],"exampleFix":"// before\ngrantPermission({ principalType: PrincipalType.USER, principalId: 'alice@example.com', ... });\n// after\nconst user = await db.findUser({ email: 'alice@example.com' }, '_id');\ngrantPermission({ principalType: PrincipalType.USER, principalId: user._id.toString(), ... });","handlingStrategy":"validation","validationCode":"function assertObjectId(id) {\n  if (!mongoose.Types.ObjectId.isValid(id)) {\n    throw new Error(`not a valid ObjectId: ${id}`);\n  }\n}","typeGuard":"const isValidObjectId = (id) => mongoose.Types.ObjectId.isValid(id);","tryCatchPattern":null,"preventionTips":["Resolve usernames/emails to Mongo _id before calling grantPermission.","Validate the 24-hex-char shape on the client before submitting."],"tags":["permissions","acl","mongodb","validation"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}