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:
@@ -196,6 +196,24 @@ final class ExposurePolicy
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-group gate: whether the ability named $name is among $abilities and not disabled.
|
||||
* lit/content-create's divi input follows lit/divi-insert this way (security M2), so the Divi
|
||||
* group's "nur lesen"/disabled switch also stops Divi layouts written through another group.
|
||||
*
|
||||
* @param list<Ability> $abilities
|
||||
*/
|
||||
public function allows(string $name, array $abilities): bool
|
||||
{
|
||||
foreach ($abilities as $ability) {
|
||||
if ($ability->name() === $name) {
|
||||
return $this->mode($ability) !== self::DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Ability> $abilities
|
||||
* @return list<string>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+10
-1
@@ -68,9 +68,18 @@ final class Plugin
|
||||
|
||||
$schema = self::contentSchema();
|
||||
$divi = new DiviIntegration($schema, $changeLog);
|
||||
$registry = null;
|
||||
$policy = null;
|
||||
// Security M2: content-create's divi input follows lit/divi-insert (asked at execute time,
|
||||
// when registry and policy exist), so the Divi group's switches stop every Divi layout write.
|
||||
$diviWritable = static function () use (&$registry, &$policy): bool {
|
||||
return $registry instanceof IntegrationRegistry
|
||||
&& $policy instanceof ExposurePolicy
|
||||
&& $policy->allows('lit/divi-insert', $registry->abilities());
|
||||
};
|
||||
$registry = new IntegrationRegistry(
|
||||
new CoreIntegration(self::pluginDir() . '/docs/guides'),
|
||||
new WordPressIntegration($schema, $changeLog, $divi->contentComposer()),
|
||||
new WordPressIntegration($schema, $changeLog, $divi->contentComposer($diviWritable)),
|
||||
new MediaIntegration($schema, $changeLog, $limits),
|
||||
$divi
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user