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

GHSA-vx2m-jpxr-xv7w

MEDIUM

GHSA-vx2m-jpxr-xv7w is a medium-severity (CVSS 5.3) vulnerability in github.com/cloudreve/Cloudreve/v4. O3 Security confirms whether GHSA-vx2m-jpxr-xv7w is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Cloudreve has Broken Access Control - Revoked Share Access Still Allows Signed File URL Generation via Cached context_hint

Published
Aug 24, 2026
Updated
Aug 25, 2026
Affected
1 pkg
Patched
None yet
Exploits
None indexed
Exploitation data as of Aug 25, 2026 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🐹github.com/cloudreve/Cloudreve/v4

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

Cloudreve's file-listing responses hand the client a context_hint (UUID) that is meant to speed up follow-up operations. When that hint is replayed on the file/url (and file/thumb) routes, DBFS caches a shareNavigatorState containing the already-loaded share root and share row.

On a later request carrying the same hint, shareNavigator.RestoreState repopulates shareRoot, and shareNavigator.To then skips Root. Root is the only place that re-checks inventory.IsValidShare (share expiry, remaining-download count, owner status, source-file validity) and the share password. As a result, a recipient who prewarms a context hint while access is valid can keep minting signed file URLs for already-known shared file paths for up to the context-hint TTL (5 * 60 = 300 s) after the owner deletes the share or the share expires — plus the lifetime of any signed entity URL minted in that window.

This is a revocation / expiry bypass, not a way to discover unknown share contents: the attacker must already have had access to the share and must know the target file URI from a prior listing.

Root cause (verified at 26b6b10)

1. List responses leak the hint and each file URIservice/explorer/response.go populates ListResponse.ContextHint and FileResponse.Path (f.Uri(false).String()).

2. file/url and file/thumb accept the client-supplied hintrouters/router.go:631 and :662:

file.POST("url",  middleware.ContextHint(), /* ... */ controllers.FileURL)
file.GET("thumb", middleware.ContextHint(), /* ... */ controllers.Thumb)

The file group's only auth gate is middleware.RequiredScopes(types.ScopeFilesRead) — there is no independent share-validation middleware on this route. All share validation lives inside DBFS.

3. The middleware trusts the header verbatimmiddleware/file.go:41:

func ContextHint() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.GetHeader(dbfs.ContextHintHeader) != "" { // X-Cr-Context-Hint
            util.WithValue(c, dbfs.ContextHintCtxKey{}, uuid.FromStringOrNil(c.GetHeader(dbfs.ContextHintHeader)))
        }
        c.Next()
    }
}

4. DBFS restores cached navigator state on a hint hitdbfs.go:745 (ContextHintTTL = 5 * 60, dbfs.go:34). On a miss it arms PersistState; the closure fires in DBFS.Recycle() at end of request.

5. Persisted share state carries the loaded shareRoot + share rowshare_navigator.go:72/:85. RestoreState reinstates n.shareRoot, n.share, n.owner, etc.

6. Root is the sole validity/password gateshare_navigator.go:114inventory.IsValidShare(share) (inventory/share.go:227: IsShareExpired checks Expires.Before(now) and RemainDownloads <= 0, plus owner-active and source-file checks) followed by the share.Password comparison.

7. To skips Root once shareRoot is setshare_navigator.go:181:

func (n *shareNavigator) To(ctx context.Context, path *fs.URI) (*File, error) {
    if n.shareRoot == nil {            // restored state => NOT nil => Root() skipped
        root, err := n.Root(ctx, path)
        ...
    }
    ...
}

The single-file-share branch is also affected: it calls latestSharedSingleFile, which fetches n.fileClient.GetByID(n.share.Edges.File.ID) straight from the restored share with no revalidation (share_navigator.go).

8. A failed download hook does not block URL issuancepkg/filemanager/manager/entity.go:250:

if err := m.fs.ExecuteNavigatorHooks(ctx, fs.HookTypeBeforeDownload, file); err != nil {
    m.l.Warning("Failed to execute navigator hooks: %s", err) // logged, NOT fatal
}

The share's BeforeDownload hook is shareClient.Downloaded() (UpdateOneID(share.ID).AddDownloads(1).AddRemainDownloads(-1)). Against a deleted share this update errors, but the error is only logged and the signed URL is still minted. The signed content endpoint file/content/:id/... is then guarded only by middleware.SignRequired — it does not re-check the share.

Steps to reproduce

Setup: one share owner; one recipient (a second free account, or anonymous if the default anon group keeps share-download). Recipient knows the share URL (and password, if any).

  1. Recipient lists the valid share:
    GET /api/v4/file?uri=<share-uri> HTTP/1.1
    Host: target
    
    Response includes context_hint and each file's path.
  2. While the share is still valid, recipient warms the cache for a known file:
    POST /api/v4/file/url HTTP/1.1
    Host: target
    X-Cr-Context-Hint: <context_hint>
    Content-Type: application/json
    
    {"uri":["<known-shared-file-uri>"]}
    
    (cache MISS → Root runs → PersistState armed → Recycle writes shareNavigatorState to KV under navigator_state_<hint>_share.)
  3. Owner deletes the share, or it expires / hits zero remaining downloads.
  4. Within 300 s, recipient repeats the same request from step 2 (same X-Cr-Context-Hint, same URI). (cache HIT → RestoreState sets shareRootTo skips RootIsValidShare never runs → signed entity URL returned.)
  5. The signed URL serves the file content; file/content/:id/... validates only the signature. Expected: step 4 returns ErrShareNotFound / ErrShareLinkExpired. Actual: step 4 returns a signed, downloadable URL.

Impact

A former share recipient (including an anonymous one, under default permissions) can keep minting signed download URLs for already-known shared files for up to 300 s after the owner deletes the share or after time/download-limit expiry, plus the validity window of each signed URL minted in that period. It defeats owner revocation, time expiry, and the remaining-download limit, and bypasses password revalidation on cached state.

Remediation

  • On RestoreState, re-run inventory.IsValidShare and re-compare the current share.Password before trusting cached shareRoot; or bind the cached state to an authorization version that changes on any share edit/delete/download-limit change.
  • Do not store authorization-sensitive share state in context-hint cache; treat the hint as a pagination/perf token only.
  • Invalidate navigator_state_* entries when a share is edited or deleted.
  • Treat HookTypeBeforeDownload failures as blocking for share-backed downloads.
  • Add a regression test: list + prewarm hint, delete share, then file/url with the same hint must fail.

Affected Packages

1 total
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/cloudreve/Cloudreve/v4all versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/cloudreve/Cloudreve/v4. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Remediation status

    No patched version of github.com/cloudreve/Cloudreve/v4 has shipped for GHSA-vx2m-jpxr-xv7w yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Mitigate without a patch

    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 pinpoints whether GHSA-vx2m-jpxr-xv7w is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-vx2m-jpxr-xv7w. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary Cloudreve's file-listing responses hand the client a `context_hint` (UUID) that is meant to speed up follow-up operations. When that hint is replayed on the `file/url` (and `file/thumb`) routes, DBFS caches a `shareNavigatorState` containing the already-loaded share root and share row. On a later request carrying the same hint, `shareNavigator.RestoreState` repopulates `shareRoot`, and `shareNavigator.To` then **skips `Root`**. `Root` is the only place that re-checks `inventory.IsValidShare` (share expiry, remaining-download count, owner status, source-file validity) and the sha
O3 Security · Impact-Aware SCA

Is GHSA-vx2m-jpxr-xv7w in your dependencies?

O3 detects GHSA-vx2m-jpxr-xv7w across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.