getgrav/grav · critical · InstallException

Could not migrate the Twig-sandbox allowlists to the additiv

Error message

Could not migrate the Twig-sandbox allowlists to the additive-defaults model

What it means

This InstallException is thrown by the Grav 1.8 postflight update that migrates security.twig_sandbox.allowed_* lists from the old replace semantics to additive defaults. The routine reads user/config/security.yaml, asks Security::planSandboxDefaultsMigration() for omitted defaults that must become denied_* entries, and saves the file with YamlUpdater. Any exception during read, planning, update, or save is wrapped as this installation failure so an upgrade cannot silently lose a tightened Twig-sandbox policy.

Source

Thrown at system/src/Grav/Installer/updates/1.8.0_2026-08-12_0.php:74

                        }
                    }
                    $yaml->define("twig_sandbox.{$key}", array_values($merged));
                }

                $yaml->save();

                $summary = [];
                foreach ($plan as $key => $additions) {
                    $summary[] = $key . ' (' . count($additions) . ')';
                }
                error_log(
                    'Grav upgrade: preserved tightened Twig-sandbox policy in '
                    . 'user/config/security.yaml by adding ' . implode(', ', $summary)
                    . '. Review these denials if any were defaults you actually want; '
                    . 'see security.twig_sandbox.denied_* in the file.'
                );
            } catch (\Exception $e) {
                throw new InstallException('Could not migrate the Twig-sandbox allowlists to the additive-defaults model', $e);
            }
        }
];

View on GitHub (pinned to 6040efed04)

Solutions

  1. Inspect InstallException::getPrevious(); the wrapped exception names the real YAML, permission, or write failure.
  2. Make user/config/security.yaml and user/config readable and writable by the PHP process, for example chown www-data:www-data user/config/security.yaml and chmod 0644 user/config/security.yaml.
  3. Validate the YAML file, fix syntax errors or restore a known-good backup, then rerun the failed Grav update; the migration is documented as idempotent.
  4. If the update cannot be made writable, back up security.yaml, temporarily remove the twig_sandbox overrides, complete the update, then manually reproduce the old policy by adding every omitted default to the matching denied_* list.
  5. Retry the update after freeing disk space or remounting the configuration volume as writable.

Example fix

// before
chown root:root user/config/security.yaml
chmod 0400 user/config/security.yaml
# Grav update fails: Could not migrate the Twig-sandbox allowlists

// after
chown www-data:www-data user/config/security.yaml
chmod 0644 user/config/security.yaml
php -r 'exit(is_readable("user/config/security.yaml") ? 0 : 1);'
# rerun the Grav update; re-running merges the same denials safely
Defensive patterns

Strategy: validation

Validate before calling

$file = GRAV_ROOT . '/user/config/security.yaml';
if (is_file($file)) {
    if (!is_readable($file) || !is_writable($file)) {
        throw new RuntimeException('Make user/config/security.yaml readable and writable before updating Grav.');
    }
    try {
        Symfony\Component\Yaml\Yaml::parseFile($file);
    } catch (Throwable $e) {
        throw new RuntimeException('Fix security.yaml syntax before updating Grav: ' . $e->getMessage(), 0, $e);
    }
}

Try / catch

try {
    $updater->update();
} catch (InstallException $e) {
    $cause = $e->getPrevious();
    log_error('Grav update failed: ' . $e->getMessage() . '; cause: ' . ($cause ? $cause->getMessage() : 'unknown'));
    restore_configuration_backup();
    throw $e;
}

Prevention

When it happens

Trigger: Running a Grav update that includes 1.8.0_2026-08-12_0.php while user/config/security.yaml exists and contains one or more tightened twig_sandbox allowed_* lists, and the file cannot be parsed or saved. Typical failing calls are YamlUpdater::instance($file), $yaml->define(...), or $yaml->save() when permissions are wrong, YAML is invalid, disk is full, or the config tree is read-only.

Common situations: Config files owned by root or an FTP user after deployment, read-only containers, invalid YAML such as tabs or duplicate keys, a full disk, or a restored backup with different ownership. Sites that intentionally removed default Twig tags, filters, functions, or classes from allowed_* are exactly the sites this migration tries to protect.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/ef16aea4ce75390d. Report an issue: GitHub.