GHSA-vx2m-jpxr-xv7w: Github.com/cloudreve/Cloudreve/v4 Cloudreve Has Broken Access Control Vulnerability

MODERATE
CVSS 5.3
CWE-863
Published
Advisory Summary

GHSA-vx2m-jpxr-xv7w is a MODERATE (CVSS 5.3) cloudreve has broken access control vulnerability (CWE-863: Incorrect Authorization) affecting Github.com/cloudreve/Cloudreve/v4 libraries in the Go ecosystem. The issue involves cloudreve has broken access control - revoked share access still allows signed file url generation via cached context_hint. Remediation is available in the latest vendor maintenance releases.

GHSA-vx2m-jpxr-xv7w at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-vx2m-jpxr-xv7w
Severity MODERATE
CVSS v3.1 Base Score 5.3
CWE Classification CWE-863: Incorrect Authorization
Affected Product Github.com/cloudreve/Cloudreve/v4
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 5.3

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity None (I:N)
Availability None (A:N)

How to Fix GHSA-vx2m-jpxr-xv7w

To remediate this vulnerability, upgrade all affected Github.com/cloudreve/Cloudreve/v4 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/cloudreve/Cloudreve/v4 fixed: Latest secure release
Upgrade github.com/cloudreve/Cloudreve/v4 to Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/cloudreve/Cloudreve/v4

How to Check if You Are Affected

To determine whether your application is affected by GHSA-vx2m-jpxr-xv7w, inspect your project dependencies for Github.com/cloudreve/Cloudreve/v4:

inspect dependencies for github.com/cloudreve/Cloudreve/v4

