Vikunja has Rate-Limit Bypass for Unauthenticated Users via Spoofed Headers
Description
Vikunja is an open-source self-hosted task management platform. Starting in version 0.8 and prior to version 2.2.0, unauthenticated users are able to bypass the application's built-in rate-limits by spoofing the X-Forwarded-For or X-Real-IP headers due to the rate-limit relying on the value of (echo.Context).RealIP. Unauthenticated users can abuse endpoints available to them for different potential impacts. The immediate concern would be brute-forcing usernames or specific accounts' passwords. This bypass allows unlimited requests against unauthenticated endpoints. Version 2.2.0 patches the issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Vikunja task management platform v0.8 to v2.1 allows unauthenticated users to bypass IP-based rate limits by spoofing X-Forwarded-For or X-Real-IP headers, enabling brute-force attacks.
Vulnerability
Overview
CVE-2026-29794 is a rate-limit bypass vulnerability in the Vikunja open-source task management platform affecting versions 0.8 through 2.1.x. The root cause lies in the rate-limiting middleware relying on Echo's RealIP() function, which trusts the X-Forwarded-For and X-Real-IP headers without validation [1][3]. When no reverse proxy overwrites these headers, an attacker can spoof them to reset their perceived IP address for each request, effectively evading the per-IP rate limits [3]. The flaw exists in the unauthenticated routes group, where rate limits are keyed by the IP address obtained from c.RealIP() [3].
Exploitation
An unauthenticated attacker can send requests to any publicly accessible endpoint—such as login, registration, or password reset forms—while arbitrarily manipulating the X-Forwarded-For header in each request [1][3]. Because the rate limiter uses the spoofed value as the client identifier, the attacker is not throttled and can fire unlimited requests. This bypass does not require any authentication or prior access [1].
Impact
The direct consequence is that an attacker can perform unlimited brute-force attempts against usernames and passwords, increasing the risk of account compromise [1]. Additionally, other unauthenticated endpoints could be flooded, potentially leading to resource exhaustion or information disclosure, though the description highlights password brute-forcing as the primary concern [1][2].
Mitigation
The issue is fixed in Vikunja version 2.2.0, released on 2026-03-20 [1][2]. The patch configures Echo's IPExtractor to either extract the IP directly from the TCP connection (ignoring client-supplied headers) or to validate those headers against a list of trusted proxy CIDR ranges [4]. Administrators are strongly encouraged to update immediately. Users running a reverse proxy that overwrites the X-Forwarded-For or X-Real-IP headers (e.g., Traefik) are partially protected, but full mitigation requires upgrading [3].
AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
code.vikunja.io/apiGo | >= 0.8, < 2.2.0 | 2.2.0 |
Affected products
2- go-vikunja/vikunjav5Range: >= 0.8, < 2.2.0
Patches
1a498dd69915afix: configure Echo IPExtractor to prevent rate limit bypass via spoofed headers
1 file changed · +40 −0
pkg/routes/routes.go+40 −0 modified@@ -54,6 +54,7 @@ package routes import ( "context" "log/slog" + "net" "strings" "time" @@ -125,6 +126,24 @@ func NewEcho() *echo.Echo { }), }) + // Configure IP extraction to prevent rate limit bypass via spoofed headers. + // Echo's default RealIP() trusts X-Forwarded-For and X-Real-IP unconditionally, + // which allows attackers to bypass IP-based rate limits. + // See: https://echo.labstack.com/docs/ip-address + switch config.ServiceIPExtractionMethod.GetString() { + case "xff": + trustOptions := parseTrustedProxies(config.ServiceTrustedProxies.GetString()) + e.IPExtractor = echo.ExtractIPFromXFFHeader(trustOptions...) + log.Debugf("IP extraction: X-Forwarded-For with %d trusted proxy ranges", len(trustOptions)) + case "realip": + trustOptions := parseTrustedProxies(config.ServiceTrustedProxies.GetString()) + e.IPExtractor = echo.ExtractIPFromRealIPHeader(trustOptions...) + log.Debugf("IP extraction: X-Real-IP with %d trusted proxy ranges", len(trustOptions)) + default: + e.IPExtractor = echo.ExtractIPDirect() + log.Debugf("IP extraction: direct (TCP remote address)") + } + e.Logger = log.NewEchoLogger(config.LogEnabled.GetBool(), config.LogHTTP.GetString(), config.LogFormat.GetString()) // Logger @@ -181,6 +200,27 @@ func NewEcho() *echo.Echo { return e } +func parseTrustedProxies(proxies string) []echo.TrustOption { + if proxies == "" { + return nil + } + + var options []echo.TrustOption + for _, cidr := range strings.Split(proxies, ",") { + cidr = strings.TrimSpace(cidr) + if cidr == "" { + continue + } + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + log.Warningf("Invalid trusted proxy CIDR %q: %v", cidr, err) + continue + } + options = append(options, echo.TrustIPRange(ipNet)) + } + return options +} + func setupSentry(e *echo.Echo) { if !config.SentryEnabled.GetBool() { return
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-m547-hp4w-j6jxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-29794ghsaADVISORY
- github.com/go-vikunja/vikunja/commit/a498dd69915a006c07e9d82660a2185d7e8136eeghsaWEB
- github.com/go-vikunja/vikunja/security/advisories/GHSA-m547-hp4w-j6jxghsax_refsource_CONFIRMWEB
- vikunja.io/changelog/vikunja-v2.2.0-was-releasedghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.