{"schema":"threatline.record/2","record":{"id":"GHSA-47gg-mh38-9rr7","aliases":["GHSA-47gg-mh38-9rr7"],"summary":"Page editor can still call arbitrary static methods through a Flex form override","details":"## Summary\nThe fix for GHSA-9rq7-4cch-f22j in Grav 2.2.2 was incomplete. A page editor can still get a `data-*@` directive in a page-authored Flex form past Grav's allowed-callable check, either by renaming the field with `unset-name@` instead of `replace-name@`, or by giving the field an integer key. The moved field is treated as if it came from the trusted blueprint file, so the directive can call any public static method, for example `Folder::delete`, `Folder::move`, `Folder::copy` or `Utils::download`.\n\n## Affected versions\nGrav 2.0.15 through 2.2.12. 2.0.15 is when directives from file blueprints started skipping the allowed-callable check; 2.2.2 closed only the `replace-name@` spelling. 1.7 is not affected.\n\n## Details\nSince 2.0.15, directives from file blueprints are trusted and directives from page-authored forms are held to a `Class::method` allowlist. `Blueprint::extend()` decides which incoming fields are untrusted by reading the incoming form as written, then merges it into the trusted blueprint. Any directive the merge puts at a different path is never marked untrusted, so `Blueprint::init()` gives it the base blueprint's trust and skips the allowlist.\n\n2.2.2 removed `replace-name@` from untrusted input before the merge. The merge in `rockettheme/toolbox` (`BlueprintForm::deepMerge()`) handles `unset` and `replace` in the same branch, so `unset-name@` (also written `@unset-name@` or `unset-name@2`) moves the field, directive included, to a new top-level key in exactly the same way. A second route needs no merge action at all: the merge adds a key the base does not have with `array_merge()`, which renumbers integer keys, so an override field keyed `5` lands at `0` and loses its untrusted attribution.\n\nBoth Flex form builders (`FlexForm`, `FlexDirectoryForm`) extend a file-defined directory blueprint with a form taken from page frontmatter, which is the reachable path. The directive runs when the page's form is built, for example when an administrator opens the page on the site.\n\n## Impact\nAn account with page-edit permission (`api.pages.write`, or `admin.pages` in the classic admin) can call server-side methods its role does not grant, giving arbitrary directory deletion, file move or overwrite, and file read outside the page tree.\n\n## Patches\nFixed in Grav 2.2.13. `unset-name@` is now removed from untrusted input along with `replace-name@`, and after every merge of untrusted input Grav compares the merged directives with the ones the blueprint had before. Any directive that is new or changed is treated as untrusted, wherever the merge put it.\n\n## Credits\nReported by @soquip.\n","categories":["Unclassified"],"product":"getgrav/grav","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/getgrav/grav/security/advisories/GHSA-47gg-mh38-9rr7","source_modified_at":"2026-10-24T04:18:38Z","fetched_at":"2026-10-25T11:14:20.449793Z","package":{"ecosystem":"composer","name":"getgrav/grav"},"range":">= 2.0.15, < 2.2.13"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/getgrav/grav/security/advisories/GHSA-47gg-mh38-9rr7","source_modified_at":"2026-10-24T04:18:38Z","fetched_at":"2026-10-25T11:14:20.449793Z","package":{"ecosystem":"composer","name":"getgrav/grav"},"version":"2.2.13"}],"cvss":null,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-24T04:18:38Z","modified_at":"2026-10-24T04:18:38Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/getgrav/grav/security/advisories/GHSA-47gg-mh38-9rr7","fetched_at":"2026-10-25T11:14:20.449793Z","source_modified_at":"2026-10-24T04:18:38Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-24T04:18:38Z","content_hash":"1d432698c38db6adc21441af71a48d1ec1f31833c2949b8a30fdcb60602acbe0"}}
