bitwarden/server · error · BadRequestException

Last synced date must be in the past.

Error message

Last synced date must be in the past.

What it means

Thrown at SecretsController.cs:314 inside GetSecretsSyncAsync (GET /organizations/{org}/secrets/sync). When the lastSyncedDate query parameter is supplied and is greater than DateTime.UtcNow, the controller throws BadRequestException("Last synced date must be in the past."). The filter maps this to HTTP 400 with that exact message. The sync protocol needs a past anchor to compute incremental changes.

Source

Thrown at src/Api/SecretsManager/Controllers/SecretsController.cs:314

        var authorizationResult = await _authorizationService.AuthorizeAsync(User, secrets, BulkSecretOperations.ReadAll);
        if (!authorizationResult.Succeeded)
        {
            throw new NotFoundException();
        }

        await LogSecretsEventAsync(secrets, EventType.Secret_Retrieved);

        var responses = secrets.Select(s => new BaseSecretResponseModel(s));
        return new ListResponseModel<BaseSecretResponseModel>(responses);
    }

    [HttpGet("/organizations/{organizationId}/secrets/sync")]
    public async Task<SecretsSyncResponseModel> GetSecretsSyncAsync([FromRoute] Guid organizationId,
        [FromQuery] DateTime? lastSyncedDate = null)
    {
        if (lastSyncedDate.HasValue && lastSyncedDate.Value > DateTime.UtcNow)
        {
            throw new BadRequestException("Last synced date must be in the past.");
        }

        if (!_currentContext.AccessSecretsManager(organizationId))
        {
            throw new NotFoundException();
        }

        var (accessClient, serviceAccountId) = await _accessClientQuery.GetAccessClientAsync(User, organizationId);
        if (accessClient != AccessClientType.ServiceAccount)
        {
            throw new BadRequestException("Only service accounts can sync secrets.");
        }

        var syncRequest = new SecretsSyncRequest
        {
            AccessClientType = accessClient,
            OrganizationId = organizationId,
            ServiceAccountId = serviceAccountId,

View on GitHub (pinned to e93b962371)

Solutions

  1. Send lastSyncedDate as UTC and clamp it to no later than DateTime.UtcNow minus a small safety margin before the request.
  2. Synchronize the client clock (NTP) to remove skew.
  3. If you have no prior sync point, omit lastSyncedDate to request a full sync.

Example fix

// before: forward an unvalidated timestamp
var url = $"/organizations/{org}/secrets/sync?lastSyncedDate={lastSynced:o}";

// after: clamp to the past in UTC
var safe = DateTime.SpecifyKind(Math.Min(lastSynced, DateTime.UtcNow.AddSeconds(-5)), DateTimeKind.Utc);
var url = $"/organizations/{org}/secrets/sync?lastSyncedDate={safe:o}";
Defensive patterns

Strategy: validation

Validate before calling

// Clamp lastSyncedDate to the past in UTC before calling sync
if (lastSyncedDate.HasValue) {
    lastSyncedDate = DateTime.SpecifyKind(
        Math.Min(lastSyncedDate.Value, DateTime.UtcNow.AddSeconds(-5)), DateTimeKind.Utc);
}

Prevention

When it happens

Trigger: Calling the sync endpoint with a lastSyncedDate query string set to a future timestamp (clock skew, or a client that computed the value incorrectly).

Common situations: Client machine clock is ahead of the server; timestamp generated with the wrong timezone/offset producing a future UTC value; sending local time instead of UTC.

Related errors


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