TL;DR
I found an authorization flaw in code16/sharp that was later assigned CVE-2026-44692.
Sharp exposes a generic download endpoint that authorizes only the supplied entity instance, but then reads a client-supplied disk and path from Laravel Storage.
Because the requested storage object is not bound to the authorized entity instance, an authenticated Sharp user who can view one valid record can use that record as an authorization anchor to download unrelated disk-relative storage objects from configured Laravel Storage disks.
In the tested version, downloads.allowed_disks defaults to *, which allows access to any configured Laravel disk unless the application explicitly restricts it.
The confirmed impact is authenticated disclosure of unrelated storage objects from configured Laravel Storage disks. I did not confirm arbitrary host filesystem access outside configured disk roots.
Affected package
- Package:
code16/sharp - Affected versions: versions before
9.22.0 - Patched version:
9.22.0 - Tested version:
9.21.1 - CVE:
CVE-2026-44692 - Severity:
High
The tested version was identified from src/SharpInternalServiceProvider.php:63.
The checkout I used during testing was not a git repository, so there was no commit hash available.
CVSS v3 base metrics
| Metric | Value |
|---|---|
| Attack vector | Network |
| Attack complexity | Low |
| Privileges required | Low |
| User interaction | None |
| Scope | Changed |
| Confidentiality | High |
| Integrity | None |
| Availability | None |
Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Affected component
src/routes/web.php:63src/Http/Controllers/Api/DownloadController.php:11-29src/Config/SharpConfigBuilder.php:119-120src/SharpInternalServiceProvider.php:225-229
Vulnerable endpoint
GET /sharp/{globalFilter}/download/{entityKey}/{instanceId?}At a high level, the endpoint authorizes access to the supplied Sharp entity instance first, but the storage object being downloaded is not tied to that authorized record.
So if the application accepts a valid Sharp record for authorization, and the request can still influence the target disk and path, the user may retrieve a different object from an allowed storage disk.
Attacker requirements
The attacker needs:
- an authenticated Sharp session
- view access to at least one valid Sharp entity instance
The attacker does not need access to the storage object being downloaded.
Root cause
The download endpoint performs authorization only against the provided entityKey and instanceId.
After the view check succeeds, the controller reads disk and path directly from the request and returns the object from Laravel Storage.
There is no authorization check that verifies whether the requested disk-relative storage object belongs to the authorized entity instance.
With the default downloads.allowed_disks = '*', the attacker can also select any configured Laravel disk.
Source-to-sink trace
The vulnerable flow is:
authenticated Sharp user with view access to one valid entity instance
-> GET /sharp/{globalFilter}/download/{entityKey}/{instanceId?}
-> view authorization check on entityKey + instanceId
-> disk and path read from request query parameters
-> Storage::disk($disk)->get($path)
-> response returns the selected storage object
Relevant implementation points:
- the download route is registered in
src/routes/web.php:63 - Sharp loads its web routes by default through
src/SharpInternalServiceProvider.php:225-229 - the default download disk policy is
downloads.allowed_disks = '*'insrc/Config/SharpConfigBuilder.php:119-120 DownloadControllerauthorizes onlyviewon the supplied entity instancediskandpathare read from request query parameters- the response is built from
Storage::disk($disk)->get($path) - no path-to-record binding is enforced before returning the file
The missing security control is a file-level authorization check that binds the requested storage object to the authorized entity instance.
Runtime validation
I verified the issue at runtime with a framework test that exercised the real Sharp route, middleware stack, authenticated session handling, authorization check, and controller execution. This was not a direct unit invocation of the controller.
The reproducer used:
- Orchestra Testbench with the real Sharp service provider
- a Sharp entity named
posts - a per-record view policy
- one authenticated Sharp user
- one authorized record:
posts/1 - one unauthorized record:
posts/2 - a
localdisk containing an unrelated object:private/backups/customers.csv - an
archivedisk containing an unrelated object:secrets.txt
The test suite passed:
OK (6 tests, 16 assertions)This confirmed the issue was reachable through the real framework route and controller path.
Confirmed exploit path
The authenticated Sharp user was allowed to view posts/1.
That valid record could then be used as the authorization anchor while requesting an unrelated object from the local storage disk.
Proof of concept
GET /sharp/default/download/posts/1?disk=local&path=private/backups/customers.csv HTTP/1.1Host: target.localUser-Agent: Mozilla/5.0Cookie: laravel_session=ATTACKER_SESSIONConnection: closeActual result
The server returned 200 OK and the contents of the unrelated storage object:
customer_id,email7,bob@example.testThe response also included file download headers, including a content disposition for attachment download.
As supplementary evidence, the same authenticated Sharp user could also read an unrelated object from another configured Laravel disk when downloads.allowed_disks remained at its default value:
GET /sharp/default/download/posts/1?disk=archive&path=secrets.txtResult:
200 OKarchive-secretExpected result
The endpoint should only return storage objects associated with the authorized entity instance, or require a file-level authorization check before returning the object.
Negative controls
I also verified the following controls:
- invalid
entityKeyreturned404 - unauthorized
instanceIdreturned403 - restricting
downloads.allowed_disksto['public']caused the samedisk=localrequest to return403 - path traversal outside the local disk root was not confirmed
Impact
This is an authenticated cross-record / cross-feature storage object disclosure issue.
A Sharp user with view access to one valid entity instance can download unrelated objects from configured Laravel Storage disks.
In real applications, those disks may contain:
- exports
- backups
- invoices
- internal documents
- uploaded files belonging to other records
- tenant-specific data
- operational or administrative files stored in private application disks
The confirmed impact is limited to configured Laravel Storage disks. I did not confirm arbitrary host filesystem access outside configured disk roots.
Limitations and assumptions
- The confirmed scope is configured Laravel Storage disks, not arbitrary host filesystem paths.
- Path traversal outside a local disk root was not confirmed.
- Impact depends on sensitive objects existing on configured disks.
- If an application explicitly restricts
downloads.allowed_disks, exposure can be reduced. - Disk allowlisting reduces the reachable storage surface, but does not fix the missing per-record file binding.
Package-level impact
This appears to be a package-level authorization flaw that applications may inherit through Sharp’s default route and controller flow.
A valid record-level view permission can be reused as an authorization anchor for unrelated storage objects. Applications using the generic download endpoint with the default downloads.allowed_disks = '*' behavior may expose disk-relative objects from configured Laravel Storage disks unless they add compensating controls.
Patch
The fix in 9.22.0 uses signed URLs for Sharp-generated download links.
After the patch, changing parameters such as:
diskpathentityKeyinstanceId
invalidates the signature, so the modified request is rejected instead of returning a different storage object.
Recommended fix
The endpoint should not accept raw client-controlled disk and path values as the authorization basis for file downloads.
Recommended fixes:
- replace
diskandpathquery parameters with an opaque server-generated file identifier - resolve the storage object server-side from the authorized entity instance
- enforce per-record file ownership or binding before returning the object
- keep disk allowlisting, but do not rely on disk allowlisting as the only security control
- prevent cross-disk access unless explicitly required and authorized
- return a handled
403or404for invalid, unbound, or traversal-like paths
A safer design would make the download endpoint derive the storage location from the authorized record or from a server-side file registry, rather than accepting the storage location directly from the request.
Workarounds
If upgrading is not possible immediately, the practical short-term mitigations are:
- reduce
downloads.allowed_disksto the smallest set possible - avoid storing unrelated sensitive files on disks reachable through Sharp downloads
- add application-level checks that bind the requested file to the authorized record
These measures reduce exposure, but they do not fully solve the missing per-record file binding issue.
References
- GHSA:
GHSA-748w-hm6r-qc7v - CVE:
CVE-2026-44692 - Laravel signed URLs: https://laravel.com/docs/urls#signed-urls
- CWE-639: https://cwe.mitre.org/data/definitions/639.html