bitwarden/server · error · NotFoundException

Resource not found.

Error message

Resource not found.

What it means

Thrown at SecretsManagerEventsController.cs:45 inside GetServiceAccountEventsAsync (GET sm/events/service-accounts/{id}). The IAuthorizationService.AuthorizeAsync for ServiceAccountOperations.ReadEvents returned Succeeded=false, so the controller throws NotFoundException (authorization-based 404). Note the serviceAccount loaded just above may be null and is passed straight into AuthorizeAsync, so a missing service account also funnels here.

Source

Thrown at src/Api/SecretsManager/Controllers/SecretsManagerEventsController.cs:45

        IAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
        _serviceAccountRepository = serviceAccountRepository;
        _eventRepository = eventRepository;
    }

    [HttpGet("sm/events/service-accounts/{serviceAccountId}")]
    public async Task<ListResponseModel<EventResponseModel>> GetServiceAccountEventsAsync(Guid serviceAccountId,
        [FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null,
        [FromQuery] string continuationToken = null)
    {
        var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId);
        var authorizationResult =
            await _authorizationService.AuthorizeAsync(User, serviceAccount, ServiceAccountOperations.ReadEvents);

        if (!authorizationResult.Succeeded)
        {
            throw new NotFoundException();
        }

        var dateRange = ApiHelpers.GetDateRange(start, end);

        var result = await _eventRepository.GetManyByOrganizationServiceAccountAsync(serviceAccount.OrganizationId,
            serviceAccount.Id, dateRange.Item1, dateRange.Item2,
            new PageOptions { ContinuationToken = continuationToken });
        var responses = result.Data.Select(e => new EventResponseModel(e));
        return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
    }
}

View on GitHub (pinned to e93b962371)

Solutions

  1. Ensure the caller has ReadEvents permission on the service account (org admin or granted access policy).
  2. Confirm the service account id is current and belongs to an org the caller can access.
  3. Fetch the service account list first to validate the id before requesting its events.

Example fix

// before: assume access to any service account's events
await client.GetAsync($"/sm/events/service-accounts/{saId}"); // 404

// after: verify the id is in the caller's readable service accounts
var visible = await ListVisibleServiceAccountsAsync();
if (visible.Any(sa => sa.Id == saId))
    await client.GetAsync($"/sm/events/service-accounts/{saId}");
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the service account id is visible to the caller first
var visible = await ListVisibleServiceAccountsAsync();
if (!visible.Any(sa => sa.Id == serviceAccountId))
    throw new UnauthorizedAccessException("Cannot read events for this service account");
await client.GetAsync($"/sm/events/service-accounts/{serviceAccountId}");

Try / catch

try { await client.GetAsync($"/sm/events/service-accounts/{id}"); }
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { Log.Warn("Events access denied or SA missing"); }

Prevention

When it happens

Trigger: GET /sm/events/service-accounts/{id} by a caller without permission to read that service account's events, or where the service account id does not exist.

Common situations: User is not an admin/manager of the service account's organization; service account id is stale/deleted; principal lacks the events-read access policy.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/35d27951e425cfc5. Report an issue: GitHub.