Summary
Cloudreve has Broken Access Control - Revoked Share Access Still Allows Signed File URL Generation via Cached context_hint
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:
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:
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:
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:
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).
- Recipient lists the valid share:
Response includesGET /api/v4/file?uri=<share-uri> HTTP/1.1 Host: targetcontext_hintand each file'spath. - While the share is still valid, recipient warms the cache for a known file:
(cache MISS →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>"]}Rootruns →PersistStatearmed →RecyclewritesshareNavigatorStateto KV undernavigator_state_<hint>_share.) - Owner deletes the share, or it expires / hits zero remaining downloads.
- Within 300 s, recipient repeats the same request from step 2 (same
X-Cr-Context-Hint, same URI).
(cache HIT →RestoreStatesetsshareRoot→ToskipsRoot→IsValidSharenever runs → signed entity URL returned.) - The signed URL serves the file content;
file/content/:id/...validates only the signature.
Expected: step 4 returnsErrShareNotFound/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.
The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions. Typical impact: unauthorized data access or execution of privileged operations.
GHSA-VX2M-JPXR-XV7W has a CVSS score of 5.3 (Medium). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. No fixed version is listed yet, so configuration controls and monitoring matter more in the interim.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
- On
RestoreState, re-runinventory.IsValidShareand re-compare the currentshare.Passwordbefore trusting cachedshareRoot; 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
HookTypeBeforeDownloadfailures as blocking for share-backed downloads. - Add a regression test: list + prewarm hint, delete share, then
file/urlwith the same hint must fail.
Frequently Asked Questions
- What is GHSA-VX2M-JPXR-XV7W? GHSA-VX2M-JPXR-XV7W is a medium-severity incorrect authorization vulnerability in github.com/cloudreve/Cloudreve/v4 (go), affecting versions <= 4.0.0-20260606032813-26b6b1044b02. No fixed version is listed yet. The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions.
- How severe is GHSA-VX2M-JPXR-XV7W? GHSA-VX2M-JPXR-XV7W has a CVSS score of 5.3 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of github.com/cloudreve/Cloudreve/v4 are affected by GHSA-VX2M-JPXR-XV7W? github.com/cloudreve/Cloudreve/v4 (go) versions <= 4.0.0-20260606032813-26b6b1044b02 is affected.
- Is there a fix for GHSA-VX2M-JPXR-XV7W? No fixed version is listed for GHSA-VX2M-JPXR-XV7W yet. Monitor the advisory for updates and apply mitigations in the interim.
- Is GHSA-VX2M-JPXR-XV7W exploitable, and should I be worried? Whether GHSA-VX2M-JPXR-XV7W is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-VX2M-JPXR-XV7W is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-VX2M-JPXR-XV7W? No fixed version is listed yet. In the interim: Keep the dependency up to date. Audit access-control checks to ensure they are applied consistently and cannot be bypassed.