Every CVE Search/ Get API key

CVE-2026-69089

Grav CMS before 2.0.11 Path Traversal via watermark

8.7 HighFix available

Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
None
Availability
None

Exploit likelihood

0.46%Up 0.1 points since Aug 4, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.7

Grav CMS before 2.0.11 Path Traversal via watermark

Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.

Record
CVE-2026-69089
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Weakness
CWE-22
Updated
2026-11-02
EUVDEUVD-2026-52282 8.7

Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.

Record
EUVD-2026-52282
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-mmwh-j75q-gxp8 8.7

Duplicate Advisory: Grav: Path Traversal in ImageMedium::watermark() — arbitrary file disclosure via publicly-cached images

Duplicate Advisory

This advisory has been withdrawn because it is a duplicate of GHSA-w3f4-8pj2-599w. This link is maintained to preserve external references.

Original Description

Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.

Record
GHSA-mmwh-j75q-gxp8
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/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
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Weakness
CWE-22
Updated
2026-11-02
GitHub AdvisoriesGHSA-w3f4-8pj2-599w 8.7

Grav: Path Traversal in ImageMedium::watermark() — arbitrary file disclosure via publicly-cached images

Reported by: Nihad Huseynli (@nihaddhuseynli (https://github.com/nihaddhuseynli)) — [email protected]

▎ Note: I attempted to report this via [email protected] first, per SECURITY.md, but the email bounced with 550 5.1.1 Address does not exist. Filing directly here instead.

Path Traversal in ImageMedium::watermark() leading to arbitrary file disclosure via publicly-served images

Summary

The watermark media action, documented and allow-listed for use in editor-authored Markdown image syntax, passes its $image argument unsanitized into UniformResourceLocator::findResource(). That resolver only lexically collapses .. segments (no realpath()/containment check) and, for the default file:// scheme, resolves straight to file_exists() with no re-validation against the registered stream root. A relative-path traversal string therefore resolves to an arbitrary absolute path on disk. If that path is a valid image, its pixel content is composited into the carrier image and the result is cached and served from a public, unauthenticated URL — i.e. any file outside Grav's media sandbox that happens to be a decodable image becomes visible to anonymous visitors, not just to the attacker.

Affected version

  • Grav CMS, develop/2.0 line, commit db8c1fcd63aaaf6d6b244bc6b4cfa5f7b96bbc7f (tip of 2.0.11 post-release).
  • Root cause lives in the pinned dependency rockettheme/toolbox v2.x-dev @ c569a53304cd7d95ff21bffa6fc590adcf0be83d (per composer.lock), specifically RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator.
  • Not yet fixed as of this commit; unrelated to the four GHSA-* advisories already patched in 2.0.7–2.0.11 (which addressed arbitrary method-name dispatch, not this parameter-content issue).

Root cause

UniformResourceLocator::normalize() (ResourceLocator/src/UniformResourceLocator.php:261) cleans ../. segments purely as string manipulation against $this->base:

foreach ($parts as $i => $part) { if ($part === '..') { $part = array_pop($list); if ($part === null || $part === '' || (!$list && strpos($part, ':'))) { return false; // only refuses once popped past the leading sentinel } } ... }

Given enough ../ segments to match the depth of $this->base, this legitimately resolves to any absolute path on the filesystem, as string math. The file://-scheme branch of findCached() (UniformResourceLocator.php:476-493) then trusts that normalized path directly:

if ($scheme === 'file') { if (!$all && !file_exists($file)) { $this->cache[$key] = $array ? [] : false; } else { $this->cache[$key] = $array ? [$file] : $file; // <-- returned as-is } }

Unlike the else branch (find()), which re-glues resolved filenames onto a registered scheme root, the file:// branch performs no containment check.

ImageMedium::watermark() (system/src/Grav/Common/Page/Medium/ImageMedium.php:367) feeds attacker-influenced input straight into this resolver:

public function watermark($image = null, $position = null, $scale = null) { ... $args = func_get_args(); $file = $args[0] ?? '1'; $file = $file === '1' ? $config->get('system.images.watermark.image') : $args[0];

$watermark = $locator->findResource($file);   // no path validation
$watermark = ImageFile::open($watermark);      // decoded & composited
...

}

watermark is on Grav's own documented allow-list of Markdown image actions (Medium::ALLOWED_ACTIONS), so it is directly reachable through Excerpts::processMediaActions() (system/src/Grav/Common/Page/Markdown/Excerpts.php:262), which parses the querystring of any Markdown image reference and dispatches call_user_func_array([$medium, $action['method']], $args) for allow-listed methods — watermark's own parameter is never checked for path-safety anywhere in that chain.

Threat model

Per Grav's own SECURITY.md trust-boundary rubric: a publisher/editor (page-edit rights, no admin panel super-user access required) authors ordinary page…

Record
GHSA-w3f4-8pj2-599w
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-22
Updated
2026-11-02
NVD 8.7

Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.

Record
CVE-2026-69089
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/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
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Weakness
CWE-22
Updated
2026-11-02
OSV 8.7

Grav CMS before 2.0.11 Path Traversal via watermark

Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.

Record
CVE-2026-69089
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-22
Updated
2026-11-02
OSVGHSA-mmwh-j75q-gxp8 8.7

Duplicate Advisory: Grav: Path Traversal in ImageMedium::watermark() — arbitrary file disclosure via publicly-cached images

Duplicate Advisory

This advisory has been withdrawn because it is a duplicate of GHSA-w3f4-8pj2-599w. This link is maintained to preserve external references.

Original Description

Grav CMS 2.0.10 contains a path traversal vulnerability in ImageMedium::watermark(), which passes its unsanitized $image argument to RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator::findResource(). Because the file:// scheme branch only lexically collapses '..' segments without a realpath/containment check, an editor authoring Markdown image syntax with traversal sequences can cause arbitrary image files outside Grav's media sandbox to be composited into a carrier image, which is then cached and served from a public, unauthenticated URL — disclosing those files to anonymous visitors.

Record
GHSA-mmwh-j75q-gxp8
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/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
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Weakness
CWE-22
Updated
2026-11-02
OSVGHSA-w3f4-8pj2-599w 8.7

Grav: Path Traversal in ImageMedium::watermark() — arbitrary file disclosure via publicly-cached images

Reported by: Nihad Huseynli (@nihaddhuseynli (https://github.com/nihaddhuseynli)) — [email protected]

▎ Note: I attempted to report this via [email protected] first, per SECURITY.md, but the email bounced with 550 5.1.1 Address does not exist. Filing directly here instead.

Path Traversal in ImageMedium::watermark() leading to arbitrary file disclosure via publicly-served images

Summary

The watermark media action, documented and allow-listed for use in editor-authored Markdown image syntax, passes its $image argument unsanitized into UniformResourceLocator::findResource(). That resolver only lexically collapses .. segments (no realpath()/containment check) and, for the default file:// scheme, resolves straight to file_exists() with no re-validation against the registered stream root. A relative-path traversal string therefore resolves to an arbitrary absolute path on disk. If that path is a valid image, its pixel content is composited into the carrier image and the result is cached and served from a public, unauthenticated URL — i.e. any file outside Grav's media sandbox that happens to be a decodable image becomes visible to anonymous visitors, not just to the attacker.

Affected version

  • Grav CMS, develop/2.0 line, commit db8c1fcd63aaaf6d6b244bc6b4cfa5f7b96bbc7f (tip of 2.0.11 post-release).
  • Root cause lives in the pinned dependency rockettheme/toolbox v2.x-dev @ c569a53304cd7d95ff21bffa6fc590adcf0be83d (per composer.lock), specifically RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator.
  • Not yet fixed as of this commit; unrelated to the four GHSA-* advisories already patched in 2.0.7–2.0.11 (which addressed arbitrary method-name dispatch, not this parameter-content issue).

Root cause

UniformResourceLocator::normalize() (ResourceLocator/src/UniformResourceLocator.php:261) cleans ../. segments purely as string manipulation against $this->base:

foreach ($parts as $i => $part) { if ($part === '..') { $part = array_pop($list); if ($part === null || $part === '' || (!$list && strpos($part, ':'))) { return false; // only refuses once popped past the leading sentinel } } ... }

Given enough ../ segments to match the depth of $this->base, this legitimately resolves to any absolute path on the filesystem, as string math. The file://-scheme branch of findCached() (UniformResourceLocator.php:476-493) then trusts that normalized path directly:

if ($scheme === 'file') { if (!$all && !file_exists($file)) { $this->cache[$key] = $array ? [] : false; } else { $this->cache[$key] = $array ? [$file] : $file; // <-- returned as-is } }

Unlike the else branch (find()), which re-glues resolved filenames onto a registered scheme root, the file:// branch performs no containment check.

ImageMedium::watermark() (system/src/Grav/Common/Page/Medium/ImageMedium.php:367) feeds attacker-influenced input straight into this resolver:

public function watermark($image = null, $position = null, $scale = null) { ... $args = func_get_args(); $file = $args[0] ?? '1'; $file = $file === '1' ? $config->get('system.images.watermark.image') : $args[0];

$watermark = $locator->findResource($file);   // no path validation
$watermark = ImageFile::open($watermark);      // decoded & composited
...

}

watermark is on Grav's own documented allow-list of Markdown image actions (Medium::ALLOWED_ACTIONS), so it is directly reachable through Excerpts::processMediaActions() (system/src/Grav/Common/Page/Markdown/Excerpts.php:262), which parses the querystring of any Markdown image reference and dispatches call_user_func_array([$medium, $action['method']], $args) for allow-listed methods — watermark's own parameter is never checked for path-safety anywhere in that chain.

Threat model

Per Grav's own SECURITY.md trust-boundary rubric: a publisher/editor (page-edit rights, no admin panel super-user access required) authors ordinary page…

Record
GHSA-w3f4-8pj2-599w
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-22
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
getgrav/grav
Packagist
2.0.10 to 2.0.10
2.0.11
getgrav/grav
Packagist
2.0.10 up to 2.0.11
2.0.11

History

History
RecordedChangedSources
affected
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-69089 is a high vulnerability (CVSS 8.7) in Getgrav Grav, published August 3, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.46%, the 40th percentile. Fixed in getgrav/grav 2.0.11.

References

Show 4 moreShow less
This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open