{"id":"EUVD-2026-103290","revision":null,"records":[{"revision":"656777371020487299fd961e2a1e54e7","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-111922","aliases":[],"title":"Vikunja 2.2.0 before 2.7.0 Permissive CORS Policy Trusting Localhost Origins","description":"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.","state":0,"publishedAt":"2026-10-17T18:46:35.991+00:00","modifiedAt":"2026-10-19T15:00:44.366+00:00","affected":[{"ecosystem":"Vendor product","name":"vikunja","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"go-vikunja\",\n                    \"product\": \"vikunja\",\n                    \"defaultStatus\": \"unaffected\",\n                    \"packageURL\": \"pkg:golang/code.vikunja.io/api\",\n                    \"versions\": [\n                        {\n                            \"version\": \"2.2.0\",\n                            \"status\": \"affected\",\n                            \"versionType\": \"semver\",\n                            \"lessThan\": \"2.7.0\"\n                        },\n                        {\n                            \"version\": \"2.7.0\",\n                            \"status\": \"unaffected\",\n                            \"versionType\": \"semver\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","vector":"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","score":7.2,"severity":"HIGH"},{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N","score":6.3,"severity":"MEDIUM"}],"references":["https://github.com/go-vikunja/vikunja/security/advisories/GHSA-m687-p538-r5hp","https://www.vulncheck.com/advisories/vikunja-2.2.0-before-2.7.0-permissive-cors-policy-trusting-localhost-origins"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-111922","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-111922","identifiers":["CVE-2026-111922"]},"retained":false,"historicalAffected":[]},{"revision":"e62a19dabee74c05b51771706e7b81f4","observedAt":"2026-11-07T04:33:21.081+00:00","statement":{"source":"euvd","id":"EUVD-2026-103290","aliases":["CVE-2026-111922","GHSA-r87c-f263-6pfc"],"title":"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 cred…","description":"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.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 4.0","vector":"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","score":7.2,"severity":null}],"references":["https://github.com/go-vikunja/vikunja/security/advisories/GHSA-m687-p538-r5hp","https://www.vulncheck.com/advisories/vikunja-2.2.0-before-2.7.0-permissive-cors-policy-trusting-localhost-origins"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-103290","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 17, 2026, 6:46:35 PM","dateUpdated":"Oct 19, 2026, 3:00:44 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-103290","identifiers":["EUVD-2026-103290","CVE-2026-111922","GHSA-r87c-f263-6pfc"]},"retained":false,"historicalAffected":[]},{"revision":"3033571ed6974b54b459dda8efd28437","observedAt":"2026-10-25T05:17:13.11+00:00","statement":{"source":"nvd","id":"CVE-2026-111922","aliases":[],"title":"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 cred…","description":"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.","state":0,"publishedAt":"2026-10-17T19:16:34.957+00:00","modifiedAt":"2026-10-19T21:14:14.15+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · disclosure@vulncheck.com · Secondary","vector":"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","score":7.2,"severity":"HIGH"},{"system":"CVSS 3.1 · disclosure@vulncheck.com · Secondary","vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N","score":6.3,"severity":"MEDIUM"}],"references":["https://github.com/go-vikunja/vikunja/security/advisories/GHSA-m687-p538-r5hp","https://www.vulncheck.com/advisories/vikunja-2.2.0-before-2.7.0-permissive-cors-policy-trusting-localhost-origins"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-111922","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-942"],"productCriteria":[],"sourceDates":{"published":"2026-10-17T19:16:34.957","lastModified":"2026-10-19T21:14:14.150"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-111922","identifiers":["CVE-2026-111922"]},"retained":false,"historicalAffected":[]},{"revision":"255f4c3326114f2b8b18e781c962a627","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GHSA-m687-p538-r5hp","aliases":["CVE-2026-111922","GO-2026-6739"],"title":"Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted","description":"## 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.","state":0,"publishedAt":"2026-10-09T20:57:50+00:00","modifiedAt":"2026-10-19T21:40:54.8358442+00:00","affected":[{"ecosystem":"Go","name":"code.vikunja.io/api","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"2.2.0"},{"kind":"last_affected","version":"2.6.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V4","vector":"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","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/go-vikunja/vikunja/security/advisories/GHSA-m687-p538-r5hp","https://github.com/go-vikunja/vikunja"],"sourceUrl":"https://github.com/advisories/GHSA-m687-p538-r5hp","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-m687-p538-r5hp","identifiers":["GHSA-m687-p538-r5hp","CVE-2026-111922","GO-2026-6739"]},"retained":false,"historicalAffected":[]},{"revision":"76150cb73f41458fac2d69a38e5000ed","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GO-2026-6739","aliases":["CVE-2026-111922","GHSA-m687-p538-r5hp"],"title":"Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted in code.vikunja.io/api","description":"Vikunja: Permissive Cross-domain Security Policy trusts every localhost origin which should not be trusted in code.vikunja.io/api.\n\nNOTE: The source advisory for this report contains additional versions that could not be automatically mapped to standard Go module versions.\n\n(If this is causing false-positive reports from vulnerability scanners, please suggest an edit to the report.)\n\nThe additional affected modules and versions are: .","state":0,"publishedAt":"2026-10-19T21:08:26+00:00","modifiedAt":"2026-10-19T21:40:54.8358442+00:00","affected":[{"ecosystem":"Go","name":"code.vikunja.io/api","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[],"references":["https://github.com/go-vikunja/vikunja/security/advisories/GHSA-m687-p538-r5hp"],"sourceUrl":"https://osv.dev/vulnerability/GO-2026-6739","license":"OSV · upstream source license applies","key":"osv:GO-2026-6739","identifiers":["GO-2026-6739","CVE-2026-111922","GHSA-m687-p538-r5hp"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-08T14:04:21.6524616+00:00","complete":true,"records":1769,"lastAttemptAt":"2026-11-08T14:04:21.6524616+00:00","lastSuccessAt":"2026-11-08T14:04:21.8442875+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.8442884+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-eaa64dac63caa79c8d4905619258c41c51b39e461b996124e1a21b5e85e7e738-4b0775bb0fc73c3ceeed66f8f17640894d61d654f97ee9386046b283ad9f9876","complete":true,"records":421100,"lastAttemptAt":"2026-11-08T14:04:21.1432513+00:00","lastSuccessAt":"2026-11-08T14:04:21.1228768+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.1432513+00:00","snapshotPublishedAt":"2026-11-08T13:27:32+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-383c643166b94ac4b6a615647191978e","complete":true,"records":13654,"lastAttemptAt":"2026-11-08T14:04:47.6384375+00:00","lastSuccessAt":"2026-11-08T13:04:50.073+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:50.073+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13706,"expectedIdentifiers":13706,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-08T14:04:21.6524616+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-08T14:04:21.6524616+00:00","lastSuccessAt":"2026-11-08T14:04:21.8452187+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.8452194+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-ceefbd28b79c4b75ac56c9c678246411","complete":false,"records":13666,"lastAttemptAt":"2026-11-08T14:04:46.9956646+00:00","lastSuccessAt":"2026-11-08T14:04:46.992+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:46.992+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13185,"expectedIdentifiers":13706,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-980593e6977a6d1b4fb1eed5a1ffe88a468736f89f0a06f710924ad59132c023","complete":true,"records":9914,"lastAttemptAt":"2026-11-08T14:04:21.5047325+00:00","lastSuccessAt":"2026-11-08T11:07:07.068788+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.5047325+00:00","snapshotPublishedAt":"2026-11-08T11:02:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-fde804134ecdda0ddd5ff5c26ee1fff5a6fa659311f7ef2e51c6ad54964db0e2","complete":true,"records":1971,"lastAttemptAt":"2026-11-08T14:04:21.2941446+00:00","lastSuccessAt":"2026-11-06T19:03:17.0399903+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.2941446+00:00","snapshotPublishedAt":"2026-11-06T18:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-964c14035e6f0f6478b2870fad6a7ef9f6045876d4a12af2c2e514521e54b6af","complete":true,"records":3055,"lastAttemptAt":"2026-11-08T14:04:21.6167179+00:00","lastSuccessAt":"2026-11-08T11:07:12.1927025+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.6167179+00:00","snapshotPublishedAt":"2026-11-08T11:02:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-f8647d7ba64b1ba0dfdc7130944766d5b36d5b77d18fff0c79e651a4cff3c2b9","complete":true,"records":231293,"lastAttemptAt":"2026-11-08T14:04:21.3943214+00:00","lastSuccessAt":"2026-11-08T11:06:56.2918717+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.3943214+00:00","snapshotPublishedAt":"2026-11-08T11:02:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}