## Ticket: CVE-2026-24425 — Twig sandbox bypass via `SourcePolicy` filter check

**Advisory**: GHSA-2q52-x2ff-qgfr — https://github.com/twigphp/Twig/security/advisories/GHSA-2q52-x2ff-qgfr
**CVE**: CVE-2026-24425 | **CWE-693** (Protection Mechanism Failure)
**Severity**: High — CVSS 8.7
**Package**: `twig/twig` (Packagist) | **Repository**: https://github.com/twigphp/Twig

### Impact

Twig's sandbox can be enabled *per-template* through a
`SourcePolicyInterface`, rather than enabled globally for every template.

When the sandbox is enabled this way, the runtime security check for the
`sort`, `filter`, `map` and `reduce` filters **fails to consult the current
template's source**. As a result the `SourcePolicy` is never applied to those
four filters.

An attacker who can supply template content can therefore pass an arbitrary
PHP callable to one of these filters — for example `system` — and have it
executed, escaping the sandbox and achieving code execution.

### Affected / fixed versions

Affected: `twig/twig` `3.9.0` through `3.25.x` (and the `2.16.x` line).
Fixed: **`3.26.0`** (NVD references the `v3.26.0` release tag).

Reproduce on a vulnerable build (**`3.25.0`**) and verify the fix on
**`3.26.0`**.

### Where to look

The repository is https://github.com/twigphp/Twig . Inspect the diff between
the tags to confirm the root cause and the guard:

```bash
git clone https://github.com/twigphp/Twig.git
cd Twig && git diff v3.25.0 v3.26.0
```

The fix makes the runtime security check for `sort`/`filter`/`map`/`reduce`
consult the current template's source so the `SourcePolicy` is correctly
applied to those filters.

### Reproduction approach

The bypass is specific to per-template sandboxing via
`SourcePolicyInterface` — the PoC must configure the sandbox that way. No
network service, database, or browser is required; the difference is
observable in-process.

1. In a scratch directory, install the **vulnerable** build:
   `composer require twig/twig:3.25.0`
2. Configure a Twig environment whose sandbox is enabled per-template through a
   `SourcePolicyInterface`.
3. Render attacker-supplied template content that applies an affected filter
   with a dangerous PHP callable, e.g. `{{ ['id']|map('system') }}` or
   `|filter` with a dangerous callable.
4. Repeat with the **fixed** build: `composer require twig/twig:3.26.0`.

### Expected result

| Build | Render `{{ ['id']|map('system') }}` under a `SourcePolicy` sandbox | Observable |
|-------|---------------------------------------------------------------------|------------|
| `twig/twig:3.25.0` (vulnerable) | `SourcePolicy` not applied to `map` | **callable executed** — command runs / side effect occurs |
| `twig/twig:3.26.0` (fixed) | runtime check consults the source; `SourcePolicy` applied | **Twig `SecurityError` thrown** |

- **Vulnerable indicator**: the PHP callable passed to the filter is executed
  (the command runs / the observable side effect occurs) despite the
  `SourcePolicy`-based sandbox.
- **Fixed indicator**: rendering the same template throws a Twig
  `SecurityError` and the callable is never invoked.

### Expected artifacts

- `reproduction_steps.sh` — installs both versions, sets up the per-template
  `SourcePolicy` sandbox, and renders the dangerous template for each build.
- `validation_verdict.json` — `verdict: confirmed`, with the vulnerable and
  fixed indicators captured.
- Logs capturing the render output / thrown exception for `3.25.0` and
  `3.26.0`.
