Vulnerabilities in pkg:golang/code.vikunja.io/api

PACKAGE
Advisory Summary

GHSA-m687-p538-r5hp is a HIGH permissive cross-domain security policy trusts vulnerability (CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains) affecting Vikunja libraries in the Go ecosystem. The issue involves permissive cross-domain security policy trusts every localhost origin which should not be trusted. Remediation is available in the latest vendor maintenance releases.

81 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:golang/code.vikunja.io/api

81 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:golang/code.vikunja.io/api. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-m687-p538-r5hp - HIGH Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted
GHSA-fmmf-xq98-g327 - MODERATE Vikunja: Write-level project members can delete admin-tier link shares through an unloaded permission check
GHSA-pjr3-86v4-5p7w - MODERATE Vikunja: Explicit lower-permission share on a sub-project is silently overridden by an inherited parent permission (broken access control / privilege-management regression in v2.6.0)
GHSA-hjx8-qv73-f7cm - MODERATE Vikunja: Webhooks and link shares survive every revocation path, so a removed collaborator keeps a live feed
GHSA-fprf-r6rv-xg99 - MODERATE Vikunja: Saved filter creation with an empty filter string recalculates task positions across all tenants
GHSA-4hv6-xc92-j86g - MODERATE Vikunja: WebSocket authentication ignores server-side session state, so revoked sessions keep receiving live pushes
GHSA-w2ch-4xgr-22ww - LOW Vikunja: Task relation deletion does not check read access to the other task, allowing cross-project relation removal
GHSA-8wvg-r2j4-3737 - MODERATE Vikunja: Assignee email addresses disclosed to read-only project members via the task assignees endpoint
GHSA-3hc7-r24j-rpwc - MODERATE Vikunja: Cross-project task disclosure through subtask expansion
GHSA-g38j-7v97-x298 - MODERATE Vikunja: CalDAV relation creation bypasses TaskRelation.CanCreate, allowing an unauthorized write into any task by known UID
GHSA-wq92-8x3r-fm38 CVE-2026-91970 HIGH Vikunja: Planka migration retains an unbounded aggregate of attacker-served attachments and can OOM the API
GHSA-c2g7-39fr-cp8q - HIGH Duplicate Advisory: Vikunja: Planka migration retains an unbounded aggregate of attacker-served attachments and can OOM the API
GHSA-pqf9-h8g4-8gmh CVE-2026-91969 HIGH Vikunja: Unbounded CSV row cardinality permits API process termination
GHSA-55p4-8j34-jv53 - HIGH Duplicate Advisory: Vikunja: Unbounded CSV row cardinality permits API process termination
GHSA-xxc3-xpmc-vmvr CVE-2026-91968 HIGH Vikunja: Unbounded nested task-filter recursion permits API process termination
GHSA-jfw3-2gcf-23r5 - HIGH Duplicate Advisory: Vikunja: Unbounded nested task-filter recursion permits API process termination
GHSA-w7jp-mf2v-8342 CVE-2026-91979 HIGH Vikunja: Denial of service via decompression bomb in the data import
GHSA-vfxw-3x8p-2vjr CVE-2026-91981 MODERATE Vikunja: Link-share token can enumerate users through the v2 API
GHSA-vcgv-7rvm-m3vp - MODERATE Duplicate Advisory: Vikunja: Link-share token can enumerate users through the v2 API
GHSA-39p5-2wrr-xh29 CVE-2026-91980 MODERATE Vikunja: Any user can enumerate every team and its members by attaching arbitrary teams to a throwaway project
GHSA-2922-qgfj-pr7v - MODERATE Duplicate Advisory: Vikunja: Any user can enumerate every team and its members by attaching arbitrary teams to a throwaway project
GHSA-4vh2-39rq-rq8j CVE-2026-91971 HIGH Vikunja: Unbounded image decode on avatar and project-background uploads enables decode/resize amplification
GHSA-69rj-q3v9-vffp - HIGH Duplicate Advisory: Vikunja: Unbounded image decode on avatar and project-background uploads enables decode/resize amplification
GHSA-m469-88xx-8rx2 CVE-2026-91973 MODERATE Vikunja: CalDAV and feeds BasicAuth endpoints have no rate limit, bypassing the anti-brute-force floor on account passwords
GHSA-p2vv-wrh8-x466 - HIGH Duplicate Advisory: Vikunja: CalDAV and feeds BasicAuth endpoints have no rate limit, bypassing the anti-brute-force floor on account passwords
GHSA-9rg3-v78m-26q8 CVE-2026-91983 MODERATE Vikunja: API token scopes bypassed via task expand parameter (comments, reactions, time entry counts)
GHSA-phph-c358-5mwm - MODERATE Duplicate Advisory: Vikunja: API token scopes bypassed via task expand parameter (comments, reactions, time entry counts)
GHSA-w39f-h553-h2mx CVE-2026-91984 MODERATE Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2)
GHSA-ccgq-fx7h-2v4c - MODERATE Duplicate Advisory: Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2)
GHSA-qfwc-vx6f-3g6g CVE-2026-91985 HIGH Vikunja: Read-only project members can obtain any link share's access hash via the single-share read endpoint (v1 and v2) and escalate to the share's permission level
GHSA-37rx-7pj5-p885 - HIGH Duplicate Advisory: Vikunja: Read-only project members can obtain any link share's access hash via the single-share read endpoint (v1 and v2) and escalate to the share's permission level
GHSA-88f6-4rjv-x774 CVE-2026-91982 MODERATE Vikunja: TOTP secret is readable after enrollment, no step-up auth
GHSA-jmr8-r558-3jhx - MODERATE Duplicate Advisory: Vikunja: TOTP secret is readable after enrollment, no step-up auth
GHSA-6rvj-qwjf-3m4q CVE-2026-91972 HIGH Vikunja: Every /api/v2 pre-auth endpoint is unthrottled on a stock install while its /api/v1 twin is rate limited
GHSA-r6w9-259g-gwrv CVE-2026-62376 HIGH Vikunja: Plaintext storage of password-reset/email-confirm tokens in database enables account takeover on DB read access
GHSA-xv7q-fvmc-jx96 CVE-2026-62367 HIGH Vikunja: OIDC email-fallback account linking ignores email_verified, enabling local-account takeover
GHSA-32r8-5843-4qw2 CVE-2026-76216 HIGH Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure
GHSA-9xwc-vwww-qqmw - HIGH Duplicate Advisory: Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure
GHSA-rj9j-8772-4h6c CVE-2026-68582 CRITICAL Vikunja: Link-share token reads any tenant's kanban buckets and enumerates usernames/IDs instance-wide (BOLA)
GHSA-p4r4-8cxw-pjx7 - CRITICAL Duplicate Advisory: Vikunja: Link-share token reads any tenant's kanban buckets and enumerates usernames/IDs instance-wide (BOLA)
GHSA-vvcv-vpph-h844 CVE-2026-68581 HIGH Vikunja: Link-share principal ID collision allows cross-account API token issuance and management
GHSA-9jrx-vmh8-c6xw - HIGH Duplicate Advisory: Vikunja: Link-share principal ID collision allows cross-account API token issuance and management
GHSA-v3p6-34mc-hj7v CVE-2026-57458 HIGH Vikunja: Scoped API token can mint unrestricted OAuth session credentials
GHSA-569v-q83c-3j3g CVE-2026-55067 MODERATE Vikunja vulnerable to authenticated cross-tenant kanban-bucket relocation via `project_view_id` mass-assignment
GHSA-5pg6-m483-7vrg CVE-2026-55066 HIGH Vikunja has cross-tenant IDOR in kanban move-task endpoint via unauthorized body task_id
GHSA-gg93-x632-9ccv CVE-2026-55065 HIGH Vikunja vulnerable to Improper Authorization and Authorization Bypass Through User-Controlled Key
GHSA-44v6-7fxq-vgf4 CVE-2026-55064 MODERATE Vikunja has an incomplete fix for CVE-2026-35595: Write-only user can detach shared project from parent hierarchy via parent_project_id=0
GHSA-f27p-pw2p-9pr4 CVE-2026-54766 MODERATE Vikunja has a project duplication bypasses write-permission check on the target parent project
GHSA-hj5c-mhh2-g7jq CVE-2026-35596 MODERATE Vikunja has Broken Access Control on Label Read via SQL Operator Precedence Bug
GHSA-fgfv-pv97-6cmj CVE-2026-35597 MODERATE Vikunja Vulnerable to TOTP Brute-Force Due to Non-Functional Account Lockout
GHSA-96q5-xm3p-7m84 CVE-2026-35594 MODERATE Vikunja: Link Share JWT tokens remain valid for 72 hours after share deletion or permission downgrade
GHSA-8jvc-mcx6-r4cg CVE-2026-34727 HIGH Vikunja has TOTP Two-Factor Authentication Bypass via OIDC Login Path
GHSA-2g7h-7rqr-9p4r CVE-2026-35601 MODERATE Vikunja has iCalendar Property Injection via CRLF in CalDAV Task Output
GHSA-2vq4-854f-5c72 CVE-2026-35595 HIGH Vikunja vulnerable to Privilege Escalation via Project Reparenting
GHSA-v479-vf79-mg83 CVE-2026-40103 MODERATE Vikunja: Scoped API tokens with projects.background permission can delete project backgrounds
GHSA-qh78-rvg3-cv54 CVE-2026-35602 MODERATE Vikunja has File Size Limit Bypass via Vikunja Import
GHSA-45q4-x4r9-8fqj CVE-2026-35600 MODERATE Vikunja has HTML Injection via Task Titles in Overdue Email Notifications
GHSA-r4fg-73rc-hhh7 CVE-2026-35599 MODERATE Vikunja has Algorithmic Complexity DoS in Repeating Task Handler
GHSA-48ch-p4gq-x46x CVE-2026-35598 MODERATE Vikunja Missing Authorization on CalDAV Task Read
GHSA-8hp8-9fhr-pfm9 CVE-2026-33680 HIGH Vikjuna: Link Share Hash Disclosure via ReadAll Endpoint Enables Permission Escalation
GHSA-g9xj-752q-xh63 CVE-2026-33679 MODERATE Vikjuna Bypasses Webhook SSRF Protections During OpenID Connect Avatar Download
GHSA-2pv8-4c52-mf8j - CRITICAL Vikunja: Unauthenticated Instance-Wide Data Breach via Link Share Hash Disclosure Chained with Cross-Project Attachment IDOR
GHSA-f95f-77jx-fcjc CVE-2026-33700 MODERATE Vikunja has a Link Share Delete IDOR — Missing Project Ownership Check Allows Cross-Project Link Share Deletion
GHSA-jfmm-mjcp-8wq2 CVE-2026-33678 HIGH Vikjuna: IDOR in Task Attachment ReadOne Allows Cross-Project File Access and Deletion
GHSA-7c2g-p23p-4jg3 CVE-2026-33677 MODERATE Vikjuna: Webhook BasicAuth Credentials Exposed to Read-Only Project Collaborators via API
GHSA-8cmm-j6c4-rr8v CVE-2026-33676 MODERATE Vikunja has Cross-Project Information Disclosure via Task Relations — Missing Authorization Check on Related Task Read
GHSA-g66v-54v9-52pr CVE-2026-33675 MODERATE Vikunja has SSRF via Todoist/Trello Migration File Attachment URLs that Allows Reading Internal Network Resources
GHSA-94xm-jj8x-3cr4 CVE-2026-33668 HIGH Vikunja Allows Disabled/Locked User Accounts to Authenticate via API Tokens, CalDAV, and OpenID Connect
GHSA-wc83-79hj-hpmq CVE-2026-33474 MODERATE Vikunja Affected by DoS via Image Preview Generation
GHSA-vq4q-79hh-q767 CVE-2026-33316 HIGH Vikunja’s Improper Access Control Enables Bypass of Administrator-Imposed Account Disablement
GHSA-47cr-f226-r4pq CVE-2026-33315 MODERATE Vikunja has a 2FA Bypass via Caldav Basic Auth
GHSA-mr3j-p26x-72x4 CVE-2026-33313 MODERATE Vikunja has an IDOR in Task Comments Allows Reading Arbitrary Comments
GHSA-p747-qc5p-773r CVE-2026-33473 MODERATE Vikunja has TOTP Reuse During Validity Window
GHSA-m547-hp4w-j6jx CVE-2026-29794 MODERATE Vikunja has a Rate-Limit Bypass for Unauthenticated Users via Spoofed Headers
GHSA-564f-wx8x-878h CVE-2026-33312 MODERATE Vikunja read-only users can delete project background images via broken object-level authorization
GHSA-rfjg-6m84-crj2 CVE-2026-28268 CRITICAL Vikunja Vulnerable to Account Takeover via Password Reset Token Reuse
GHSA-7jp5-298q-jg98 CVE-2026-27616 HIGH Vikunja: Stored XSS via Unsanitized SVG Attachment Upload Leads to Token Exposure
GHSA-3ccg-x393-96v8 CVE-2026-27575 CRITICAL Vijkunja has Weak Password Policy Combined with Persistent Sessions After Password Change
GHSA-4qgr-4h56-8895 CVE-2026-27116 MODERATE Vikunja has Reflected HTML Injection via filter Parameter in its Projects Module
GHSA-42wg-38gx-85rh CVE-2026-27819 HIGH Vikunja has Path Traversal in CLI Restore
GHSA-m4g2-2q66-vc9v CVE-2026-25935 HIGH Vikunja Vulnerable to XSS Via Task Preview

