GHSA-6RVJ-QWJF-3M4Q
Vulnerability from github – Published: 2026-10-09 20:51 – Updated: 2026-10-09 20:51Summary
registerAPIRoutesV2 never applies the unconditional pre-auth rate-limit floor (unauthRateLimit()) to the v2 public routes — it passes that limiter only to /api/v2/ws — and otherwise relies on setupRateLimit, which registers nothing when ratelimit.enabled is false (the default). So on a stock install every v2 pre-auth endpoint (login, register, password-reset token, oauth token) is unthrottled, while its v1 twin is throttled.
Details
unauthRateLimit() -> perMinuteIPRateLimit("noauth", RateLimitNoAuthRoutesLimit) (pkg/routes/rate_limit.go, ~lines 100-118) is an unconditional per-IP floor (default 10/60s) that deliberately ignores RateLimitEnabled, which is why v1's pre-auth routes are throttled even with the global limiter off. v1 applies it: ur := a.Group(""); ur.Use(unauthRateLimit()) (pkg/routes/routes.go ~line 459). registerAPIRoutesV2 (~lines 405-431) passes the unauthRateLimit() instance only to /api/v2/ws; its auth routes get only setupRateLimit(a, ...), which is config-gated and registers nothing by default.
PoC (verified at runtime against v2.5.0)
POST /api/v1/login x25 -> 429 from attempt 5
POST /api/v2/login x25 -> 403 x25, 429 x0
POST /api/v1/user/password/token -> 429 (throttled)
POST /api/v2/user/password/token x20 -> 404 x20, 429 x0
Request bodies are byte-identical across versions (shared user.Login / user.PasswordTokenRequest). Both v2 endpoints reach their handlers (403/404, not route-404), so the comparison is valid.
Impact
The pre-auth rate-limit floor — the instance's only default anti-brute-force / anti-abuse control — is absent on all v2 public endpoints. Enables unbounded credential guessing, account-enumeration probing, and password-reset flooding on a default install. Reported as an authentication-control bypass, not a DoS.
Fix
Apply unauthRateLimit() to the v2 public route group, matching v1.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.5.0"
},
"package": {
"ecosystem": "Go",
"name": "code.vikunja.io/api"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.6.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-91972"
],
"database_specific": {
"cwe_ids": [
"CWE-307"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-09T20:51:05Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n`registerAPIRoutesV2` never applies the unconditional pre-auth rate-limit floor (`unauthRateLimit()`) to the v2 public routes \u2014 it passes that limiter only to `/api/v2/ws` \u2014 and otherwise relies on `setupRateLimit`, which registers nothing when `ratelimit.enabled` is false (the default). So on a stock install every v2 pre-auth endpoint (login, register, password-reset token, oauth token) is unthrottled, while its v1 twin is throttled.\n\n### Details\n`unauthRateLimit()` -\u003e `perMinuteIPRateLimit(\"noauth\", RateLimitNoAuthRoutesLimit)` (`pkg/routes/rate_limit.go`, ~lines 100-118) is an unconditional per-IP floor (default 10/60s) that deliberately ignores `RateLimitEnabled`, which is why v1\u0027s pre-auth routes are throttled even with the global limiter off. v1 applies it: `ur := a.Group(\"\"); ur.Use(unauthRateLimit())` (`pkg/routes/routes.go` ~line 459). `registerAPIRoutesV2` (~lines 405-431) passes the `unauthRateLimit()` instance only to `/api/v2/ws`; its auth routes get only `setupRateLimit(a, ...)`, which is config-gated and registers nothing by default.\n\n### PoC (verified at runtime against v2.5.0)\n```\nPOST /api/v1/login x25 -\u003e 429 from attempt 5\nPOST /api/v2/login x25 -\u003e 403 x25, 429 x0\nPOST /api/v1/user/password/token -\u003e 429 (throttled)\nPOST /api/v2/user/password/token x20 -\u003e 404 x20, 429 x0\n```\nRequest bodies are byte-identical across versions (shared `user.Login` / `user.PasswordTokenRequest`). Both v2 endpoints reach their handlers (403/404, not route-404), so the comparison is valid.\n\n### Impact\nThe pre-auth rate-limit floor \u2014 the instance\u0027s only default anti-brute-force / anti-abuse control \u2014 is absent on all v2 public endpoints. Enables unbounded credential guessing, account-enumeration probing, and password-reset flooding on a default install. Reported as an authentication-control bypass, not a DoS.\n\n### Fix\nApply `unauthRateLimit()` to the v2 public route group, matching v1.",
"id": "GHSA-6rvj-qwjf-3m4q",
"modified": "2026-10-09T20:51:05Z",
"published": "2026-10-09T20:51:05Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-6rvj-qwjf-3m4q"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-91972"
},
{
"type": "PACKAGE",
"url": "https://github.com/go-vikunja/vikunja"
},
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/releases/tag/v2.6.0"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/vikunja-before-2.6.0-authentication-bypass-via-unthrottled-api"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Vikunja: Every /api/v2 pre-auth endpoint is unthrottled on a stock install while its /api/v1 twin is rate limited"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.