Every CVE Search/ Get API key

CVE-2026-58271

@sync-in/server vulnerable to TOTP Brute-Force via `POST /api/app/sync/register`

6.8 MediumFix available

Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, POST /api/app/sync/register accepts credentials and a TOTP code to register a desktop sync client. On a failed TOTP, SyncClientsManager.register() calls updateAccesses(user, ip, false), which hits a freeze branch that writes passwordAttempts back unchanged. The counter never reaches USER_MAX_PASSWORD_ATTEMPTS (10) and the lockout gate never fires. A successful guess returns a {clientId, clientToken} pair. The token can be exchanged via POST /api/app/sync/auth/cookie for a full JWT. While the code is valid, the attacker can also call POST /api/auth/2fa/disable to permanently remove MFA. Version 2.4.0 patches the issue.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
None

Exploit likelihood

0.22%Unchanged since Sep 22, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.8

@sync-in/server vulnerable to TOTP Brute-Force via `POST /api/app/sync/register`

Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, POST /api/app/sync/register accepts credentials and a TOTP code to register a desktop sync client. On a failed TOTP, SyncClientsManager.register() calls updateAccesses(user, ip, false), which hits a freeze branch that writes passwordAttempts back unchanged. The counter never reaches USER_MAX_PASSWORD_ATTEMPTS (10) and the lockout gate never fires. A successful guess returns a {clientId, clientToken} pair. The token can be exchanged via POST /api/app/sync/auth/cookie for a full JWT. While the code is valid, the attacker can also call POST /api/auth/2fa/disable to permanently remove MFA. Version 2.4.0 patches the issue.

Record
CVE-2026-58271
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-307
Updated
2026-11-02
EUVDEUVD-2026-84112 6.8

Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, POST /api/app/sync/register accepts credentials and a TOTP code to register a desktop sync client. On a failed TOTP, SyncClientsManager.register() calls updateAccesses(user, ip, false), which hits a freeze branch that writes passwordAttempts back unchanged. The counter never reaches USER_MAX_PASSWORD_ATTEMPTS (10) and the lockout gate never fires. A successful guess returns a {clientId, clientToken} pair. The token can be exchanged via POST /api/app/sync/auth/cookie for a full JWT. While the code is valid, the attacker can also call POST /api/auth/2fa/disable to permanently remove MFA. Version 2.4.0 patches the issue.

Record
EUVD-2026-84112
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-274f-6w77-8qm9 6.8

@sync-in/server vulnerable to TOTP Brute-Force via `POST /api/app/sync/register`

Affected component: Sync-in Server v2.3.0, POST /api/app/sync/register.

Required attacker capability: Valid login and password for a TOTP-enabled account with desktop sync permission.

Summary

POST /api/app/sync/register accepts credentials and a TOTP code to register a desktop sync client. In the vulnerable version, on a failed TOTP attempt, SyncClientsManager.register() called updateAccesses(user, ip, false), which hit a freeze branch that wrote passwordAttempts back unchanged. The counter never reached USER_MAX_PASSWORD_ATTEMPTS (10), so the account lockout gate never fired for repeated TOTP failures through this endpoint.

A successful TOTP guess registers a sync client and returns a {clientId, clientToken} pair, provided the account has the required desktop app permission and the registration payload is valid. The token can then be exchanged via POST /api/app/sync/auth/cookie for an authenticated session. While the guessed TOTP code is still valid, and because the attacker already knows the password, the attacker can also call POST /api/auth/2fa/disable to remove MFA.

Details

The endpoint is declared at sync.controller.ts line 71. @AuthTokenSkip() bypasses the bearer-token guard, so the route is reachable without any prior session:

@Post(SYNC_ROUTE.REGISTER)
@AuthTokenSkip()
register(@Body() syncClientRegistrationDto: SyncClientRegistrationDto, @Req() req: FastifyRequest): Promise<SyncClientAuthRegistration> {
  return this.syncClientsManager.register(syncClientRegistrationDto, req.ip)
}

