Kirby is missing permission checks in the content changes API
Description
Kirby is an open-source content management system. From versions 5.0.0 to 5.2.1, Kirby is missing permission checks in the content changes API. This vulnerability affects all Kirby sites where user permissions are configured to prevent specific role(s) from performing write actions, specifically by disabling the update permission with the intent to prevent modifications to site content. This vulnerability does not affect those who have not altered the deviated from default user permissions. This issue has been patched in version 5.2.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
getkirby/cmsPackagist | >= 5.0.0, < 5.2.2 | 5.2.2 |
Affected products
1Patches
1f5ce1347b427fix: Check permissions in Changes controller
3 files changed · +60 −2
i18n/translations/en.json+4 −0 modified@@ -298,6 +298,10 @@ "error.validation.uuid": "Please enter a valid UUID", "error.validation.url": "Please enter a valid URL", + "error.version.discard.permission": "You are not allowed to discard this version", + "error.version.publish.permission": "You are not allowed to publish this version", + "error.version.save.permission": "You are not allowed to change this version", + "expand": "Expand", "expand.all": "Expand all",
src/Api/Controller/Changes.php+19 −0 modified@@ -5,6 +5,7 @@ use Kirby\Cms\Language; use Kirby\Cms\ModelWithContent; use Kirby\Content\Lock; +use Kirby\Exception\PermissionException; use Kirby\Filesystem\F; use Kirby\Form\Fields; use Kirby\Form\Form; @@ -40,6 +41,12 @@ protected static function cleanup(ModelWithContent $model): void */ public static function discard(ModelWithContent $model): array { + if ($model->permissions()->can('update') === false) { + throw new PermissionException( + key: 'version.discard.permission', + ); + } + $model->version('changes')->delete('current'); // Removes the old .lock file when it is no longer needed @@ -56,6 +63,12 @@ public static function discard(ModelWithContent $model): array */ public static function publish(ModelWithContent $model, array $input): array { + if ($model->permissions()->can('update') === false) { + throw new PermissionException( + key: 'version.publish.permission', + ); + } + // save the given changes first static::save( model: $model, @@ -91,6 +104,12 @@ public static function publish(ModelWithContent $model, array $input): array */ public static function save(ModelWithContent $model, array $input): array { + if ($model->permissions()->can('update') === false) { + throw new PermissionException( + key: 'version.save.permission', + ); + } + // Removes the old .lock file when it is no longer needed // @todo Remove in 6.0.0 static::cleanup($model);
tests/Api/Controller/ChangesTest.php+37 −2 modified@@ -4,6 +4,7 @@ use Kirby\Cms\Page; use Kirby\Data\Data; +use Kirby\Exception\PermissionException; use Kirby\TestCase; class ChangesTest extends TestCase @@ -43,6 +44,8 @@ public function tearDown(): void public function testDiscard(): void { + $this->app->impersonate('kirby'); + Data::write($file = $this->page->root() . '/_changes/article.txt', []); $response = Changes::discard($this->page); @@ -52,6 +55,14 @@ public function testDiscard(): void $this->assertFileDoesNotExist($file); } + public function testDiscardWithoutPermissions(): void + { + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to discard this version'); + + Changes::discard($this->page); + } + public function testPublish(): void { $this->app->impersonate('kirby'); @@ -88,8 +99,18 @@ public function testPublish(): void ], $published); } + public function testPublishWithoutPermissions(): void + { + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to publish this version'); + + Changes::publish($this->page, []); + } + public function testSave(): void { + $this->app->impersonate('kirby'); + Data::write($this->page->root() . '/article.txt', [ // title and uuid should be passed through 'title' => 'Test', @@ -116,12 +137,15 @@ public function testSave(): void $this->assertSame([ 'title' => 'Test', 'text' => 'Test', - 'uuid' => 'test' + 'uuid' => 'test', + 'lock' => 'kirby' ], $changes); } public function testSaveWithNoDiff(): void { + $this->app->impersonate('kirby'); + Data::write($this->page->root() . '/article.txt', [ // title and uuid should be passed through 'title' => 'Test', @@ -150,6 +174,8 @@ public function testSaveWithNoDiff(): void */ public function testSaveWithUndefinedField(): void { + $this->app->impersonate('kirby'); + Data::write($this->page->root() . '/article.txt', [ // title and uuid should be passed through 'title' => 'Test', @@ -168,7 +194,16 @@ public function testSaveWithUndefinedField(): void 'title' => 'Test', 'text' => 'Test', 'uuid' => 'test', - 'undefined' => 'This should be passed through' + 'undefined' => 'This should be passed through', + 'lock' => 'kirby' ], $changes); } + + public function testSaveWithoutPermissions(): void + { + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to change this version'); + + Changes::save($this->page, []); + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-4j78-4xrm-cr2fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-21896ghsaADVISORY
- github.com/getkirby/kirby/commit/f5ce1347b427b819bf193acf11fd0da232f7af47ghsax_refsource_MISCWEB
- github.com/getkirby/kirby/releases/tag/5.2.2ghsax_refsource_MISCWEB
- github.com/getkirby/kirby/security/advisories/GHSA-4j78-4xrm-cr2fghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.