Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure
## Summary Vikunja's `web.Auth` interface (`pkg/web/web.go`, single method `GetID() int64`) is satisfied by BOTH `*user.User` and `*models.LinkSharing`. A link-share's `GetID()` returns the **raw positive** `share.ID` (`pkg/models/link_sharing.go:83-85`), which lives in the same positive autoincrement ID space as `users.id`. The safe negated form `getUserID() = share.ID * -1` (`link_sharing.go:126-128`) exists but is NOT used at three permission sinks. As a result, a link-share principal with id `N` — which should have zero authority over teams or bot users — is treated as the *user* whose `users.id == N` at three permission checks that lack the `a.(*LinkSharing)` guard their sibling methods have. This is the same principal-type-confusion class as CVE-2026-68581 (GHSA-vvcv-vpph-h844), but at three code paths that advisory/fix never touched. ## Root Cause `web.Auth` is a one-method interface (`GetID() int64`). `*LinkSharing.GetID()` returns the raw positive share id. Three permission methods compare this raw id directly and omit the link-share type guard used elsewhere in the same files: 1. **`TeamMember.CanDelete`** (`pkg/models/team_members_permissions.go:31-40`): the self-removal fast path `if u.ID == a.GetID() { return true }` (:36) executes **before** `IsAdmin`. `IsAdmin` (:48-51) is the ONLY place that rejects link shares (`if _, is := a.(*LinkSharing); is { return false }`, :50) — and it is never reached when the fast path returns true. 2. **`BotUser.isOwner`** (`pkg/mo
- Products
- code.vikunja.io/api
- Affected versions
- Not specified
- Fixed versions
- 2.5.0
- CVEs
- CVE-2026-76216
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 1 Nov 2026, 18:34 UTC · Revision 1
Source evidence
Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure
core-osv · Original sourcePublished 9 Oct 2026, 20:43 UTC
## Summary Vikunja's `web.Auth` interface (`pkg/web/web.go`, single method `GetID() int64`) is satisfied by BOTH `*user.User` and `*models.LinkSharing`. A link-share's `GetID()` returns the **raw positive** `share.ID` (`pkg/models/link_sharing.go:83-85`), which lives in the same positive autoincrement ID space as `users.id`. The safe negated form `getUserID() = share.ID * -1` (`link_sharing.go:126-128`) exists but is NOT used at three permission sinks. As a result, a link-share principal with id `N` — which should have zero authority over teams or bot users — is treated as the *user* whose `users.id == N` at three permission checks that lack the `a.(*LinkSharing)` guard their sibling methods have. This is the same principal-type-confusion class as CVE-2026-68581 (GHSA-vvcv-vpph-h844), but at three code paths that advisory/fix never touched. ## Root Cause `web.Auth` is a one-method interface (`GetID() int64`). `*LinkSharing.GetID()` returns the raw positive share id. Three permission methods compare this raw id directly and omit the link-share type guard used elsewhere in the same files: 1. **`TeamMember.CanDelete`** (`pkg/models/team_members_permissions.go:31-40`): the self-removal fast path `if u.ID == a.GetID() { return true }` (:36) executes **before** `IsAdmin`. `IsAdmin` (:48-51) is the ONLY place that rejects link shares (`if _, is := a.(*LinkSharing); is { return false }`, :50) — and it is never reached when the fast path returns true. 2. **`BotUser.isOwner`** (`pkg/mo
osv:GHSA-32r8-5843-4qw2
code.vikunja.io/api
Fixed versions: 2.5.0
Complete source record
{
"id": "a7027153b7f3838d8fc7d4bc114e101f051bf133d43d1ee79374acef628ecb2f",
"url": "https://osv.dev/vulnerability/GHSA-32r8-5843-4qw2",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "Vikunja: Link-share principal-type confusion enables cross-account team removal, bot takeover, and roster disclosure",
"summary": "## Summary Vikunja's `web.Auth` interface (`pkg/web/web.go`, single method `GetID() int64`) is satisfied by BOTH `*user.User` and `*models.LinkSharing`. A link-share's `GetID()` returns the **raw positive** `share.ID` (`pkg/models/link_sharing.go:83-85`), which lives in the same positive autoincrement ID space as `users.id`. The safe negated form `getUserID() = share.ID * -1` (`link_sharing.go:126-128`) exists but is NOT used at three permission sinks. As a result, a link-share principal with id `N` — which should have zero authority over teams or bot users — is treated as the *user* whose `users.id == N` at three permission checks that lack the `a.(*LinkSharing)` guard their sibling methods have. This is the same principal-type-confusion class as CVE-2026-68581 (GHSA-vvcv-vpph-h844), but at three code paths that advisory/fix never touched. ## Root Cause `web.Auth` is a one-method interface (`GetID() int64`). `*LinkSharing.GetID()` returns the raw positive share id. Three permission methods compare this raw id directly and omit the link-share type guard used elsewhere in the same files: 1. **`TeamMember.CanDelete`** (`pkg/models/team_members_permissions.go:31-40`): the self-removal fast path `if u.ID == a.GetID() { return true }` (:36) executes **before** `IsAdmin`. `IsAdmin` (:48-51) is the ONLY place that rejects link shares (`if _, is := a.(*LinkSharing); is { return false }`, :50) — and it is never reached when the fast path returns true. 2. **`BotUser.isOwner`** (`pkg/mo",
"evidence": "osv:GHSA-32r8-5843-4qw2",
"products": [
{
"name": {
"value": "code.vikunja.io/api",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "2.5.0",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": []
}
],
"severity": "high",
"structured": {
"id": "GHSA-32r8-5843-4qw2",
"aliases": [
"CVE-2026-76216",
"GHSA-32r8-5843-4qw2",
"GO-2026-6711"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H"
}
],
"affected": [
{
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "0"
},
{
"fixed": "2.5.0"
}
]
}
],
"package": {
"name": "code.vikunja.io/api",
"purl": "pkg:golang/code.vikunja.io/api",
"ecosystem": "Go"
},
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-32r8-5843-4qw2/GHSA-32r8-5843-4qw2.json"
}
}
],
"modified": "2026-10-27T15:30:05.146149430Z",
"provider": "osv",
"references": [
"https://github.com/go-vikunja/vikunja/security/advisories/GHSA-32r8-5843-4qw2",
"https://nvd.nist.gov/vuln/detail/CVE-2026-76216",
"https://github.com/go-vikunja/vikunja/commit/a717d64d5658708ba390f18dd697f5e567e68aac",
"https://github.com/go-vikunja/vikunja",
"https://github.com/go-vikunja/vikunja/releases/tag/v2.5.0",
"https://www.vulncheck.com/advisories/vikunja-through-principal-type-confusion-via-linksharing"
],
"provider_metadata": {
"cwe_ids": [
"CWE-639",
"CWE-843",
"CWE-863"
],
"severity": "HIGH",
"github_reviewed": true,
"nvd_published_at": null,
"github_reviewed_at": "2026-10-09T20:43:40Z"
}
},
"related_cves": [
{
"value": "CVE-2026-76216",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/database_specific/severity"
},
"source_id": "core-osv",
"document_id": 462572,
"published_at": "2026-10-09T20:43:40+00:00"
}