Analyze the terminal output to locate any version of Github.com/cloudreve/Cloudreve/v4 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-863: Incorrect Authorization, where the product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. In Github.com/cloudreve/Cloudreve/v4, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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 URI** — `service/explorer/response.go` populates `ListResponse.ContextHint` and `FileResponse.Path` (`f.Uri(false).String()`). **2. `file/url` and `file/thumb` accept the client-supplied hint** — `routers/router.go:631` and `:662`: ```go 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 verbatim** — `middleware/file.go:41`: ```go 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 hit** — `dbfs.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` row** — `share_navigator.go:72`/`:85`. `RestoreState` reinstates `n.shareRoot`, `n.share`, `n.owner`, etc. **6. `Root` is the sole validity/password gate** — `share_navigator.go:114` → `inventory.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 set** — `share_navigator.go:181`: ```go 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 issuance** — `pkg/filemanager/manager/entity.go:250`: ```go 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 `shareRoot` → `To` skips `Root` → `IsValidShare` 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.

GHSA-vx2m-jpxr-xv7w Affected Github.com/cloudreve/Cloudreve/v4 Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
github.com/cloudreve/Cloudreve/v4
Go
≤ 4.0.0-20260606032813-26b6b1044b02

References

Frequently Asked Questions

What is GHSA-vx2m-jpxr-xv7w?
GHSA-vx2m-jpxr-xv7w is a security vulnerability in Github.com/cloudreve/Cloudreve/v4 involving Cloudreve has Broken Access Control - Revoked Share Access Still Allows Signed File URL Generation via Cached context_hint under CWE-863: Incorrect Authorization.
Which Github.com/cloudreve/Cloudreve/v4 versions are affected?
Affected versions include releases prior to current release patches across affected packages: github.com/cloudreve/Cloudreve/v4.
What version fixes GHSA-vx2m-jpxr-xv7w?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/cloudreve/Cloudreve/v4 affected?
Yes, github.com/cloudreve/Cloudreve/v4 is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What is the CVSS score of GHSA-vx2m-jpxr-xv7w?
GHSA-vx2m-jpxr-xv7w has a CVSS v3.1 base score of 5.3 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N.
What CWE is associated with GHSA-vx2m-jpxr-xv7w?
GHSA-vx2m-jpxr-xv7w is classified under CWE-863: Incorrect Authorization (The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.).
How can I check Go dependencies for GHSA-vx2m-jpxr-xv7w?
Run the dependency tree command: 'inspect dependencies for github.com/cloudreve/Cloudreve/v4'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-vx2m-jpxr-xv7w",
  "modified" : "2026-08-24T22:01:03Z",
  "published" : "2026-08-24T22:01:03Z",
  "aliases" : [ ],
  "summary" : "Cloudreve has Broken Access Control - Revoked Share Access Still Allows Signed File URL Generation via Cached context_hint",
  "details" : "## Summary\n \nCloudreve'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.\n \nOn 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.\n \nThis 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.\n\n## Root cause (verified at `26b6b10`)\n \n**1. List responses leak the hint and each file URI** — `service/explorer/response.go` populates `ListResponse.ContextHint` and `FileResponse.Path` (`f.Uri(false).String()`).\n \n**2. `file/url` and `file/thumb` accept the client-supplied hint** — `routers/router.go:631` and `:662`:\n \n```go\nfile.POST(\"url\",  middleware.ContextHint(), /* ... */ controllers.FileURL)\nfile.GET(\"thumb\", middleware.ContextHint(), /* ... */ controllers.Thumb)\n```\n \nThe `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.\n \n**3. The middleware trusts the header verbatim** — `middleware/file.go:41`:\n \n```go\nfunc ContextHint() gin.HandlerFunc {\n    return func(c *gin.Context) {\n        if c.GetHeader(dbfs.ContextHintHeader) != \"\" { // X-Cr-Context-Hint\n            util.WithValue(c, dbfs.ContextHintCtxKey{}, uuid.FromStringOrNil(c.GetHeader(dbfs.ContextHintHeader)))\n        }\n        c.Next()\n    }\n}\n```\n \n**4. DBFS restores cached navigator state on a hint hit** — `dbfs.go:745` (`ContextHintTTL = 5 * 60`, `dbfs.go:34`). On a miss it arms `PersistState`; the closure fires in `DBFS.Recycle()` at end of request.\n \n**5. Persisted share state carries the loaded `shareRoot` + `share` row** — `share_navigator.go:72`/`:85`. `RestoreState` reinstates `n.shareRoot`, `n.share`, `n.owner`, etc.\n \n**6. `Root` is the sole validity/password gate** — `share_navigator.go:114` → `inventory.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.\n \n**7. `To` skips `Root` once `shareRoot` is set** — `share_navigator.go:181`:\n \n```go\nfunc (n *shareNavigator) To(ctx context.Context, path *fs.URI) (*File, error) {\n    if n.shareRoot == nil {            // restored state => NOT nil => Root() skipped\n        root, err := n.Root(ctx, path)\n        ...\n    }\n    ...\n}\n```\n \nThe 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`).\n \n**8. A failed download hook does not block URL issuance** — `pkg/filemanager/manager/entity.go:250`:\n \n```go\nif err := m.fs.ExecuteNavigatorHooks(ctx, fs.HookTypeBeforeDownload, file); err != nil {\n    m.l.Warning(\"Failed to execute navigator hooks: %s\", err) // logged, NOT fatal\n}\n```\n \nThe 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.\n\n## Steps to reproduce\n \n**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).\n \n1. Recipient lists the valid share:\n   ```\n   GET /api/v4/file?uri=<share-uri> HTTP/1.1\n   Host: target\n   ```\n   Response includes `context_hint` and each file's `path`.\n2. While the share is still valid, recipient warms the cache for a known file:\n   ```\n   POST /api/v4/file/url HTTP/1.1\n   Host: target\n   X-Cr-Context-Hint: <context_hint>\n   Content-Type: application/json\n \n   {\"uri\":[\"<known-shared-file-uri>\"]}\n   ```\n   (cache MISS → `Root` runs → `PersistState` armed → `Recycle` writes `shareNavigatorState` to KV under `navigator_state_<hint>_share`.)\n3. Owner deletes the share, **or** it expires / hits zero remaining downloads.\n4. Within 300 s, recipient repeats the **same** request from step 2 (same `X-Cr-Context-Hint`, same URI).\n   (cache HIT → `RestoreState` sets `shareRoot` → `To` skips `Root` → `IsValidShare` never runs → signed entity URL returned.)\n5. The signed URL serves the file content; `file/content/:id/...` validates only the signature.\n**Expected:** step 4 returns `ErrShareNotFound` / `ErrShareLinkExpired`.\n**Actual:** step 4 returns a signed, downloadable URL.\n \n## Impact\n \nA 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.\n \n## Remediation\n \n- 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.\n- Do not store authorization-sensitive share state in context-hint cache; treat the hint as a pagination/perf token only.\n- Invalidate `navigator_state_*` entries when a share is edited or deleted.\n- Treat `HookTypeBeforeDownload` failures as blocking for share-backed downloads.\n- Add a regression test: list + prewarm hint, delete share, then `file/url` with the same hint must fail.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/cloudreve/Cloudreve/v4"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "last_affected" : "4.0.0-20260606032813-26b6b1044b02"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/cloudreve/cloudreve/security/advisories/GHSA-vx2m-jpxr-xv7w"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/cloudreve/cloudreve"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-863" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-24T22:01:03Z",
    "nvd_published_at" : null
  }
}