PluginProbe ʕ •ᴥ•ʔ
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / 5.1.3
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback v5.1.3
5.1.3 5.1.2 5.1.1 5.1 5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 3.3.0 3.3.1 3.3.2 3.3.2.1 3.3.2.2 3.3.3 3.30 3.31 3.32 3.4 3.4.1 3.4.3 3.4.4 3.5 3.5.1 3.6 3.6.1 3.7 3.8 3.9 3.9.1 3.9.2 3.9.3 3.9.4 3.9.6 3.9.6.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4
atarim-visual-collaboration / doit / abilities / class-avcf-abilities-execute-php.php
atarim-visual-collaboration / doit / abilities Last commit date
class-avcf-abilities-base.php 4 days ago class-avcf-abilities-block-navigation.php 4 days ago class-avcf-abilities-cache.php 4 days ago class-avcf-abilities-content.php 4 days ago class-avcf-abilities-core.php 4 days ago class-avcf-abilities-execute-php.php 4 days ago class-avcf-abilities-global-styles.php 4 days ago class-avcf-abilities-gutenberg.php 4 days ago class-avcf-abilities-media.php 4 days ago class-avcf-abilities-metadata.php 4 days ago class-avcf-abilities-navigation.php 4 days ago class-avcf-abilities-patterns.php 4 days ago class-avcf-abilities-plugins.php 4 days ago class-avcf-abilities-readonly.php 4 days ago class-avcf-abilities-settings.php 4 days ago class-avcf-abilities-taxonomies.php 4 days ago class-avcf-abilities-templates.php 4 days ago class-avcf-abilities-theme-files.php 4 days ago class-avcf-abilities-themes.php 4 days ago class-avcf-abilities-users.php 4 days ago class-avcf-abilities-wp-cli.php 4 days ago
class-avcf-abilities-execute-php.php
142 lines
1 <?php
2 /**
3 * execute-php: run arbitrary PHP (developer / debug capability).
4 *
5 * This is the most powerful ability in the plugin — arbitrary code execution,
6 * equivalent in reach to WP-CLI `wp eval`, Code Snippets, or WP Console. It is
7 * therefore heavily gated:
8 *
9 * - Refuses when DISALLOW_FILE_EDIT / DISALLOW_FILE_MODS is set — a site that
10 * forbids code changes should not have an eval endpoint either. This is the
11 * plugin-side gate. Opt-in / consent for actually invoking this ability is
12 * handled on the AGENT side (the MCP client prompts the operator before the
13 * call), so this ability is not additionally blocklisted plugin-side.
14 * - Requires `manage_options` (administrator).
15 * - Every execution is audit-logged (user, time, code hash) via error_log and
16 * the `avcf_execute_php_audit` action so a site can capture it.
17 * - Output and return value are size-capped; execution is wrapped in a
18 * Throwable catch (catches Errors/ParseErrors; true fatals like OOM are not
19 * catchable in PHP and will surface as a request failure).
20 *
21 * @package atarim-visual-collaboration
22 */
23
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit;
26 }
27
28 class AVCF_Abilities_ExecutePHP {
29
30 /** Max characters returned for captured output and for the return value each. */
31 const MAX_OUTPUT = 102400; // 100 KB
32
33 public function register() {
34 if ( ! function_exists( 'wp_register_ability' ) ) {
35 return;
36 }
37 $self = $this;
38 wp_register_ability( 'atarim/execute-php', [
39 'label' => 'Execute PHP (developer)',
40 'description' => 'Run a snippet of PHP on the server and return its echoed output and return value. Powerful developer/debug tool — equivalent to WP-CLI "wp eval". Do NOT include a "<?php" opening tag; the code is evaluated directly (use "return $x;" to return a value). WordPress is fully loaded, so core/plugin functions and $wpdb are available. Every call is audit-logged. Refused when the site disables file editing (DISALLOW_FILE_EDIT). Output and return value are capped at 100 KB each.',
41 'category' => 'atarim',
42 'input_schema' => [
43 'type' => 'object',
44 'properties' => [
45 'code' => [ 'type' => 'string', 'description' => 'PHP code to evaluate (no <?php tag). Use return to hand back a value; echo/print is captured as output.' ],
46 ],
47 'required' => [ 'code' ],
48 'additionalProperties' => false,
49 ],
50 'output_schema' => [
51 'type' => 'object',
52 'properties' => [
53 'success' => [ 'type' => 'boolean' ],
54 'executed' => [ 'type' => 'boolean' ],
55 'return_value' => [ 'type' => 'string' ],
56 'output' => [ 'type' => 'string' ],
57 'output_truncated' => [ 'type' => 'boolean' ],
58 'error' => [ 'type' => 'string' ],
59 'message' => [ 'type' => 'string' ],
60 ],
61 'required' => [ 'success', 'message' ],
62 ],
63 'execute_callback' => function( $input = [] ) use ( $self ) {
64 if ( ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) || ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) ) {
65 return [ 'success' => false, 'executed' => false, 'message' => 'Refused: PHP execution is turned off because this site disables code/file modifications at the configuration level (DISALLOW_FILE_EDIT or DISALLOW_FILE_MODS is set in wp-config.php). This is a permanent, site-wide setting, so retrying will not help — an administrator would need to change the site configuration to allow it.' ];
66 }
67 $code = isset( $input['code'] ) ? (string) $input['code'] : '';
68 if ( '' === trim( $code ) ) {
69 return [ 'success' => false, 'executed' => false, 'message' => 'code is required.' ];
70 }
71 // A leading <?php would be a parse error under eval(); strip one if present.
72 $code = preg_replace( '/^\s*<\?php\s*/', '', $code );
73
74 $self->audit( $code );
75
76 $error = null;
77 $ret = null;
78 ob_start();
79 try {
80 $ret = eval( $code ); // phpcs:ignore Squiz.PHP.Eval
81 } catch ( \Throwable $e ) {
82 $error = $e->getMessage() . ' (' . get_class( $e ) . ')';
83 }
84 $output = (string) ob_get_clean();
85
86 $ret_str = $self->cap( ( null === $ret ) ? 'null' : var_export( $ret, true ) );
87 $out_cap = $self->cap( $output );
88
89 if ( null !== $error ) {
90 return [
91 'success' => false,
92 'executed' => true,
93 'output' => $out_cap['text'],
94 'error' => $error,
95 'message' => 'PHP raised an error: ' . $error,
96 ];
97 }
98 return [
99 'success' => true,
100 'executed' => true,
101 'return_value' => $ret_str['text'],
102 'output' => $out_cap['text'],
103 'output_truncated' => ( $out_cap['truncated'] || $ret_str['truncated'] ),
104 'message' => 'Executed.' . ( ( $out_cap['truncated'] || $ret_str['truncated'] ) ? ' Output/return value was truncated at 100 KB.' : '' ),
105 ];
106 },
107 'permission_callback' => function() { return current_user_can( 'manage_options' ); },
108 'meta' => [
109 'mcp' => [ 'public' => true, 'type' => 'tool' ],
110 'annotations' => [ 'readonly' => false, 'destructive' => true, 'idempotent' => false ],
111 ],
112 ] );
113 }
114
115 /** Record an execution to the error log and an action hook. */
116 public function audit( $code ) {
117 $user = function_exists( 'wp_get_current_user' ) ? wp_get_current_user() : null;
118 $entry = [
119 'time' => gmdate( 'c' ),
120 'user_id' => ( $user && isset( $user->ID ) ) ? (int) $user->ID : 0,
121 'login' => ( $user && isset( $user->user_login ) ) ? (string) $user->user_login : '',
122 'sha1' => sha1( (string) $code ),
123 'bytes' => strlen( (string) $code ),
124 'preview' => substr( (string) $code, 0, 200 ),
125 ];
126 error_log( sprintf( // phpcs:ignore
127 '[atarim/execute-php] user=%d(%s) bytes=%d sha1=%s',
128 $entry['user_id'], $entry['login'], $entry['bytes'], $entry['sha1']
129 ) );
130 do_action( 'avcf_execute_php_audit', $entry );
131 }
132
133 /** Cap a string to MAX_OUTPUT; returns [ 'text' => ..., 'truncated' => bool ]. */
134 public function cap( $text ) {
135 $text = (string) $text;
136 if ( strlen( $text ) > self::MAX_OUTPUT ) {
137 return [ 'text' => substr( $text, 0, self::MAX_OUTPUT ), 'truncated' => true ];
138 }
139 return [ 'text' => $text, 'truncated' => false ];
140 }
141 }
142