GHSA-m687-p538-r5hp at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-m687-p538-r5hp
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains
Affected Product Vikunja
Package Ecosystem Go
Published Date
Last Updated

How to Fix GHSA-m687-p538-r5hp

To remediate this vulnerability, upgrade all affected Vikunja 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: Latest secure release
Upgrade code.vikunja.io/api to Latest secure release
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 GHSA-m687-p538-r5hp, inspect your project dependencies for Vikunja:

inspect dependencies for code.vikunja.io/api

Analyze the terminal output to locate any version of Vikunja in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), 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-942: Permissive Cross-domain Security Policy with Untrusted Domains, where the product uses a web-client protection mechanism such as a content security policy (csp) or cross-domain policy file, but the policy includes untrusted domains with which the web client is allowed to communicate. In Vikunja, 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 Vikunja ships with `cors.origins` defaulting to `http://127.0.0.1:*` and `http://localhost:*`, and sends `Access-Control-Allow-Credentials: true`, so a page served from any port on the user's own machine may make credentialed cross-origin requests to the API and read the responses. The mandatory `service.publicurl` is appended to that default list rather than replacing it, so a fully configured production deployment still trusts every localhost origin. Combined with the token refresh endpoint, which authenticates with the `vikunja_refresh_token` cookie and returns a bearer JWT in the response body, one `fetch()` from such a page yields a working access token for whoever is logged in, and from there the whole account. ## Vulnerability Details `cors.enable` defaults to true and `cors.origins` defaults to the two localhost wildcards: https://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/config/config.go#L495-L497 The middleware is registered with `AllowCredentials: true` and an `UnsafeAllowOriginFunc` that implements the port wildcard through `matchCORSOrigin`, since Echo v5 will not accept a wildcard port itself: https://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/routes/routes.go#L263-L281 The detail that turns a development convenience into a production exposure is the last line of configuration handling. Enabling CORS without a public URL is a fatal error, so every deployment sets one, and that value is *appended* to the origin list rather than substituted for it: https://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/config/config.go#L828-L830 An operator who configures nothing but their own hostname therefore ends up allowing three origins with credentials: their site, and any port on `localhost` and `127.0.0.1`. There is no supported configuration in which the localhost entries are quietly dropped, and nothing in the logs distinguishes the intended origin from the inherited ones. The escalation path is the refresh endpoint. It is deliberately unauthenticated because it authenticates with the refresh cookie instead of a bearer token, and it returns the new access token in the JSON body: https://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/routes/api/v1/login.go#L129-L150 The cookie is `HttpOnly`, but that offers no protection here, because the attacking page never reads the cookie. It issues a credentialed request and the browser attaches the cookie itself. `SetRefreshTokenCookie` marks the cookie `SameSite=None` whenever the public URL is `https`, precisely so the cookie survives split-origin deployments, which is also what allows a cross-site send from a localhost page: https://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/modules/auth/auth.go#L84-L104 So a single `fetch('https://vikunja.example.com/api/v1/user/token/refresh', {method: 'POST', credentials: 'include'})` from any page on any localhost port returns a bearer token for the logged-in user, and the CORS response headers permit the page to read it. The token carries the user's full API authority. ## Proof of Concept Three files, built together in one directory and run with: [poc.zip](https://github.com/user-attachments/files/31917334/poc.zip) ``` docker build -f Dockerfile -t poc . && docker run --rm poc ``` - `Dockerfile` builds Vikunja from commit `a881ac39eecd07575a5aede8e74727c28d5fd578` and bakes the reproducer in. - `poc.sh` is the entrypoint. It starts the application on sqlite with `service.publicurl` as the only setting configured, waits for the health check, and runs the exploit. - `exploit.py` registers a user, logs them in, then acts as a page on `http://localhost:31337`: it reads the refresh cookie attributes, sends the CORS preflight, makes the credentialed refresh request, and asks the API who the returned token belongs to. Nothing is stubbed and no internal function is called; everything after startup is ordinary HTTP against the running application. In the output `[+]` lines are measured checks and `[*]` lines are commentary, so only the checks and the final verdict are evidence. A run against `a881ac39eecd07575a5aede8e74727c28d5fd578` prints: ``` [*] Vikunja built from commit a881ac39ee, sqlite, service.publicurl=https://vikunja.example.com. [*] service.publicurl is the only setting configured. cors.enable and cors.origins keep their defaults. [+] The victim logs in and the refresh cookie is issued SameSite=None; Secure, so it is sent cross-site. [+] A page on http://localhost:31337 is allowed to send credentials: Access-Control-Allow-Origin: http://localhost:31337, Access-Control-Allow-Credentials: true [+] That page reads the response of a credentialed refresh, which carries a bearer token: eyJhbGciOiJIUzI1NiIsInR5cCI6... [+] The token authenticates as: 'victim' EXPLOIT SUCCESSFUL ``` ## Suggested Fix The fix is for `service.publicurl` to replace the localhost defaults rather than extend them, so that configuring a deployment narrows the allowed origins instead of widening them. If the localhost entries are retained deliberately for desktop clients, they should not be combined with `AllowCredentials: true`, since it is the combination that allows a third-party origin to read authenticated responses. ## Attribution This vulnerability was found using Google's security automation tooling, abd triaged manually with manual report writing by Ada Logics. Please credit Google and Ada Logics in any advisories.

