VYPR
Moderate severityOSV Advisory· Published Jan 8, 2026· Updated Jan 8, 2026

Kirby is missing permission checks in the content changes API

CVE-2026-21896

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.

PackageAffected versionsPatched versions
getkirby/cmsPackagist
>= 5.0.0, < 5.2.25.2.2

Affected products

1

Patches

1
f5ce1347b427

fix: Check permissions in Changes controller

https://github.com/getkirby/kirbyBastian AllgeierJan 6, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.