{"record":{"id":"326de6ce6b9e5393","repo":"bitwarden/server","slug":"one-or-more-collections-do-not-belong-to-this-orga","errorCode":null,"errorMessage":"One or more collections do not belong to this organization.","messagePattern":"One or more collections do not belong to this organization\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"bitwarden_license/src/Services/Pam/Services/AccessRuleWriteValidator.cs","lineNumber":79,"sourceCode":"\n    private async Task<List<Guid>> ValidateCollectionsAsync(Guid organizationId, IEnumerable<Guid> collectionIds,\n        Guid? existingRuleId)\n    {\n        var distinctIds = collectionIds.Distinct().ToList();\n        if (distinctIds.Count == 0)\n        {\n            return distinctIds;\n        }\n\n        var collections = await _collectionRepository.GetManyByManyIdsAsync(distinctIds);\n        if (collections.Count != distinctIds.Count)\n        {\n            throw new BadRequestException(\"One or more collections could not be found.\");\n        }\n\n        if (collections.Any(c => c.OrganizationId != organizationId))\n        {\n            throw new BadRequestException(\"One or more collections do not belong to this organization.\");\n        }\n\n        // Deletes clear Collection.AccessRuleId and the FK forbids dangling links, so any set link points at an\n        // existing rule; only a link to a different rule is a conflict. A rule being created has no id, so for it\n        // any link at all conflicts.\n        if (collections.Any(c => c.AccessRuleId.HasValue && c.AccessRuleId != existingRuleId))\n        {\n            throw new BadRequestException(\"One or more collections are already governed by another access rule.\");\n        }\n\n        return distinctIds;\n    }\n}\n","sourceCodeStart":61,"sourceCodeEnd":93,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Services/Pam/Services/AccessRuleWriteValidator.cs#L61-L93","documentation":"Thrown by AccessRuleWriteValidator.ValidateCollectionsAsync (line 79) when all collections exist but at least one belongs to a different organization (c.OrganizationId != organizationId). This prevents cross-organization collection assignment to an access rule.","triggerScenarios":"The collectionIds list includes one or more Collection GUIDs whose OrganizationId differs from the organizationId the access rule belongs to. The check uses collections.Any(c => c.OrganizationId != organizationId).","commonSituations":"Admin has access to multiple orgs and picks a collection from the wrong org; collection IDs are shared across orgs in a multi-tenant dev/staging setup; API client sends collection IDs from a different org's context.","solutions":["Filter collection IDs to only those belonging to the same organization before submission.","Query collections by organization: _collectionRepository.GetManyByOrganizationIdAsync(orgId) and intersect with the requested IDs.","Audit the API client to ensure it scopes collection lookups to the correct organizationId."],"exampleFix":"// before — pass raw collection IDs that may span orgs\nawait _validator.ValidateAsync(orgId, rule, requestedCollectionIds);\n// after — filter to same-org collections first\nvar orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(orgId);\nvar orgCollectionIds = orgCollections.Select(c => c.Id).ToHashSet();\nvar safeIds = requestedCollectionIds.Where(id => orgCollectionIds.Contains(id));\nawait _validator.ValidateAsync(orgId, rule, safeIds);","handlingStrategy":"validation","validationCode":"var orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(organizationId);\nvar orgIds = orgCollections.Select(c => c.Id).ToHashSet();\nvar crossOrg = collectionIds.Where(id => !orgIds.Contains(id)).ToList();\nif (crossOrg.Count > 0)\n    return BadRequest($\"Collections do not belong to org {organizationId}: {string.Join(\", \", crossOrg)}\");","typeGuard":null,"tryCatchPattern":"try { await _validator.ValidateAsync(orgId, rule, collectionIds); }\ncatch (BadRequestException ex) when (ex.Message.Contains(\"do not belong\"))\n{ /* scope collection picker to current org */ }","preventionTips":["Scope the collection picker UI to the current organization only.","Never share collection IDs across org contexts in API clients.","Add org-scoping filters at the API gateway or service layer."],"tags":["pam","access-rule","collections","organization","multi-tenant","validation"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}