{"record":{"id":"e2687dc96d23f06d","repo":"bitwarden/server","slug":"only-service-accounts-can-sync-secrets","errorCode":null,"errorMessage":"Only service accounts can sync secrets.","messagePattern":"Only service accounts can sync secrets\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/SecretsManager/Controllers/SecretsController.cs","lineNumber":325,"sourceCode":"\n    [HttpGet(\"/organizations/{organizationId}/secrets/sync\")]\n    public async Task<SecretsSyncResponseModel> GetSecretsSyncAsync([FromRoute] Guid organizationId,\n        [FromQuery] DateTime? lastSyncedDate = null)\n    {\n        if (lastSyncedDate.HasValue && lastSyncedDate.Value > DateTime.UtcNow)\n        {\n            throw new BadRequestException(\"Last synced date must be in the past.\");\n        }\n\n        if (!_currentContext.AccessSecretsManager(organizationId))\n        {\n            throw new NotFoundException();\n        }\n\n        var (accessClient, serviceAccountId) = await _accessClientQuery.GetAccessClientAsync(User, organizationId);\n        if (accessClient != AccessClientType.ServiceAccount)\n        {\n            throw new BadRequestException(\"Only service accounts can sync secrets.\");\n        }\n\n        var syncRequest = new SecretsSyncRequest\n        {\n            AccessClientType = accessClient,\n            OrganizationId = organizationId,\n            ServiceAccountId = serviceAccountId,\n            LastSyncedDate = lastSyncedDate\n        };\n        var syncResult = await _secretsSyncQuery.GetAsync(syncRequest);\n\n        if (syncResult.HasChanges)\n        {\n            await LogSecretsEventAsync(syncResult.Secrets, EventType.Secret_Retrieved);\n        }\n\n        return new SecretsSyncResponseModel(syncResult.HasChanges, syncResult.Secrets);\n    }","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/SecretsManager/Controllers/SecretsController.cs#L307-L343","documentation":"Thrown at SecretsController.cs:325 inside GetSecretsSyncAsync. The IAccessClientQuery resolved the caller's accessClient to something other than AccessClientType.ServiceAccount, so the controller throws BadRequestException(\"Only service accounts can sync secrets.\") -> HTTP 400. The sync endpoint is machine-only: it is designed to be driven by a service account access token, not a human user session.","triggerScenarios":"A human user (user session/token, IdentityClientType.User) calls GET /organizations/{org}/secrets/sync instead of a service account. The endpoint exists for machine synchronization.","commonSituations":"Reusing a user OAuth token in a CI sync script instead of a service account token; misconfigured machine agent authenticated as a user; UI/CLI accidentally pointing the sync flow at a user credential.","solutions":["Create a service account in Secrets Manager and use its access token for sync calls.","Switch the client to authenticate with the service-account credential flow.","If a user-driven list is needed, use the regular secrets list endpoints instead of /sync."],"exampleFix":"// before: user token on a machine-only endpoint\nawait userClient.GetAsync($\"/organizations/{org}/secrets/sync\"); // 400\n\n// after: authenticate as a service account\nvar saClient = new HttpClient { DefaultRequestHeaders = Authorization = ServiceAccountAuth(saToken) };\nawait saClient.GetAsync($\"/organizations/{org}/secrets/sync\");","handlingStrategy":"validation","validationCode":"// Only call /sync with a service-account token\nif (await GetIdentityClientTypeAsync(token) != AccessClientType.ServiceAccount)\n    throw new InvalidOperationException(\"Sync requires a service account\");\nawait saClient.GetAsync($\"/organizations/{orgId}/secrets/sync\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Provision a dedicated service account for sync workloads.","Never reuse a user OAuth token for machine sync.","Configure the agent to authenticate via the service-account flow."],"tags":["csharp","aspnet","bitwarden","secrets-manager","service-account","validation","sync","api"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}