CVE-2026-76846
Grav before 2.0.16 Information Disclosure via Twig Sandbox
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 config_access is enabled.
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
What each source reports
CVE List Score used
8.7
Grav before 2.0.16 Information Disclosure via Twig Sandbox
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 config_access is enabled.
- Record
CVE-2026-76846- CVSS 4.0
- 8.7 High
CVSS: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 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
EUVDEUVD-2026-65220
8.7
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 config_access is enabled.
- Record
EUVD-2026-65220- CVSS 4.0
- 8.7 High
CVSS: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-crrc-vpp2-f5x7
8.7
Duplicate Advisory: Grav: config_denied_paths default list omits `system`, exposing real secrets (e.g. system.cache.redis.password) via the Twig sandbox when config_access is enabled
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-xjw5-q542-3vmr. This link is maintained to preserve external references.
Original Description
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 config_access is enabled.
- Record
GHSA-crrc-vpp2-f5x7- CVSS 4.0
- 8.7 High
CVSS: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 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-xjw5-q542-3vmr
8.7
Grav: config_denied_paths default list omits `system`, exposing real secrets (e.g. system.cache.redis.password) via the Twig sandbox when config_access is enabled
Summary
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_VERSION2.0.15 - PHP 8.3.6 with curl, zip, dom, gd extensions installed
- Full
composer install --no-devrun against the real repository (no mocked dependencies) so the actualGrav\Common\Config\ConfigandGrav\Common\Twig\Sandbox\SandboxConfigclasses 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
…
- Record
GHSA-xjw5-q542-3vmr- CVSS 4.0
- 8.7 High
CVSS: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 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
NVD
8.7
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 config_access is enabled.
- Record
CVE-2026-76846- CVSS 4.0
- 8.7 High
CVSS: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 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
OSV
8.7
Grav before 2.0.16 Information Disclosure via Twig Sandbox
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 config_access is enabled.
- Record
CVE-2026-76846- CVSS 4.0
- 8.7 High
CVSS: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-522
- Updated
- 2026-11-02
OSVGHSA-crrc-vpp2-f5x7
8.7
Duplicate Advisory: Grav: config_denied_paths default list omits `system`, exposing real secrets (e.g. system.cache.redis.password) via the Twig sandbox when config_access is enabled
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-xjw5-q542-3vmr. This link is maintained to preserve external references.
Original Description
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 config_access is enabled.
- Record
GHSA-crrc-vpp2-f5x7- CVSS 4.0
- 8.7 High
CVSS: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 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
OSVGHSA-xjw5-q542-3vmr
8.7
Grav: config_denied_paths default list omits `system`, exposing real secrets (e.g. system.cache.redis.password) via the Twig sandbox when config_access is enabled
Summary
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_VERSION2.0.15 - PHP 8.3.6 with curl, zip, dom, gd extensions installed
- Full
composer install --no-devrun against the real repository (no mocked dependencies) so the actualGrav\Common\Config\ConfigandGrav\Common\Twig\Sandbox\SandboxConfigclasses 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
…
- Record
GHSA-xjw5-q542-3vmr- CVSS 4.0
- 8.7 High
CVSS: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 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - Weakness
- CWE-522
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
getgrav/gravPackagist |
through 2.0.15 | 2.0.16 |
getgrav/gravPackagist |
before 2.0.16, 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.16 |
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-76846 is a high vulnerability (CVSS 8.7) in Getgrav Grav, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.30%, the 23rd percentile. Fixed in getgrav/grav 2.0.16.
References
github.com/getgrav/grav/security/advisories/GHSA-xjw5-q542-3vmr
vulncheck.com/advisories/grav-before-information-disclosure-via-twig-sandbox
nvd.nist.gov/vuln/detail/CVE-2026-76846
github.com/advisories/GHSA-crrc-vpp2-f5x7
github.com/advisories/GHSA-xjw5-q542-3vmr
github.com/CVEProject/cvelistV5/tree/main/cves/2026/76xxx/CVE-2026-76846.json
github.com/getgrav/grav
Left out here: 300 versions and the rest of 2 source descriptions. The JSON record has them.