GHSA-m687-p538-r5hp Affected Vikunja Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
code.vikunja.io/api
Go
≥ 2.2.0
≤ 2.6.0

References

Frequently Asked Questions

What is GHSA-m687-p538-r5hp?
GHSA-m687-p538-r5hp is a security vulnerability in Vikunja involving Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted under CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains.
Which Vikunja versions are affected?
Affected versions include releases prior to current release patches across affected packages: code.vikunja.io/api.
What version fixes GHSA-m687-p538-r5hp?
Upgrading to current release patches (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 Latest secure release. Upgrade your dependency to Latest secure release or later.
What CWE is associated with GHSA-m687-p538-r5hp?
GHSA-m687-p538-r5hp is classified under CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains (The product uses a web-client protection mechanism such as a Content Security Policy (CSP) or cross-domain policy file, but the policy includes untrusted domains with which the web client is allowed to communicate.).
How can I check Go dependencies for GHSA-m687-p538-r5hp?
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-m687-p538-r5hp",
  "modified" : "2026-10-09T20:57:50Z",
  "published" : "2026-10-09T20:57:50Z",
  "aliases" : [ ],
  "summary" : "Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted",
  "details" : "## Summary\n\nVikunja ships with `cors.origins` defaulting to `http://127.0.0.1:*` and `http://localhost:*`, and sends `Access-Control-Allow-Credentials: true`, so a page served from any port on the user's own machine may make credentialed cross-origin requests to the API and read the responses. The mandatory `service.publicurl` is appended to that default list rather than replacing it, so a fully configured production deployment still trusts every localhost origin. Combined with the token refresh endpoint, which authenticates with the `vikunja_refresh_token` cookie and returns a bearer JWT in the response body, one `fetch()` from such a page yields a working access token for whoever is logged in, and from there the whole account.\n\n## Vulnerability Details\n\n`cors.enable` defaults to true and `cors.origins` defaults to the two localhost wildcards:\n\nhttps://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/config/config.go#L495-L497\n\nThe middleware is registered with `AllowCredentials: true` and an `UnsafeAllowOriginFunc` that implements the port wildcard through `matchCORSOrigin`, since Echo v5 will not accept a wildcard port itself:\n\nhttps://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/routes/routes.go#L263-L281\n\nThe detail that turns a development convenience into a production exposure is the last line of configuration handling. Enabling CORS without a public URL is a fatal error, so every deployment sets one, and that value is *appended* to the origin list rather than substituted for it:\n\nhttps://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/config/config.go#L828-L830\n\nAn operator who configures nothing but their own hostname therefore ends up allowing three origins with credentials: their site, and any port on `localhost` and `127.0.0.1`. There is no supported configuration in which the localhost entries are quietly dropped, and nothing in the logs distinguishes the intended origin from the inherited ones.\n\nThe escalation path is the refresh endpoint. It is deliberately unauthenticated because it authenticates with the refresh cookie instead of a bearer token, and it returns the new access token in the JSON body:\n\nhttps://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/routes/api/v1/login.go#L129-L150\n\nThe cookie is `HttpOnly`, but that offers no protection here, because the attacking page never reads the cookie. It issues a credentialed request and the browser attaches the cookie itself. `SetRefreshTokenCookie` marks the cookie `SameSite=None` whenever the public URL is `https`, precisely so the cookie survives split-origin deployments, which is also what allows a cross-site send from a localhost page:\n\nhttps://github.com/go-vikunja/vikunja/blob/a881ac39eecd07575a5aede8e74727c28d5fd578/pkg/modules/auth/auth.go#L84-L104\n\nSo a single `fetch('https://vikunja.example.com/api/v1/user/token/refresh', {method: 'POST', credentials: 'include'})` from any page on any localhost port returns a bearer token for the logged-in user, and the CORS response headers permit the page to read it. The token carries the user's full API authority.\n\n## Proof of Concept\n\nThree files, built together in one directory and run with:\n\n[poc.zip](https://github.com/user-attachments/files/31917334/poc.zip)\n\n```\ndocker build -f Dockerfile -t poc . && docker run --rm poc\n```\n\n- `Dockerfile` builds Vikunja from commit `a881ac39eecd07575a5aede8e74727c28d5fd578` and bakes the reproducer in.\n- `poc.sh` is the entrypoint. It starts the application on sqlite with `service.publicurl` as the only setting configured, waits for the health check, and runs the exploit.\n- `exploit.py` registers a user, logs them in, then acts as a page on `http://localhost:31337`: it reads the refresh cookie attributes, sends the CORS preflight, makes the credentialed refresh request, and asks the API who the returned token belongs to.\n\nNothing is stubbed and no internal function is called; everything after startup is ordinary HTTP against the running application. In the output `[+]` lines are measured checks and `[*]` lines are commentary, so only the checks and the final verdict are evidence.\n\nA run against `a881ac39eecd07575a5aede8e74727c28d5fd578` prints:\n\n```\n[*] Vikunja built from commit a881ac39ee, sqlite, service.publicurl=https://vikunja.example.com.\n[*] service.publicurl is the only setting configured. cors.enable and cors.origins keep their defaults.\n\n[+] The victim logs in and the refresh cookie is issued SameSite=None; Secure, so it is sent cross-site.\n[+] A page on http://localhost:31337 is allowed to send credentials: Access-Control-Allow-Origin: http://localhost:31337, Access-Control-Allow-Credentials: true\n[+] That page reads the response of a credentialed refresh, which carries a bearer token: eyJhbGciOiJIUzI1NiIsInR5cCI6...\n[+] The token authenticates as: 'victim'\n\nEXPLOIT SUCCESSFUL\n```\n\n## Suggested Fix\n\nThe fix is for `service.publicurl` to replace the localhost defaults rather than extend them, so that configuring a deployment narrows the allowed origins instead of widening them. If the localhost entries are retained deliberately for desktop clients, they should not be combined with `AllowCredentials: true`, since it is the combination that allows a third-party origin to read authenticated responses.\n\n## Attribution\n\nThis vulnerability was found using Google's security automation tooling, abd triaged manually with manual report writing by Ada Logics. Please credit Google and Ada Logics in any advisories.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "code.vikunja.io/api"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.2.0"
      }, {
        "last_affected" : "2.6.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-m687-p538-r5hp"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/go-vikunja/vikunja"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-942" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-09T20:57:50Z",
    "nvd_published_at" : null
  }
}