fix(security): no direct HTTP access to plugin PHP files (audit L2, cheap part)

Every file under src/ exits outside WordPress (defined('ABSPATH') ||
exit; after the use block, so a direct request no longer prints a fatal
error with the server path); divi-convert-fixture.php gets the WP_CLI
guard the other integration scripts have. A unit test keeps both true;
PSR1 side-effects warning excluded for the guard.

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:32:12 +02:00
co-authored by Claude Opus 5.5
parent 4d4bf57c9f
commit b24eb6c3e9
185 changed files with 418 additions and 1 deletions
+2
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
defined('ABSPATH') || exit;
/**
* Media helpers (download_url, wp_tempnam, media_handle_sideload, …) live in
* wp-admin/includes and are not loaded on REST requests.
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
defined('ABSPATH') || exit;
/**
* The pure part of MediaUsageFinder: whether a piece of stored content (post content or a
* block widget) references an attachment – by ID (wp-image-<id>, [gallery ids], Divi 4
@@ -13,6 +13,8 @@ use LIT\MCP\ChangeLog\ChangeLog;
use WP_Error;
use WP_Post;
defined('ABSPATH') || exit;
/**
* lit/media-delete: deletes one attachment after confirmation. WordPress has no media trash by
* default (MEDIA_TRASH false): wp_delete_attachment($id, false) then deletes the attachment and
@@ -10,6 +10,8 @@ use LIT\MCP\Abilities\Errors;
use LIT\MCP\Limits;
use WP_Error;
defined('ABSPATH') || exit;
/**
* Delivers a media file (original or a named image size) as an MCP embedded resource
* (spec §4.3). Direct tool: like ImageContent (lit-media-preview), an embedded resource
+2
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
defined('ABSPATH') || exit;
/**
* Shared file-path safety check for lit-media-preview and lit-media-download
* (controller ruling: never read a file outside the uploads directory, even
@@ -8,6 +8,8 @@ use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\Annotations;
use WP_Error;
defined('ABSPATH') || exit;
final class MediaGetAbility implements Ability
{
public function __construct(private MediaPresenter $presenter, private MediaUsageFinder $finder)
+2
View File
@@ -9,6 +9,8 @@ use LIT\MCP\Integrations\WordPress\PostGuard;
use WP_Error;
use WP_Post;
defined('ABSPATH') || exit;
/**
* Loads an attachment for abilities and checks the capability on that attachment
* (spec §4.5). Every error is ready to be returned from permission()/execute().
+2
View File
@@ -13,6 +13,8 @@ use Throwable;
use WP_Error;
use WpOrg\Requests\Exception as RequestsException;
defined('ABSPATH') || exit;
/**
* Imports files into the media library (spec §6.3, §10).
*
@@ -9,6 +9,8 @@ use LIT\MCP\Integrations\Integration;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Limits;
defined('ABSPATH') || exit;
/**
* Media library tools (spec §6.3). Always available. $log and $limits serve the write
* tools (upload, update, delete).
@@ -8,6 +8,8 @@ use LIT\MCP\Integrations\WordPress\PostRow;
use RuntimeException;
use WP_Post;
defined('ABSPATH') || exit;
/**
* Writes the editable metadata of an attachment (alt text, title, caption, description) and
* restores it from a snapshot taken before (lit/media-update rolls a batch back with it).
@@ -7,6 +7,8 @@ namespace LIT\MCP\Integrations\Media;
use LIT\MCP\Abilities\JsonMap;
use WP_Post;
defined('ABSPATH') || exit;
/**
* Output format of an attachment. Not final so abilities can be tested with a mock.
*/
@@ -9,6 +9,8 @@ use LIT\MCP\Abilities\Annotations;
use LIT\MCP\Abilities\Errors;
use WP_Error;
defined('ABSPATH') || exit;
/**
* Renders a downscaled preview of an image attachment as an MCP ImageContent block
* (spec §4.3). Direct tool only: the mcp-adapter only turns `{type: 'image', …}` into
@@ -12,6 +12,8 @@ use LIT\MCP\Integrations\WordPress\PostGuard;
use WP_Error;
use WP_Post;
defined('ABSPATH') || exit;
/**
* Media library search (spec §6.3). Direct tool. `unused` only ever checks a small,
* bounded number of candidates per call via MediaUsageFinder::isUsed() (each call does one
@@ -17,6 +17,8 @@ use Throwable;
use WP_Error;
use WP_Post;
defined('ABSPATH') || exit;
/**
* lit/media-update: metadata of up to 50 attachments in one call, all or nothing.
* Every item is validated first (attachment, edit_post, lengths, optional `modified` lock);
@@ -9,6 +9,8 @@ use LIT\MCP\Abilities\Annotations;
use LIT\MCP\Limits;
use WP_Error;
defined('ABSPATH') || exit;
/**
* Uploads a Base64-encoded file into the media library (spec §6.3), up to the configured
* upload limit (at most Limits::INLINE_MAX_MB, the request carries the whole file).
@@ -8,6 +8,8 @@ use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\Annotations;
use WP_Error;
defined('ABSPATH') || exit;
/**
* Imports a publicly reachable file into the media library (spec §6.3). Direct tool.
* Download, checks and cleanup: MediaImporter; permission and result: MediaUploads.
+2
View File
@@ -14,6 +14,8 @@ use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use WP_Error;
use WP_Post;
defined('ABSPATH') || exit;
/**
* What both upload abilities share (lit/media-upload-from-url, lit/media-upload): the
* permission check, running the import and building the result for the new attachment.
@@ -8,6 +8,8 @@ use Closure;
use LIT\MCP\Integrations\WordPress\PostStatuses;
use LIT\MCP\Log;
defined('ABSPATH') || exit;
/**
* Finds where an attachment is used (spec §6.3). Deliberately errs on the side of "used":
* a false positive only asks for force: true, a false negative would break a page.
+2
View File
@@ -6,6 +6,8 @@ namespace LIT\MCP\Integrations\Media;
use Closure;
defined('ABSPATH') || exit;
/**
* The plugin's own check that a URL import only reaches public addresses (security audit M3), in
* addition to wp_http_validate_url(): older WordPress versions (the plugin supports 6.9) block