{"record":{"id":"d0f5314a8f9b21bc","repo":"fullstackhero/dotnet-starter-kit","slug":"cross-tenant-audit-summary-requires-permissions-audittrails","errorCode":null,"errorMessage":"Cross-tenant audit summary requires Permissions.AuditTrails.ViewCrossTenant.","messagePattern":"Cross-tenant audit summary requires Permissions\\.AuditTrails\\.ViewCrossTenant\\.","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditSummary/GetAuditSummaryQueryHandler.cs","lineNumber":107,"sourceCode":"        var currentTenant = _currentUser.GetTenant();\n        var requested = string.IsNullOrWhiteSpace(query.TenantId) ? null : query.TenantId;\n\n        bool wantsCrossTenant =\n            requested is not null\n            && !string.Equals(requested, currentTenant, StringComparison.OrdinalIgnoreCase);\n\n        if (!wantsCrossTenant)\n        {\n            return _dbContext.AuditRecords.AsNoTracking();\n        }\n\n        var userId = _currentUser.GetUserId().ToString();\n        var allowed = await _permissions\n            .HasPermissionAsync(userId, AuditingPermissions.AuditTrails.ViewCrossTenant, ct)\n            .ConfigureAwait(false);\n        if (!allowed)\n        {\n            throw new ForbiddenException(\"Cross-tenant audit summary requires Permissions.AuditTrails.ViewCrossTenant.\");\n        }\n\n        return _dbContext.AuditRecords\n            .AsNoTracking()\n            .IgnoreQueryFilters()\n            .Where(a => a.TenantId == requested);\n    }\n\n    private (DateTime FromUtc, DateTime ToUtc) ResolveWindow(DateTime? from, DateTime? to)\n    {\n        var now = _timeProvider.GetUtcNow().UtcDateTime;\n        var resolvedTo = to ?? now;\n        var resolvedFrom = from ?? resolvedTo - DefaultWindow;\n\n        if (resolvedTo - resolvedFrom > MaxWindow)\n        {\n            resolvedFrom = resolvedTo - MaxWindow;\n        }","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditSummary/GetAuditSummaryQueryHandler.cs#L89-L125","documentation":"GetAuditSummaryQueryHandler.BuildBaseQueryAsync throws ForbiddenException when the caller lacks the AuditingPermissions.AuditTrails.ViewCrossTenant permission but requested a cross-tenant summary. The query needs IgnoreQueryFilters() to span tenants, which is only permitted for holders of that permission. Maps to HTTP 403.","triggerScenarios":"Calling the audit-summary endpoint with a cross-tenant request (tenant filter matching multiple tenants or 'all') while the authenticated user's permission set does not include ViewCrossTenant.","commonSituations":"Tenant admin attempting platform-wide reporting; role definitions missing the ViewCrossTenant claim after seeding; permission renamed/moved in a newer version so older roles lack it.","solutions":["Grant the AuditingPermissions.AuditTrails.ViewCrossTenant permission to the caller's role.","If cross-tenant scope was not intended, request the summary scoped to the caller's own tenant.","Re-run permission/claim seeding (DbMigrator --seed) so roles include the new permission.","Use a root-operator account for platform-wide audit summaries."],"exampleFix":"// before\nrole.Permissions = [Permissions.Users.View];\n\n// after\nrole.Permissions = [Permissions.Users.View, AuditingPermissions.AuditTrails.ViewCrossTenant];","handlingStrategy":"validation","validationCode":"bool canView = await permissions.HasPermissionAsync(userId, AuditingPermissions.AuditTrails.ViewCrossTenant, ct); if (!canView) scopeSummaryToOwnTenant();","typeGuard":null,"tryCatchPattern":"try { return await api.GetAuditSummary(request); } catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.Forbidden) { return Results.Forbid(); }","preventionTips":["Gate cross-tenant filters behind a permission check in the UI","Re-run permission seeding after adding new auditing permissions","Default summary queries to the caller's own tenant"],"tags":["authorization","multitenancy","permissions","auditing"],"backgroundTag":"permission-denied","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}