VoidAuth: The Second Factor Can Be Brute-Forced Without Limit
The TOTP check keeps no per-account counter, and the only throttle hangs on an address the caller sets themselves.
Advisory ID: TP-2026-096
Product: VoidAuth (self-hosted authentication service and identity provider, TypeScript)
Vulnerability type: Improper restriction of authentication attempts (CWE-307)
CVE: requested (GitHub CNA), assignment pending
CVSS 3.1: 6.8 (Medium) · CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
Affected versions: <= 1.15.0
Vendor advisory: GHSA-cjxc-rf3w-gjx2
Fixed in: 1.15.1
Reported: 18 July 2026
Summary
VoidAuth verifies the second factor at a dedicated endpoint that keeps neither a per-account counter nor a lockout after repeated failures. The only throttle is a global rate limit keyed on the caller's address. The application trusts the proxy header, so that address comes from the leftmost entry of X-Forwarded-For, which the caller supplies. Anyone who changes that entry on every request gets a fresh counter each time and submits codes without limit. Since the check allows a window of one step, three codes are valid at any moment, so roughly 333,000 attempts suffice on average.
Root cause
POST /api/interaction/totp verifies the second factor through validateTOTP, which permits a one-step window and keeps no failed-attempt counter (server/db/totp.ts:75-91). The matching handler adds no per-account lockout (server/routes/interaction.ts:906). The only throttle is standardRateLimit and sensitiveRateLimit, both keyed on the default req.ip (server/util/rateLimit.ts:7-23). Because app.enable('trust proxy') is set, req.ip equals the leftmost, caller-supplied entry of X-Forwarded-For (server/cli/server.ts:36). The gate checkPrivilegedForTotpValidate requires only a completed first factor, so the endpoint is reachable with a password-only session.
Proof of Concept
# the attacker knows the victim's password and has passed the first factor.
for i in $(seq 0 999999); do
curl -s https://voidauth.example/api/interaction/totp \
-H "X-Forwarded-For: 10.0.$((RANDOM%255)).$((RANDOM%255))" \
-H 'Content-Type: application/json' \
-b "$SESSION" \
--data "{\"code\":\"$(printf '%06d' $i)\"}"
done
# every request carries a new address and therefore a fresh counter.
# three codes are valid at any moment, so about 333,000 attempts suffice.
Not affected is a setup where a reverse proxy overwrites X-Forwarded-For with the real client address instead of appending to it.
Impact
- Defeat of multi-factor authentication for any account that enrolled a TOTP authenticator.
- A fully authenticated session for an attacker who holds only the victim's password.
- The default configuration is affected, both on direct network access and behind any proxy that appends to
X-Forwarded-For, which matches the usual nginx pattern. - No signal to the victim, since neither a lockout nor a failed-attempt counter exists.
References
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.
