Any authenticated user can clear server-wide brute-force lockouts via /api/auth/locked-ips

Author: carfeiiCreated Sep 15, 2026Updated Sep 15, 2026

Summary

hermes-studio's login-limiter locks out IPs that repeatedly fail password, token, or device-pairing authentication. DELETE /api/auth/locked-ips clears these lockouts (all of them, if no ip query parameter is given), and GET /api/auth/locked-ips lists which IPs are currently locked and their failure counts. Every other account-management route in the same router (/api/auth/users and its subroutes) requires the super_admin role via the requireSuperAdmin middleware; the two locked-ips routes are missing it, so any authenticated user, regardless of role, can list locked IPs and clear every brute-force lockout on the server at will.

Affected versions: confirmed on commit 51fd3c8 (current main).

Details

packages/server/src/modules/studio/routes/auth.ts, the full protected route table:

typescript
export const authProtectedRoutes = new Router()
authProtectedRoutes.post('/api/auth/setup', ctrl.setupPassword)
authProtectedRoutes.get('/api/auth/me', ctrl.currentUser)
authProtectedRoutes.post('/api/auth/change-password', ctrl.changePassword)
authProtectedRoutes.post('/api/auth/change-username', ctrl.changeUsername)
authProtectedRoutes.get('/api/auth/avatar', ctrl.getMyAvatar)
authProtectedRoutes.put('/api/auth/avatar', ctrl.updateMyAvatar)
authProtectedRoutes.delete('/api/auth/password', ctrl.removePassword)
authProtectedRoutes.get('/api/auth/users', requireSuperAdmin, ctrl.listManagedUsers)
authProtectedRoutes.post('/api/auth/users', requireSuperAdmin, ctrl.createManagedUser)
authProtectedRoutes.put('/api/auth/users/:id', requireSuperAdmin, ctrl.updateManagedUser)
authProtectedRoutes.delete('/api/auth/users/:id', requireSuperAdmin, ctrl.deleteManagedUser)
authProtectedRoutes.get('/api/auth/locked-ips', ctrl.listLockedIps)
authProtectedRoutes.delete('/api/auth/locked-ips', ctrl.unlockIpHandler)

Every user-management route requires super_admin. The two locked-ips routes, right below them, don't.

requireSuperAdmin (middleware/auth.ts):

typescript
export async function requireSuperAdmin(ctx: Context, next: Next): Promise<void> {
  if (ctx.state.user?.role !== 'super_admin') {
    ctx.status = 403
    ctx.body = { error: 'Super administrator privileges are required' }
    return
  }
  await next()
}

controllers/auth.ts, the handlers:

typescript
export async function listLockedIps(ctx: Context) {
  const locks = getLockedIps()
  ctx.body = { locks }
}

/**
 * DELETE /api/auth/locked-ips?ip=xxx
 * Unlock a specific IP. No ip param = unlock all.
 */
export async function unlockIpHandler(ctx: Context) {
  const ip = ctx.query.ip as string
  if (ip) {
    const found = unlockIp(ip)
    ...
  } else {
    unlockAll()
    ...
  }
}

services/auth/login-limiter.ts, unlockAll() resets every tracked lockout state in the process:

typescript
export function unlockAll(): number {
  const count = getLockedIps().length
  state.passwordIpMap = {}
  state.tokenIpMap = {}
  state.pairingIpMap = {}
  state.globalTotalFailures = 0
  state.globalLockedUntil = 0
  dirty = true
  persistStateSync()
  return count
}

POC

(available upon request)

Impact

Any user with a valid account on a hermes-studio instance, at any role, not just admin, can call DELETE /api/auth/locked-ips (no parameters) to instantly clear every IP lockout and failure counter the login-limiter is tracking for password, token, and device-pairing authentication, server-wide, and can repeat this as often as needed. This removes the server's only defense against online brute-force guessing of other accounts' passwords (including the super_admin account), letting an unprivileged member neutralize rate-limiting for themselves or an accomplice attacking any other account on the instance. GET /api/auth/locked-ips additionally discloses which IPs are currently rate-limited and how many failures they've accrued to any authenticated user.

Source: EKKOLearnAI/hermes-studio