v2.2.0: includes/Tools/CliTools.php

This commit is contained in:
2026-05-29 12:44:11 +00:00
parent 39652c42af
commit c70d2010dc
+84
View File
@@ -0,0 +1,84 @@
<?php
namespace AskTamMCP\Tools;
defined('ABSPATH') || exit;
class CliTools {
private static array $ALLOWED_COMMANDS = [
'cache', 'cron', 'db', 'eval', 'export', 'import', 'media',
'option', 'plugin', 'post', 'rewrite', 'role', 'search-replace',
'site', 'term', 'theme', 'transient', 'user', 'widget',
'elementor', 'wc', 'yoast',
];
private static array $BLOCKED_PATTERNS = [
'/\brm\b/', '/\bdd\b/', '/\bmkfs\b/', '/;/', '/&&/', '/\|\|/',
'/`/', '/\$\(/', '/>/','/</', '/\beval\b/', '/\bexec\b/',
'/\bpasswd\b/', '/\bsudo\b/', '/\bchmod\b/', '/\bchown\b/',
];
public static function run(array $args): array {
if (!self::isWpCliAvailable()) {
throw new \RuntimeException("WP-CLI is not available on this server.");
}
$command = trim($args['command'] ?? '');
if (empty($command)) throw new \RuntimeException("Command is required.");
// Security: block shell injection patterns
foreach (self::$BLOCKED_PATTERNS as $pattern) {
if (preg_match($pattern, $command)) {
throw new \RuntimeException("Command contains blocked pattern: $pattern");
}
}
// Validate first word is an allowed WP-CLI command
$parts = preg_split('/\s+/', $command);
$first = strtolower($parts[0] ?? '');
if (!in_array($first, self::$ALLOWED_COMMANDS, true)) {
throw new \RuntimeException("Command '$first' is not in the allowed list: " . implode(', ', self::$ALLOWED_COMMANDS));
}
$wp_cli = self::findWpCli();
$wp_path = ABSPATH;
$full_cmd = escapeshellarg($wp_cli) . ' ' . $command . ' --path=' . escapeshellarg($wp_path) . ' --no-color 2>&1';
$output = [];
$exit_code = 0;
exec($full_cmd, $output, $exit_code);
return [
'command' => 'wp ' . $command,
'exit_code' => $exit_code,
'success' => $exit_code === 0,
'output' => implode("\n", $output),
];
}
public static function isAvailable(array $args): array {
$available = self::isWpCliAvailable();
return [
'available' => $available,
'path' => $available ? self::findWpCli() : null,
'version' => $available ? shell_exec(self::findWpCli() . ' --version --no-color 2>&1') : null,
'allowed_cmds'=> self::$ALLOWED_COMMANDS,
];
}
private static function isWpCliAvailable(): bool {
return !empty(self::findWpCli());
}
private static function findWpCli(): string {
foreach (['/usr/local/bin/wp', '/usr/bin/wp', '/home/*/bin/wp'] as $path) {
if (strpos($path, '*') !== false) {
$matches = glob($path);
if ($matches) return $matches[0];
} elseif (file_exists($path) && is_executable($path)) {
return $path;
}
}
// Try which
$which = trim(shell_exec('which wp 2>/dev/null') ?: '');
return $which;
}
}