{"record":{"id":"d177158501afd3f4","repo":"bitwarden/server","slug":"resource-not-found-d17715","errorCode":null,"errorMessage":"Resource not found.","messagePattern":"Resource not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Api/Dirt/Controllers/ReportsController.cs","lineNumber":66,"sourceCode":"        _getPasskeyDirectoryQuery = getPasskeyDirectoryQuery;\n        _logger = logger;\n    }\n\n    /// <summary>\n    /// Organization member information containing a list of cipher ids\n    /// assigned\n    /// </summary>\n    /// <param name=\"orgId\">Organzation Id</param>\n    /// <returns>IEnumerable of MemberCipherDetailsResponseModel</returns>\n    /// <exception cref=\"NotFoundException\">If Access reports permission is not assigned</exception>\n    [HttpGet(\"member-cipher-details/{orgId}\")]\n    public async Task<IEnumerable<MemberCipherDetailsResponseModel>> GetMemberCipherDetails(Guid orgId)\n    {\n        // Using the AccessReports permission here until new permissions\n        // are needed for more control over reports\n        if (!await _currentContext.AccessReports(orgId))\n        {\n            throw new NotFoundException();\n        }\n\n        var riskDetails = await GetRiskInsightsReportDetails(new RiskInsightsReportRequest { OrganizationId = orgId });\n\n        var responses = riskDetails.Select(x => new MemberCipherDetailsResponseModel(x));\n\n        return responses;\n    }\n\n    /// <summary>\n    /// Access details for an organization member. Includes the member information,\n    /// group collection assignment, and item counts\n    /// </summary>\n    /// <param name=\"orgId\">Organization Id</param>\n    /// <returns>IEnumerable of MemberAccessReportResponseModel</returns>\n    /// <exception cref=\"NotFoundException\">If Access reports permission is not assigned</exception>\n    [HttpGet(\"member-access/{orgId}\")]\n    public async Task<IEnumerable<MemberAccessDetailReportResponseModel>> GetMemberAccessReport(Guid orgId)","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Dirt/Controllers/ReportsController.cs#L48-L84","documentation":"Thrown by GetMemberCipherDetails when the calling user lacks the AccessReports permission for the given orgId. Bitwarden deliberately returns 404 (NotFoundException with no message) instead of 403 to avoid leaking the existence of the organization or the endpoint to unauthorized users. The permission check _currentContext.AccessReports(orgId) returns false.","triggerScenarios":"GET /reports/member-cipher-details/{orgId} called by a user who is not an org admin/owner, or whose role does not include the AccessReports permission, or who is not a member of the organization at all.","commonSituations":"A custom-role user whose permissions were recently trimmed; a service account token that was provisioned without reports access; a user removed from the org but still holding a stale session; testing against the wrong orgId.","solutions":["Grant the AccessReports permission to the user's role in the organization admin panel.","Verify the calling user is an active member of the target organization.","If using a service account or API key, ensure it was created with reports-scoped access.","Confirm the orgId in the URL matches an organization the user actually belongs to."],"exampleFix":"// before\nvar details = await api.GetMemberCipherDetailsAsync(orgId);\n\n// after — check access first\nvar perms = await api.GetMyPermissionsAsync(orgId);\nif (!perms.Contains(\"access_reports\"))\n{\n    // surface a clear auth error instead of hitting the opaque 404\n    throw new UnauthorizedAccessException(\"AccessReports permission required.\");\n}\nvar details = await api.GetMemberCipherDetailsAsync(orgId);","handlingStrategy":"validation","validationCode":"// Check permissions before calling the report endpoint\nvar access = await _currentContext.AccessReports(orgId);\nif (!access)\n    return Forbid(\"AccessReports permission is required.\");\nvar details = await _reportQuery.GetMemberCipherDetailsAsync(orgId);","typeGuard":"public static bool HasReportsAccess(CurrentContext ctx, Guid orgId) =>\n    ctx.Permissions.TryGetValue(orgId, out var p) && p.AccessReports;","tryCatchPattern":"try\n{\n    var details = await _reportService.GetMemberCipherDetailsAsync(orgId);\n}\ncatch (NotFoundException)\n{\n    // 404 may mean either no access or no data — check permissions separately\n    if (!await _currentContext.AccessReports(orgId))\n        return Forbid();\n    return NotFound();\n}","preventionTips":["Verify the user's role includes AccessReports before calling report endpoints.","Cache the user's permission set client-side and disable report UI when missing.","Use service accounts with explicit reports-scoped access for API integrations."],"tags":["reports","authorization","permissions","not-found","csharp","aspnet"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}