Any authenticated user can clear server-wide brute-force lockouts via /api/auth/locked-ips
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:
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):
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:
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:
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