CVE-2026-111922
Vikunja 2.2.0 before 2.7.0 Permissive CORS Policy Trusting Localhost Origins
Vikunja from 2.2.0 before 2.7.0 contains a permissive CORS configuration that trusts every localhost and 127.0.0.1 port with credentials, even when service.publicurl is set. Attackers controlling a page on any localhost port can send a credentialed request to the token refresh endpoint and read the returned bearer JWT to take over the logged-in user's account.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
7.2
Vikunja 2.2.0 before 2.7.0 Permissive CORS Policy Trusting Localhost Origins
Vikunja from 2.2.0 before 2.7.0 contains a permissive CORS configuration that trusts every localhost and 127.0.0.1 port with credentials, even when service.publicurl is set. Attackers controlling a page on any localhost port can send a credentialed request to the token refresh endpoint and read the returned bearer JWT to take over the logged-in user's account.
- Record
CVE-2026-111922- CVSS 4.0
- 7.2 High
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 - CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N - Weakness
- CWE-942
- Updated
- 2026-11-02
EUVDEUVD-2026-103290
7.2
Vikunja from 2.2.0 before 2.7.0 contains a permissive CORS configuration that trusts every localhost and 127.0.0.1 port with credentials, even when service.publicurl is set. Attackers controlling a page on any localhost port can send a credentialed request to the token refresh endpoint and read the returned bearer JWT to take over the logged-in user's account.
- Record
EUVD-2026-103290- CVSS 4.0
- 7.2 High
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 - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-m687-p538-r5hp
7.2
Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted
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:
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:
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:
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:
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:
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:
docker build -f Dockerfile -t poc . && docker run --rm poc
Dockerfilebuilds Vikunja from commita881ac39eecd07575a5aede8e74727c28d5fd578and bakes the reproducer in.poc.shis the entrypoint. It starts the application on sqlite withservice.publicurlas the only setting configured, waits for the health check, and runs the exploit.exploit.pyregisters a user, logs them in, then acts as a page onhttp://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…
- Record
GHSA-m687-p538-r5hp- CVSS 4.0
- 7.2 High
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 - Weakness
- CWE-942
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-r87c-f263-6pfc
7.2
Vikunja from 2.2.0 before 2.7.0 contains a permissive CORS configuration that trusts every...
Vikunja from 2.2.0 before 2.7.0 contains a permissive CORS configuration that trusts every localhost and 127.0.0.1 port with credentials, even when service.publicurl is set. Attackers controlling a page on any localhost port can send a credentialed request to the token refresh endpoint and read the returned bearer JWT to take over the logged-in user's account.
- Record
GHSA-r87c-f263-6pfc- CVSS 4.0
- 7.2 High
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N - Weakness
- CWE-942
- Updated
- 2026-11-02
NVD
7.2
Vikunja from 2.2.0 before 2.7.0 contains a permissive CORS configuration that trusts every localhost and 127.0.0.1 port with credentials, even when service.publicurl is set. Attackers controlling a page on any localhost port can send a credentialed request to the token refresh endpoint and read the returned bearer JWT to take over the logged-in user's account.
- Record
CVE-2026-111922- CVSS 4.0
- 7.2 High
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N - Weakness
- CWE-942
- Updated
- 2026-11-02
OSV
7.2
Vikunja 2.2.0 before 2.7.0 Permissive CORS Policy Trusting Localhost Origins
Vikunja from 2.2.0 before 2.7.0 contains a permissive CORS configuration that trusts every localhost and 127.0.0.1 port with credentials, even when service.publicurl is set. Attackers controlling a page on any localhost port can send a credentialed request to the token refresh endpoint and read the returned bearer JWT to take over the logged-in user's account.
- Record
CVE-2026-111922- CVSS 4.0
- 7.2 High
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 - Weakness
- CWE-942
- Updated
- 2026-11-02
OSVGHSA-m687-p538-r5hp
7.2
Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted
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:
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:
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:
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:
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:
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:
docker build -f Dockerfile -t poc . && docker run --rm poc
Dockerfilebuilds Vikunja from commita881ac39eecd07575a5aede8e74727c28d5fd578and bakes the reproducer in.poc.shis the entrypoint. It starts the application on sqlite withservice.publicurlas the only setting configured, waits for the health check, and runs the exploit.exploit.pyregisters a user, logs them in, then acts as a page onhttp://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…
- Record
GHSA-m687-p538-r5hp- CVSS 4.0
- 7.2 High
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 - Weakness
- CWE-942
- Updated
- 2026-11-02
OSVGO-2026-6739 No score
Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted in code.vikunja.io/api
Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted in code.vikunja.io/api.
NOTE: The source advisory for this report contains additional versions that could not be automatically mapped to standard Go module versions.
(If this is causing false-positive reports from vulnerability scanners, please suggest an edit to the report.)
The additional affected modules and versions are: .
- Record
GO-2026-6739- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
code.vikunja.io/apiGo |
2.2.0 to 2.6.0 | None known |
code.vikunja.io/apiGo |
All versions | None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-111922 is a high vulnerability (CVSS 7.2) in Go-vikunja Vikunja, published October 9, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.15%, the 4th percentile. No source names a fixed version.
References
github.com/go-vikunja/vikunja/security/advisories/GHSA-m687-p538-r5hp
vulncheck.com/advisories/vikunja-2.2.0-before-2.7.0-permissive-cors-policy-trusting-localhost-origins
github.com/advisories/GHSA-m687-p538-r5hp
nvd.nist.gov/vuln/detail/CVE-2026-111922
github.com/advisories/GHSA-r87c-f263-6pfc
github.com/CVEProject/cvelistV5/tree/main/cves/2026/111xxx/CVE-2026-111922.json
github.com/go-vikunja/vikunja
Left out here: the rest of 2 source descriptions. The JSON record has them.