bitwarden/server · error · BadRequestException
Resources must be unique
Error message
Resources must be unique
What it means
Thrown by AccessPolicyHelpers.CheckForDistinctAccessPolicies when the number of input access policies does not equal the number of distinct policies after de-duplication by (granteeId, grantedResourceId) tuple. This means the request contains duplicate access policy entries — the same user/group is granted access to the same project/secret/service-account more than once.
Source
Thrown at src/Api/SecretsManager/Utilities/AccessPolicyHelpers.cs:32
{
UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId),
UserSecretAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedSecretId),
UserServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId,
ap.GrantedServiceAccountId),
GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId),
GroupSecretAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedSecretId),
GroupServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedServiceAccountId),
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,
ap.GrantedProjectId),
ServiceAccountSecretAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,
ap.GrantedSecretId),
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)),
};
}).ToList();
if (accessPolicies.Count != distinctAccessPolicies.Count)
{
throw new BadRequestException("Resources must be unique");
}
}
public static void CheckAccessPoliciesHaveReadPermission(IEnumerable<BaseAccessPolicy> accessPolicies)
{
var accessPoliciesPermission = accessPolicies.All(policy => policy.Read);
if (!accessPoliciesPermission)
{
throw new BadRequestException("Resources must be Read = true");
}
}
}
View on GitHub (pinned to e93b962371)
Solutions
- De-duplicate the access policy list by (granteeId, grantedResourceId) before submitting.
- Inspect the request payload for repeated entries and remove duplicates.
- If using a UI, ensure the add-access form prevents adding the same user/group twice for the same resource.
Example fix
// before: duplicate user+project pair
var policies = new[] {
new { OrgUserId = uid, GrantedProjectId = pid, Read = true },
new { OrgUserId = uid, GrantedProjectId = pid, Read = true } // duplicate
};
// after: distinct pairs only
var policies = new[] {
new { OrgUserId = uid, GrantedProjectId = pid, Read = true }
}; Defensive patterns
Strategy: validation
Validate before calling
// De-duplicate access policies by (grantee, granted) before submission
var distinct = policies
.GroupBy(p => GetPolicyKey(p))
.Select(g => g.First())
.ToList();
// GetPolicyKey returns (granteeId, grantedResourceId) per policy type
await client.SetAccessPoliciesAsync(distinct); Type guard
static bool HasDistinctAccessPolicies(IEnumerable<BaseAccessPolicy> policies)
=> policies.Count() == policies.DistinctBy(GetPolicyKey).Count(); Try / catch
try { await client.SetAccessPoliciesAsync(policies); }
catch (ApiException ex) when (ex.Message.Contains("must be unique"))
{
var deduped = policies.GroupBy(GetPolicyKey).Select(g => g.First()).ToList();
await client.SetAccessPoliciesAsync(deduped);
} Prevention
- Always de-duplicate access policy collections by (grantee, granted resource) before submit.
- Prevent duplicate entries in UI selectors (disable already-added users/groups).
- Merge policy lists from multiple sources with a union-by-key step.
When it happens
Trigger: An access policy bulk-create/update request includes the same (organizationUserId, grantedProjectId) or (groupId, grantedSecretId) pair multiple times — e.g., the client sends two entries for the same user on the same project.
Common situations: Frontend form resubmits and appends duplicates; merging access policy lists from multiple sources without de-duplicating; copy-paste error in policy JSON.
Related errors
- Machine account access must be Can read, write
- Resources must be Read = true
- No version IDs provided.
- Last synced date must be in the past.
- Only service accounts can sync secrets.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/ada9ac4635c56016.
Report an issue: GitHub.