fix(security): headingLevel prüfen, überschreibende Tools kennzeichnen, Meta-Tools auf lit/ beschränken

- Divi-Vollformat: headingLevel nur h1 bis h6 (ValueCleaner, StoredNodeGuard, ShortformWriter);
  sonst ließ sich für Konten ohne unfiltered_html ein Skript-Element erzeugen.
- content-update, divi-update und wp-settings-update sind destructive, damit MCP-Clients nachfragen.
- mcp-adapter/execute-ability und get-ability-info nehmen auf dem OAuth-Server nur lit/-Abilities an.
- release-publish.sh übergibt den Gitea-Token per Konfigurationsdatei statt als curl-Argument.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-05 16:36:16 +01:00
co-authored by Claude Sonnet 5.5
parent 194fd9e183
commit 165e61d7fe
14 changed files with 221 additions and 11 deletions
+37
View File
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace LIT\MCP;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Server\RequestContext;
use WP_Error;
use WP_User;
defined('ABSPATH') || exit;
@@ -25,9 +28,13 @@ final class Access
'execute_ability',
];
/** Meta tools of the adapter that name another ability in their input ("ability_name"). */
private const META_ABILITIES_BY_NAME = ['mcp-adapter/execute-ability', 'mcp-adapter/get-ability-info'];
public function register(): void
{
add_filter('user_has_cap', [$this, 'grantCapability'], 10, 4);
add_filter('wp_register_ability_args', [$this, 'limitMetaToolsToOwnAbilities'], 10, 2);
foreach (self::ADAPTER_META_TOOLS as $tool) {
add_filter(
@@ -37,6 +44,36 @@ final class Access
}
}
/**
* Security audit 2026-11 (B3): the adapter's execute-ability and get-ability-info reach every ability with
* meta.mcp.public, from any plugin – past the Tools tab ("aus", "nur lesen"), which knows only lit/ tools.
* On the OAuth-protected server they therefore name lit/ abilities only.
*
* @param array<string, mixed> $args
* @return array<string, mixed>
*/
public function limitMetaToolsToOwnAbilities(array $args, string $name): array
{
if (! in_array($name, self::META_ABILITIES_BY_NAME, true)) {
return $args;
}
$inner = $args['permission_callback'] ?? null;
$args['permission_callback'] = static function (mixed $input = null) use ($inner): bool|WP_Error {
$target = is_array($input) && is_string($input['ability_name'] ?? null) ? $input['ability_name'] : '';
if (RequestContext::isOAuthServerRequest() && ! str_starts_with($target, 'lit/')) {
return Errors::forbidden(
'Über diesen Server sind nur LIT-MCP-Tools nutzbar.',
'Nur Abilities mit dem Präfix lit/ ausführen oder abfragen.'
);
}
return is_callable($inner) ? $inner($input) : false;
};
return $args;
}
/**
* @return list<string>
*/