Inside SyncClientsManager.register(), after both the TOTP code and the recovery code are rejected, the handler fires a fire-and-forget access update and throws (sync-clients-manager.service.ts line 73):

this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error({ tag: this.register.name, msg: `${e}` }))
throw new HttpException(authCode.message, HttpStatus.UNAUTHORIZED)

In the vulnerable version, updateAccesses() at users-manager.service.ts line 182 defaulted isAuthTwoFa to false:

async updateAccesses(user: UserModel, ip: string, success: boolean, isAuthTwoFa = false) {
  let passwordAttempts: number
  if (!isAuthTwoFa && configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {
    passwordAttempts = user.passwordAttempts
  } else {
    passwordAttempts = success ? 0 : Math.min(user.passwordAttempts + 1, USER_MAX_PASSWORD_ATTEMPTS)
  }
  await this.usersQueries.updateUserOrGuest(user.id, {
    ...
    passwordAttempts: passwordAttempts,
    isActive: user.isActive && passwordAttempts < USER_MAX_PASSWORD_ATTEMPTS
  })
}

When register() called updateAccesses(user, ip, false), isAuthTwoFa defaulted to false. The condition on line 184 evaluated to true when TOTP was enabled site-wide and the account had it active. The else branch with Math.min(user.passwordAttempts + 1, ...) was never reached. passwordAttempts was written back unchanged, and the lockout gate in validateUserAccess() at line 89 never fired for repeated TOTP failures through this endpoint.

The freeze was designed for the web login flow, where a correct password at POST /api/auth/login produces a partial session and the counter should be preserved until POST /api/auth/2fa/login/verify completes. That route calls authProvider2FA.verify(body, req, true), which passes isAuthTwoFa=true into updateAccesses() and correctly increments on 2FA failure. The register() endpoint reused updateAccesses() for an outright TOTP rejection while passing the default isAuthTwoFa=false, triggering the freeze incorrectly.

The same freeze also applied to logUser() (users-manager.service.ts line 69), called by both POST /api/auth/login and POST /api/auth/token. On a wrong password for a 2FA-enabled account, updateAccesses(user, ip, false) was called without an isAuthTwoFa argument, so the freeze fired and…

Record
GHSA-274f-6w77-8qm9
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-307
Updated
2026-11-02
NVD 6.8

Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, POST /api/app/sync/register accepts credentials and a TOTP code to register a desktop sync client. On a failed TOTP, SyncClientsManager.register() calls updateAccesses(user, ip, false), which hits a freeze branch that writes passwordAttempts back unchanged. The counter never reaches USER_MAX_PASSWORD_ATTEMPTS (10) and the lockout gate never fires. A successful guess returns a {clientId, clientToken} pair. The token can be exchanged via POST /api/app/sync/auth/cookie for a full JWT. While the code is valid, the attacker can also call POST /api/auth/2fa/disable to permanently remove MFA. Version 2.4.0 patches the issue.

Record
CVE-2026-58271
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-307
Updated
2026-11-02
OSV 6.8

@sync-in/server vulnerable to TOTP Brute-Force via `POST /api/app/sync/register`

Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, POST /api/app/sync/register accepts credentials and a TOTP code to register a desktop sync client. On a failed TOTP, SyncClientsManager.register() calls updateAccesses(user, ip, false), which hits a freeze branch that writes passwordAttempts back unchanged. The counter never reaches USER_MAX_PASSWORD_ATTEMPTS (10) and the lockout gate never fires. A successful guess returns a {clientId, clientToken} pair. The token can be exchanged via POST /api/app/sync/auth/cookie for a full JWT. While the code is valid, the attacker can also call POST /api/auth/2fa/disable to permanently remove MFA. Version 2.4.0 patches the issue.

Record
CVE-2026-58271
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-307
Updated
2026-11-02
OSVGHSA-274f-6w77-8qm9 6.8

@sync-in/server vulnerable to TOTP Brute-Force via `POST /api/app/sync/register`

Affected component: Sync-in Server v2.3.0, POST /api/app/sync/register.

Required attacker capability: Valid login and password for a TOTP-enabled account with desktop sync permission.

Summary

POST /api/app/sync/register accepts credentials and a TOTP code to register a desktop sync client. In the vulnerable version, on a failed TOTP attempt, SyncClientsManager.register() called updateAccesses(user, ip, false), which hit a freeze branch that wrote passwordAttempts back unchanged. The counter never reached USER_MAX_PASSWORD_ATTEMPTS (10), so the account lockout gate never fired for repeated TOTP failures through this endpoint.

A successful TOTP guess registers a sync client and returns a {clientId, clientToken} pair, provided the account has the required desktop app permission and the registration payload is valid. The token can then be exchanged via POST /api/app/sync/auth/cookie for an authenticated session. While the guessed TOTP code is still valid, and because the attacker already knows the password, the attacker can also call POST /api/auth/2fa/disable to remove MFA.

Details

The endpoint is declared at sync.controller.ts line 71. @AuthTokenSkip() bypasses the bearer-token guard, so the route is reachable without any prior session:

@Post(SYNC_ROUTE.REGISTER)
@AuthTokenSkip()
register(@Body() syncClientRegistrationDto: SyncClientRegistrationDto, @Req() req: FastifyRequest): Promise<SyncClientAuthRegistration> {
  return this.syncClientsManager.register(syncClientRegistrationDto, req.ip)
}

Inside SyncClientsManager.register(), after both the TOTP code and the recovery code are rejected, the handler fires a fire-and-forget access update and throws (sync-clients-manager.service.ts line 73):

this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error({ tag: this.register.name, msg: `${e}` }))
throw new HttpException(authCode.message, HttpStatus.UNAUTHORIZED)

