CVE-2026-74907
Grav before 2.0.15 Path Traversal via plugin-asset-map.php
Grav before 2.0.15 contains a path traversal vulnerability in the static asset server within index.php that uses string prefix matching instead of directory-boundary validation. Unauthenticated attackers can access files in sibling directories by exploiting directory names that extend the base path string, such as requesting assets-secret when assets is the configured base.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.2
Grav before 2.0.15 Path Traversal via plugin-asset-map.php
Grav before 2.0.15 contains a path traversal vulnerability in the static asset server within index.php that uses string prefix matching instead of directory-boundary validation. Unauthenticated attackers can access files in sibling directories by exploiting directory names that extend the base path string, such as requesting assets-secret when assets is the configured base.
- Record
CVE-2026-74907- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 5.9 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-60588
8.2
Grav before 2.0.15 contains a path traversal vulnerability in the static asset server within index.php that uses string prefix matching instead of directory-boundary validation. Unauthenticated attackers can access files in sibling directories by exploiting directory names that extend the base path string, such as requesting assets-secret when assets is the configured base.
- Record
EUVD-2026-60588- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2rhw-8953-48q3
8.2
Duplicate Advisory: Grav: Unauthenticated Path Traversal via Missing Directory-Boundary Check in `plugin-asset-map.php` Static Asset Server (`index.php`)
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-4v9q-p283-qc2m. This link is maintained to preserve external references.
Original Description
Grav before 2.0.15 contains a path traversal vulnerability in the static asset server within index.php that uses string prefix matching instead of directory-boundary validation. Unauthenticated attackers can access files in sibling directories by exploiting directory names that extend the base path string, such as requesting assets-secret when assets is the configured base.
- Record
GHSA-2rhw-8953-48q3- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 5.9 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-4v9q-p283-qc2m
8.2
Grav: Unauthenticated Path Traversal via Missing Directory-Boundary Check in `plugin-asset-map.php` Static Asset Server (`index.php`)
Verified against: getgrav/grav devel branch, GRAV_VERSION = "2.0.15", file `index.php
Title
Unauthenticated Path Traversal via Missing Directory-Boundary Check in plugin-asset-map.php Static Asset Server (index.php)
Product / Affected Versions
- Product:
getgrav/grav - File:
index.php(top-level front controller, runs before Grav itself boots) - Confirmed present in: devel branch, 2.0.15
- Precondition: requires
user/config/plugin-asset-map.phpto exist and contain at least one route-prefix mapping ,this is an opt-in mechanism (per the code comment: "Fast static asset serving for plugins that bundle SPA apps"). No core mechanism generates this file automatically; it's created by a plugin that opts into this fast-path. Not reachable on a stock Grav install with no such plugin. Where reachable, it requires zero authentication.
CWE
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') , specific mechanism: a path-prefix containment check performed with plain string comparison (str_starts_with) instead of a directory-boundary-aware comparison, allowing escape into any sibling path whose name happens to extend the base directory's name as a string.
Description
index.php implements a fast-path static file server that runs before Grav's own routing/security stack, gated on the presence of an asset-map file:
$assetMapFile = __DIR__ . '/user/config/plugin-asset-map.php';
if (is_file($assetMapFile)) {
$assetMap = require $assetMapFile;
foreach ($assetMap as $routePrefix => $diskPath) {
if (str_starts_with($path, $routePrefix)) {
$relPath = substr($path, strlen($routePrefix));
$filePath = __DIR__ . '/' . ltrim($diskPath, '/') . $relPath;
$realFile = realpath($filePath);
$realBase = realpath(__DIR__ . '/' . ltrim($diskPath, '/'));
if ($realFile && $realBase && str_starts_with($realFile, $realBase) && is_file($realFile)) {
// ... serves $realFile directly, with Content-Type inferred from extension
readfile($realFile);
exit;
}
}
}
}
realpath() correctly resolves .. sequences, so a naive ../../etc/passwd-style traversal that leaves the filesystem entirely is blocked (it wouldn't share the $realBase string prefix). But the containment check itself, str_starts_with($realFile, $realBase), has no directory-boundary awareness it's a plain string-prefix test, not "is $realFile inside the $realBase directory." Any resolved path whose string representation merely begins with the same characters as $realBase passes, including sibling directories that extend the base directory's name (assets → assets-secret, assets.bak, assets_old, assets2, etc.) a very common real-world directory-naming pattern (backup dirs, versioned dirs, disabled/legacy dirs sitting alongside the active one).
Live Proof of Concept
Setup: the exact code block above, extracted verbatim from index.php, executed with PHP 8.3.6 against a realistic directory layout (a plugin's active assets/ dir sitting next to an unrelated assets-secret/ dir containing a fake secret):
user/plugins/myplugin/assets/app.js <- intended, public
user/plugins/myplugin/assets-secret/config.php <- NOT intended to be served
user/config/plugin-asset-map.php:
return ['/myplugin-assets' => 'user/plugins/myplugin/assets'];
Legitimate request (/myplugin-assets/app.js):
realFile: '/home/claude/grav-poc/user/plugins/myplugin/assets/app.js'
realBase: '/home/claude/grav-poc/user/plugins/myplugin/assets'
>>> WOULD SERVE FILE <<<
>>> Content: public asset content
Traversal request (/myplugin-assets/../assets-secret/config.php):
realFile: '/home/claude/grav-poc/user/plugins/myplugin/assets-secret/config.php'
realBase: '/home/claude/grav-poc/user/plugins/myplugin/assets'
>>> WOULD SERVE…
- Record
GHSA-4v9q-p283-qc2m- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 5.9 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
NVD
8.2
Grav before 2.0.15 contains a path traversal vulnerability in the static asset server within index.php that uses string prefix matching instead of directory-boundary validation. Unauthenticated attackers can access files in sibling directories by exploiting directory names that extend the base path string, such as requesting assets-secret when assets is the configured base.
- Record
CVE-2026-74907- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 5.9 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
8.2
Grav before 2.0.15 Path Traversal via plugin-asset-map.php
Grav before 2.0.15 contains a path traversal vulnerability in the static asset server within index.php that uses string prefix matching instead of directory-boundary validation. Unauthenticated attackers can access files in sibling directories by exploiting directory names that extend the base path string, such as requesting assets-secret when assets is the configured base.
- Record
CVE-2026-74907- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVGHSA-2rhw-8953-48q3
8.2
Duplicate Advisory: Grav: Unauthenticated Path Traversal via Missing Directory-Boundary Check in `plugin-asset-map.php` Static Asset Server (`index.php`)
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-4v9q-p283-qc2m. This link is maintained to preserve external references.
Original Description
Grav before 2.0.15 contains a path traversal vulnerability in the static asset server within index.php that uses string prefix matching instead of directory-boundary validation. Unauthenticated attackers can access files in sibling directories by exploiting directory names that extend the base path string, such as requesting assets-secret when assets is the configured base.
- Record
GHSA-2rhw-8953-48q3- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 5.9 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVGHSA-4v9q-p283-qc2m
8.2
Grav: Unauthenticated Path Traversal via Missing Directory-Boundary Check in `plugin-asset-map.php` Static Asset Server (`index.php`)
Verified against: getgrav/grav devel branch, GRAV_VERSION = "2.0.15", file `index.php
Title
Unauthenticated Path Traversal via Missing Directory-Boundary Check in plugin-asset-map.php Static Asset Server (index.php)
Product / Affected Versions
- Product:
getgrav/grav - File:
index.php(top-level front controller, runs before Grav itself boots) - Confirmed present in: devel branch, 2.0.15
- Precondition: requires
user/config/plugin-asset-map.phpto exist and contain at least one route-prefix mapping ,this is an opt-in mechanism (per the code comment: "Fast static asset serving for plugins that bundle SPA apps"). No core mechanism generates this file automatically; it's created by a plugin that opts into this fast-path. Not reachable on a stock Grav install with no such plugin. Where reachable, it requires zero authentication.
CWE
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') , specific mechanism: a path-prefix containment check performed with plain string comparison (str_starts_with) instead of a directory-boundary-aware comparison, allowing escape into any sibling path whose name happens to extend the base directory's name as a string.
Description
index.php implements a fast-path static file server that runs before Grav's own routing/security stack, gated on the presence of an asset-map file:
$assetMapFile = __DIR__ . '/user/config/plugin-asset-map.php';
if (is_file($assetMapFile)) {
$assetMap = require $assetMapFile;
foreach ($assetMap as $routePrefix => $diskPath) {
if (str_starts_with($path, $routePrefix)) {
$relPath = substr($path, strlen($routePrefix));
$filePath = __DIR__ . '/' . ltrim($diskPath, '/') . $relPath;
$realFile = realpath($filePath);
$realBase = realpath(__DIR__ . '/' . ltrim($diskPath, '/'));
if ($realFile && $realBase && str_starts_with($realFile, $realBase) && is_file($realFile)) {
// ... serves $realFile directly, with Content-Type inferred from extension
readfile($realFile);
exit;
}
}
}
}
realpath() correctly resolves .. sequences, so a naive ../../etc/passwd-style traversal that leaves the filesystem entirely is blocked (it wouldn't share the $realBase string prefix). But the containment check itself, str_starts_with($realFile, $realBase), has no directory-boundary awareness it's a plain string-prefix test, not "is $realFile inside the $realBase directory." Any resolved path whose string representation merely begins with the same characters as $realBase passes, including sibling directories that extend the base directory's name (assets → assets-secret, assets.bak, assets_old, assets2, etc.) a very common real-world directory-naming pattern (backup dirs, versioned dirs, disabled/legacy dirs sitting alongside the active one).
Live Proof of Concept
Setup: the exact code block above, extracted verbatim from index.php, executed with PHP 8.3.6 against a realistic directory layout (a plugin's active assets/ dir sitting next to an unrelated assets-secret/ dir containing a fake secret):
user/plugins/myplugin/assets/app.js <- intended, public
user/plugins/myplugin/assets-secret/config.php <- NOT intended to be served
user/config/plugin-asset-map.php:
return ['/myplugin-assets' => 'user/plugins/myplugin/assets'];
Legitimate request (/myplugin-assets/app.js):
realFile: '/home/claude/grav-poc/user/plugins/myplugin/assets/app.js'
realBase: '/home/claude/grav-poc/user/plugins/myplugin/assets'
>>> WOULD SERVE FILE <<<
>>> Content: public asset content
Traversal request (/myplugin-assets/../assets-secret/config.php):
realFile: '/home/claude/grav-poc/user/plugins/myplugin/assets-secret/config.php'
realBase: '/home/claude/grav-poc/user/plugins/myplugin/assets'
>>> WOULD SERVE…
- Record
GHSA-4v9q-p283-qc2m- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 5.9 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-22
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
getgrav/gravPackagist |
through 2.0.14 | 2.0.15 |
getgrav/gravPackagist |
before 2.0.15, plus listed versions 0.8.00.9.00.9.10.9.100.9.11 Show 45 moreShow less0.9.120.9.130.9.140.9.150.9.160.9.170.9.180.9.190.9.20.9.200.9.210.9.220.9.230.9.240.9.250.9.260.9.270.9.280.9.290.9.30.9.300.9.310.9.320.9.330.9.340.9.350.9.360.9.370.9.380.9.390.9.40.9.400.9.410.9.420.9.430.9.440.9.450.9.50.9.60.9.70.9.80.9.91.0.01.0.0-rc.11.0.0-rc.2 |
2.0.15 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-74907 is a high vulnerability (CVSS 8.2) in Getgrav Grav, published August 18, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.43%, the 37th percentile. Fixed in getgrav/grav 2.0.15.
References
github.com/getgrav/grav/security/advisories/GHSA-4v9q-p283-qc2m
vulncheck.com/advisories/grav-before-path-traversal-via-plugin-asset-map-php
nvd.nist.gov/vuln/detail/CVE-2026-74907
github.com/advisories/GHSA-2rhw-8953-48q3
github.com/advisories/GHSA-4v9q-p283-qc2m
github.com/CVEProject/cvelistV5/tree/main/cves/2026/74xxx/CVE-2026-74907.json
github.com/getgrav/grav
Left out here: 299 versions and the rest of 2 source descriptions. The JSON record has them.