{"record":{"id":"cd1acb211aefa556","repo":"danny-avila/LibreChat","slug":"invalid-resource-id-resourceid-cd1acb","errorCode":null,"errorMessage":"Invalid resource ID: ${resourceId}","messagePattern":"Invalid resource ID: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/api/src/acl/accessControlService.ts","lineNumber":85,"sourceCode":"      }\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        (!principalId || !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 || !Types.ObjectId.isValid(resourceId)) {\n        throw new Error(`Invalid resource ID: ${resourceId}`);\n      }\n\n      this.validateResourceType(resourceType as ResourceType);\n\n      // Get the role to determine permission bits\n      const role = await this._dbMethods.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}`,\n        );\n      }\n      return await this._dbMethods.grantPermission(\n        principalType,","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/packages/api/src/acl/accessControlService.ts#L67-L103","documentation":"grantPermission requires resourceId to be present and a valid Mongo ObjectId (Types.ObjectId.isValid). This is checked for every grant regardless of principal type, since every ACL entry must anchor to a real resource document.","triggerScenarios":"grantPermission with resourceId undefined, null, an empty string, a slug, or any non-24-hex-char value; passing the resource's name or path instead of its _id.","commonSituations":"Client sends a resource slug or index instead of its Mongo id; a route parameter parsed incorrectly; the resource was deleted between fetch and grant so its id reference is stale.","solutions":["Resolve the resource's _id from the database and pass its toString().","Validate resourceId with Types.ObjectId.isValid at the route handler and return 400 on failure.","Ensure the resource still exists before granting permissions on it."],"exampleFix":"// before\nawait grantPermission({ ..., resourceId: agentSlug });\n\n// after\nimport { Types } from 'mongoose';\nif (!Types.ObjectId.isValid(agentSlug)) {\n  return res.status(400).json({ error: 'Invalid resource id' });\n}\nawait grantPermission({ ..., resourceId: agentSlug });","handlingStrategy":"validation","validationCode":"import { Types } from 'mongoose';\nfunction assertResourceId(v: unknown): string {\n  if (typeof v !== 'string' || !Types.ObjectId.isValid(v)) {\n    throw new Error(`Invalid resource ID: ${String(v)}`);\n  }\n  return v;\n}","typeGuard":"import { Types } from 'mongoose';\nconst isResourceId = (v: unknown): v is string =>\n  typeof v === 'string' && Types.ObjectId.isValid(v);","tryCatchPattern":null,"preventionTips":["Resolve the resource _id from the database and pass its toString().","Return 400 at the route handler for ids that fail ObjectId.isValid.","Confirm the resource still exists before granting on it."],"tags":["acl","validation","permissions","mongodb","objectid"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}