NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
🐘 Packagist
Not in CISA KEV

CVE-2026-58657 — grav

Fix: getgrav/grav@6582166

CVE-2026-58657 is a Cross-site Scripting (XSS) vulnerability in getgrav/grav. A fix is available for getgrav/grav — see the affected versions and patch details below.

Grav - Stored CSS Injection via Markdown Image resize() Action

Also known asGHSA-ffmg-hfvg-jhg9
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 30, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-58657.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk-0.10%
Lower risk than most CVEs19th percentile — riskier than 19% of all scored CVEsHighest risk
0.00%0.29%0.58%0.87%0.2%0.4%0.4%0.3%Aug 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected
🐘getgrav/grav

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Grav 2.0.0-rc.9 and the current 2.0 branch still allow stored CSS injection through Markdown image media actions. The prior media hardening rejects direct ?style= payloads and unsafe attribute() fallbacks, but the adjacent resize() action still writes caller-controlled values directly into styleAttributes.

A publisher who can edit page Markdown can store a crafted image URL that renders additional CSS declarations in the final <img style=...> attribute. This crosses the same lower-privileged publisher to higher-privileged reviewer/admin rendered-content boundary as the earlier media style and attribute advisories.

Impact

A lower-privileged content editor can persist CSS declarations that are rendered when a higher-privileged user views the page or admin preview. The demonstrated payload creates a full-viewport fixed overlay by injecting position:fixed, viewport dimensions, background color, and z-index declarations.

This does not require JavaScript execution. The impact is stored CSS injection in rendered content, with UI redress/overlay and content-manipulation risk in higher-privileged sessions.

Reproduction

Tested versions:

  • Grav 2.0 branch commit 6582166173bb8eb5869d96aea384e0e73777c94c
  • Grav 2.0.0-rc.9 commit e03d29aa0d3ece16d73c1ffccfa78df8bf5f28b8

Minimal Markdown payload:

![logo](image.png?resize=100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999,200)

A minimal PHPUnit-style reproducer can drive the same parser path directly:

$m = new class {
    use \Grav\Common\Media\Traits\MediaObjectTrait;
    use \Grav\Common\Media\Traits\StaticResizeTrait;

    public function addMetaFile($filepath) {}
    public function __toString(): string { return ''; }
    public function url($reset = true) { return '/img.png'; }
    public function get($name, mixed $default = null, $separator = null) { return $default; }
    public function set($name, mixed $value, $separator = null) { return $this; }
    protected function createThumbnail($thumb) { return null; }
    protected function createLink(array $attributes) { return null; }
    protected function getItems(): array { return []; }
};

$excerpts = new \Grav\Common\Page\Markdown\Excerpts(null, ['markdown' => [], 'images' => []]);
$m = $excerpts->processMediaActions(
    $m,
    'image.png?resize=100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999,200'
);
$element = $m->parsedownElement('', '', '', '', false);
var_dump($element['attributes']['style']);

Observed style attribute:

width: 100;position:fixed;top:0;left:0;width:100vw;height:100vh;background:white;z-index:9999px;height: 200px;

The appended px lands on the final z-index value, but the preceding injected declarations remain syntactically valid CSS.

Root Cause / Technical Details

system/src/Grav/Common/Page/Markdown/Excerpts.php::processMediaActions() parses the image query string into media actions and invokes the requested public media method with call_user_func_array([$medium, $action['method']], $args).

For resize(), system/src/Grav/Common/Media/Traits/StaticResizeTrait.php::resize() stores width and height directly into style attributes:

$this->styleAttributes['width'] = $width . 'px';
$this->styleAttributes['height'] = $height . 'px';

It does not verify that the values are numeric, length-only, or free of CSS declaration delimiters. Later, system/src/Grav/Common/Media/Traits/MediaObjectTrait.php::parsedownElement() serializes keyed style attributes as raw CSS declarations:

$style .= $key . ': ' . $value . ';';

The sanitizer added for direct style() inputs is not reached for values introduced by resize(). As a result, resize=100;position:fixed;...,200 breaks out of the intended width: value and injects additional declarations.

PoC Evidence

On both current 2.0 and 2.0.0-rc.9, the targeted regression test produced the injected style string above. Existing tests still confirm the direct style() and attribute() paths are rejected; the bypass is specific to the adjacent resize() styleAttributes path.

Remediation

Sanitize or type-normalize all values before they enter styleAttributes, not only values passed through MediaObjectTrait::style(). For resize(), cast or validate width and height as numeric values before appending px, or use a shared CSS declaration builder that rejects semicolons, colons, property names, and other declaration-breaking characters. Add regression coverage for resize=100;position:fixed;top:0,200 and any other media action that writes to styleAttributes directly.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistgetgrav/grav≥ 2.0.0-rc.9&&< 2.0.02.0.0composer require getgrav/grav:^2.0.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for getgrav/grav, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update getgrav/grav to 2.0.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-58657 is resolved across your whole dependency graph.

  3. Workarounds

    Escape or sanitise the affected output on the server side rather than relying on client-side filtering, and add a Content-Security-Policy that blocks inline script execution so injected markup cannot run even if it reaches the page.

Frequently Asked Questions

## Summary Grav 2.0.0-rc.9 and the current 2.0 branch still allow stored CSS injection through Markdown image media actions. The prior media hardening rejects direct `?style=` payloads and unsafe `attribute()` fallbacks, but the adjacent `resize()` action still writes caller-controlled values directly into `styleAttributes`. A publisher who can edit page Markdown can store a crafted image URL that renders additional CSS declarations in the final `<img style=...>` attribute. This crosses the same lower-privileged publisher to higher-privileged reviewer/admin rendered-content boundary as the e
O3 Security · Impact-Aware SCA

Is CVE-2026-58657 in your dependencies?

Find it across Packagist, including transitive dependencies.

CVE-2026-58657: grav — Fixed in 2.0.0