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:
co-authored by
Claude Sonnet 5.5
parent
194fd9e183
commit
165e61d7fe
@@ -7,6 +7,8 @@ namespace LIT\MCP\Tests\Unit;
|
||||
use Brain\Monkey\Filters;
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Access;
|
||||
use LIT\MCP\Server\RequestContext;
|
||||
use WP_REST_Request;
|
||||
use WP_User;
|
||||
|
||||
final class AccessTest extends TestCase
|
||||
@@ -93,4 +95,73 @@ final class AccessTest extends TestCase
|
||||
|
||||
self::assertNotFalse(has_filter('user_has_cap', [$access, 'grantCapability']));
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
RequestContext::reset();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> The args of a meta tool as the adapter registers them (permission always granted). */
|
||||
private function metaToolArgs(): array
|
||||
{
|
||||
return ['permission_callback' => static fn (mixed $input = null): bool => true];
|
||||
}
|
||||
|
||||
private function asOAuthServerRequest(): void
|
||||
{
|
||||
RequestContext::recordRoute(null, [], new WP_REST_Request('/mcp/mcp-oauth-server'));
|
||||
}
|
||||
|
||||
public function testRegistersTheFilterThatLimitsTheAdapterMetaTools(): void
|
||||
{
|
||||
$access = new Access();
|
||||
$access->register();
|
||||
|
||||
self::assertNotFalse(has_filter('wp_register_ability_args', [$access, 'limitMetaToolsToOwnAbilities']));
|
||||
}
|
||||
|
||||
/** Security audit 2026-11, B3: execute-ability and get-ability-info may only name lit/ abilities. */
|
||||
public function testMetaToolsRefuseForeignAbilitiesOnTheOAuthServer(): void
|
||||
{
|
||||
$this->asOAuthServerRequest();
|
||||
|
||||
foreach (['mcp-adapter/execute-ability', 'mcp-adapter/get-ability-info'] as $meta) {
|
||||
$permission = (new Access())->limitMetaToolsToOwnAbilities($this->metaToolArgs(), $meta)['permission_callback'];
|
||||
|
||||
self::assertTrue($permission(['ability_name' => 'lit/guide']), $meta);
|
||||
foreach (['akismet/comment-check', 'elementor/delete-page', 'litx/guide', '', 'LIT/guide'] as $foreign) {
|
||||
$result = $permission(['ability_name' => $foreign]);
|
||||
|
||||
self::assertInstanceOf(\WP_Error::class, $result, $meta . ' ' . $foreign);
|
||||
self::assertSame('lit_forbidden', $result->get_error_code());
|
||||
}
|
||||
self::assertInstanceOf(\WP_Error::class, $permission(null), 'no ability_name');
|
||||
self::assertInstanceOf(\WP_Error::class, $permission(['ability_name' => ['lit/guide']]), 'not a string');
|
||||
}
|
||||
}
|
||||
|
||||
public function testMetaToolsKeepTheirOwnCheckOutsideTheOAuthServer(): void
|
||||
{
|
||||
$permission = (new Access())
|
||||
->limitMetaToolsToOwnAbilities($this->metaToolArgs(), 'mcp-adapter/execute-ability')['permission_callback'];
|
||||
|
||||
self::assertTrue($permission(['ability_name' => 'akismet/comment-check']));
|
||||
}
|
||||
|
||||
public function testOtherAbilitiesAreLeftAlone(): void
|
||||
{
|
||||
$args = $this->metaToolArgs();
|
||||
|
||||
self::assertSame($args, (new Access())->limitMetaToolsToOwnAbilities($args, 'lit/guide'));
|
||||
self::assertSame($args, (new Access())->limitMetaToolsToOwnAbilities($args, 'mcp-adapter/discover-abilities'));
|
||||
}
|
||||
|
||||
public function testAMetaToolWithoutAPermissionCallbackFailsClosed(): void
|
||||
{
|
||||
$this->asOAuthServerRequest();
|
||||
$permission = (new Access())->limitMetaToolsToOwnAbilities([], 'mcp-adapter/execute-ability')['permission_callback'];
|
||||
|
||||
self::assertFalse($permission(['ability_name' => 'lit/guide']));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user