# Grav: The system, site, and theme Twig variables bypass the content sandbox entirely and are never covered by config_denied_paths

Grav's Twig sandbox copies raw system/site/theme config arrays into editor-rendered page content, so `{{ system.cache.redis.password }}` leaks secrets despite config_denied_paths.

- Payout: Research
- Severity: high
- Program: Grav CMS
- Found by: alham-rizvi
- Disclosed: 2026-09-17
- CVE: CVE-2026-72698
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-p597-crqc-m349
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349
- Tags: area:web domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:php technique:incomplete-fix technique:source-review vuln:auth-bypass vuln:info-disclosure vuln:secrets-leak

**Target:** Twig sandboxed page-content render in Grav CMS
**Root cause:** Twig::init() puts raw system/site/theme config arrays into twig_vars; processPage() copies them into the sandboxed render replacing only `config`. Twig's SecurityPolicy only gates object method/property access, never array key reads, so config_denied_paths is never consulted for these arrays.
**Steps:**
1. Read `system/src/Grav/Common/Twig/Twig.php` init(): `$this->twig_vars += ['system' => $config->get('system'), 'site' => ..., 'theme' => ...]`
2. Read processPage(): `$sandbox_vars = $twig_vars; $sandbox_vars['config'] = $this->buildSandboxConfig();` — only config is replaced
3. Build a Twig Environment with SandboxExtension and a SecurityPolicy with empty allowed_classes/methods/properties (allow `escape` filter since autoescape is forced)
4. Render template `{{ system.cache.redis.password }}` with `system` set to the raw config array
5. Observe the secret rendered in plaintext; sandbox throws no SecurityError
**How found:** Noticed the GHSA-j274-39qw-32c9 fix only wrapped `config` in a SandboxConfig facade while `system`/`site`/`theme` were copied verbatim from twig_vars; reasoned that Twig's SecurityPolicy only arbitrates object access, so plain arrays bypass it entirely, then verified against the exact pinned Twig commit with a maximally strict policy.
**Exploitation:** A content author with page edit rights enables `process.twig: true` in frontmatter (gated by security.twig_content.process_enabled, or unconditional for modular content) and writes `{{ system.cache.redis.password }}` or iterates `site.*`/`theme.*` to dump secrets into rendered page output, readable by the attacker.
**Insight:** When a sandbox fix wraps one variable in a filtering facade, audit every other variable in the same render context — sibling raw arrays (config trees, request data) bypass object-only sandbox policies completely, since Twig never gates array subscript access.