In the vulnerable version, updateAccesses() at users-manager.service.ts line 182 defaulted isAuthTwoFa to false:

async updateAccesses(user: UserModel, ip: string, success: boolean, isAuthTwoFa = false) {
  let passwordAttempts: number
  if (!isAuthTwoFa && configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {
    passwordAttempts = user.passwordAttempts
  } else {
    passwordAttempts = success ? 0 : Math.min(user.passwordAttempts + 1, USER_MAX_PASSWORD_ATTEMPTS)
  }
  await this.usersQueries.updateUserOrGuest(user.id, {
    ...
    passwordAttempts: passwordAttempts,
    isActive: user.isActive && passwordAttempts < USER_MAX_PASSWORD_ATTEMPTS
  })
}

When register() called updateAccesses(user, ip, false), isAuthTwoFa defaulted to false. The condition on line 184 evaluated to true when TOTP was enabled site-wide and the account had it active. The else branch with Math.min(user.passwordAttempts + 1, ...) was never reached. passwordAttempts was written back unchanged, and the lockout gate in validateUserAccess() at line 89 never fired for repeated TOTP failures through this endpoint.

The freeze was designed for the web login flow, where a correct password at POST /api/auth/login produces a partial session and the counter should be preserved until POST /api/auth/2fa/login/verify completes. That route calls authProvider2FA.verify(body, req, true), which passes isAuthTwoFa=true into updateAccesses() and correctly increments on 2FA failure. The register() endpoint reused updateAccesses() for an outright TOTP rejection while passing the default isAuthTwoFa=false, triggering the freeze incorrectly.

The same freeze also applied to logUser() (users-manager.service.ts line 69), called by both POST /api/auth/login and POST /api/auth/token. On a wrong password for a 2FA-enabled account, updateAccesses(user, ip, false) was called without an isAuthTwoFa argument, so the freeze fired and…

Record
GHSA-274f-6w77-8qm9
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-307
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
@sync-in/server
npm
through 2.3.0
2.4.0
@sync-in/server
npm
before 2.4.0
2.4.0

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-58271 is a medium vulnerability (CVSS 6.8) in Devolutions Server, published September 21, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.22%, the 12th percentile. Fixed in @sync-in/server 2.4.0.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open