fix(security): content-create divi follows the Divi group switches (audit M2)

The divi input of lit/content-create is refused with unsupported when
lit/divi-insert is disabled or the Divi group is disabled or read-only
(ExposurePolicy::allows as a cross-group gate, asked at execute time).
content-create without divi keeps working. Adds a registrar test that
read-only write tools are never registered, so execute-ability can't
reach them either.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 19:16:35 +02:00
co-authored by Claude Opus 5.5
parent 7d0837be6c
commit 5f596b8a17
10 changed files with 169 additions and 6 deletions
+10 -3
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi;
use Closure;
use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Design\DiviDesignSystemAbility;
@@ -58,14 +59,20 @@ final class DiviIntegration implements Integration
return DiviModules::isAvailable();
}
/** Divi short form for lit/content-create; available() reports whether Divi 5 is loaded. */
public function contentComposer(): ContentComposer
/**
* Divi short form for lit/content-create; available() reports whether Divi 5 is loaded,
* writable() asks $writable (the Tools-tab gate, security M2).
*
* @param (Closure(): bool)|null $writable
*/
public function contentComposer(?Closure $writable = null): ContentComposer
{
return new DiviContentComposer(
$this->schema,
new DiviComposition($this->modules, $this->design),
new DiviSaver(),
$this->design
$this->design,
$writable
);
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Pages;
use Closure;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\BlockTree\Document;
@@ -34,12 +35,18 @@ use WP_Error;
*/
final class DiviContentComposer implements ContentComposer
{
/** @var Closure(): bool */
private Closure $writable;
/** @param (Closure(): bool)|null $writable Tools-tab gate (Plugin: lit/divi-insert enabled); null = always. */
public function __construct(
private ContentSchema $schema,
private DiviComposition $composition,
private DiviSaver $saver,
private DesignSystemReader $design
private DesignSystemReader $design,
?Closure $writable = null
) {
$this->writable = $writable ?? static fn (): bool => true;
}
public function available(): bool
@@ -47,6 +54,11 @@ final class DiviContentComposer implements ContentComposer
return DiviModules::isAvailable();
}
public function writable(): bool
{
return ($this->writable)();
}
public function check(string $postType, array $sections): ?WP_Error
{
if (! $this->schema->diviEnabled($postType)) {
@@ -21,6 +21,12 @@ interface ContentComposer
/** Whether composing is possible on this site right now (e.g. Divi 5 active). */
public function available(): bool;
/**
* Whether the site owner lets LIT MCP write such content (Tools tab: for Divi, lit/divi-insert
* is not disabled and the Divi group is neither disabled nor "nur lesen" – security M2).
*/
public function writable(): bool;
/**
* Validates the description for a new entry of $postType. Nothing is written.
*
@@ -118,6 +118,15 @@ final class ContentCreateAbility implements Ability
);
}
if ($withDivi && ! $this->divi->writable()) {
return Errors::unsupported(
'Divi-Inhalte zu schreiben ist in LIT MCP auf dieser Website abgeschaltet; nichts wurde angelegt.',
'Die Seite ohne divi anlegen. Divi-Schreiben kann ein Administrator unter Einstellungen → LIT MCP '
. '→ Tools freigeben (Gruppe Divi nicht „nur lesen“ oder deaktiviert, lit-divi-insert nicht '
. 'deaktiviert).'
);
}
$prepared = $this->writer->prepare($postType, $input, true);
$problem = self::combined($prepared, $withDivi ? $this->checkDivi($postType, $input) : null);
if ($problem !== null) {