{"record":{"id":"ef16aea4ce75390d","repo":"getgrav/grav","slug":"could-not-migrate-the-twig-sandbox-allowlists-to-t","errorCode":null,"errorMessage":"Could not migrate the Twig-sandbox allowlists to the additive-defaults model","messagePattern":"Could not migrate the Twig-sandbox allowlists to the additive-defaults model","errorType":"exception","errorClass":"InstallException","httpStatus":null,"severity":"critical","filePath":"system/src/Grav/Installer/updates/1.8.0_2026-08-12_0.php","lineNumber":74,"sourceCode":"                        }\n                    }\n                    $yaml->define(\"twig_sandbox.{$key}\", array_values($merged));\n                }\n\n                $yaml->save();\n\n                $summary = [];\n                foreach ($plan as $key => $additions) {\n                    $summary[] = $key . ' (' . count($additions) . ')';\n                }\n                error_log(\n                    'Grav upgrade: preserved tightened Twig-sandbox policy in '\n                    . 'user/config/security.yaml by adding ' . implode(', ', $summary)\n                    . '. Review these denials if any were defaults you actually want; '\n                    . 'see security.twig_sandbox.denied_* in the file.'\n                );\n            } catch (\\Exception $e) {\n                throw new InstallException('Could not migrate the Twig-sandbox allowlists to the additive-defaults model', $e);\n            }\n        }\n];\n","sourceCodeStart":56,"sourceCodeEnd":78,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Installer/updates/1.8.0_2026-08-12_0.php#L56-L78","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect InstallException::getPrevious(); the wrapped exception names the real YAML, permission, or write failure.","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.","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.","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.","Retry the update after freeing disk space or remounting the configuration volume as writable."],"exampleFix":"// before\nchown root:root user/config/security.yaml\nchmod 0400 user/config/security.yaml\n# Grav update fails: Could not migrate the Twig-sandbox allowlists\n\n// after\nchown www-data:www-data user/config/security.yaml\nchmod 0644 user/config/security.yaml\nphp -r 'exit(is_readable(\"user/config/security.yaml\") ? 0 : 1);'\n# rerun the Grav update; re-running merges the same denials safely","handlingStrategy":"validation","validationCode":"$file = GRAV_ROOT . '/user/config/security.yaml';\nif (is_file($file)) {\n    if (!is_readable($file) || !is_writable($file)) {\n        throw new RuntimeException('Make user/config/security.yaml readable and writable before updating Grav.');\n    }\n    try {\n        Symfony\\Component\\Yaml\\Yaml::parseFile($file);\n    } catch (Throwable $e) {\n        throw new RuntimeException('Fix security.yaml syntax before updating Grav: ' . $e->getMessage(), 0, $e);\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    $updater->update();\n} catch (InstallException $e) {\n    $cause = $e->getPrevious();\n    log_error('Grav update failed: ' . $e->getMessage() . '; cause: ' . ($cause ? $cause->getMessage() : 'unknown'));\n    restore_configuration_backup();\n    throw $e;\n}","preventionTips":["Keep user/config writable by the PHP process during Grav updates.","Lint security.yaml in CI or a pre-deployment check.","Back up security.yaml before every Grav upgrade.","Test major upgrades on a staging copy when custom twig_sandbox allowlists exist.","Never mark an update complete after this exception; inspect the wrapped cause first."],"tags":["grav","installer","upgrade","yaml","permissions","twig-sandbox","security-config"],"backgroundTag":"config-migration-failed","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}