feat: includes/Tools/CliTools.php

This commit is contained in:
2026-05-29 12:51:47 +00:00
parent 0704ac02a6
commit 83b259561f
+19 -17
View File
@@ -13,7 +13,7 @@ class CliTools {
private static array $BLOCKED_PATTERNS = [ private static array $BLOCKED_PATTERNS = [
'/\brm\b/', '/\bdd\b/', '/\bmkfs\b/', '/;/', '/&&/', '/\|\|/', '/\brm\b/', '/\bdd\b/', '/\bmkfs\b/', '/;/', '/&&/', '/\|\|/',
'/`/', '/\$\(/', '/>/','/</', '/\beval\b/', '/\bexec\b/', '/`/', '/\$\(/', '/>/', '/</', '/\beval\b/', '/\bexec\b/',
'/\bpasswd\b/', '/\bsudo\b/', '/\bchmod\b/', '/\bchown\b/', '/\bpasswd\b/', '/\bsudo\b/', '/\bchmod\b/', '/\bchown\b/',
]; ];
@@ -24,23 +24,24 @@ class CliTools {
$command = trim($args['command'] ?? ''); $command = trim($args['command'] ?? '');
if (empty($command)) throw new \RuntimeException("Command is required."); if (empty($command)) throw new \RuntimeException("Command is required.");
// Security: block shell injection patterns
foreach (self::$BLOCKED_PATTERNS as $pattern) { foreach (self::$BLOCKED_PATTERNS as $pattern) {
if (preg_match($pattern, $command)) { if (preg_match($pattern, $command)) {
throw new \RuntimeException("Command contains blocked pattern: $pattern"); throw new \RuntimeException("Command contains blocked pattern.");
} }
} }
// Validate first word is an allowed WP-CLI command
$parts = preg_split('/\s+/', $command); $parts = preg_split('/\s+/', $command);
$first = strtolower($parts[0] ?? ''); $first = strtolower($parts[0] ?? '');
if (!in_array($first, self::$ALLOWED_COMMANDS, true)) { if (!in_array($first, self::$ALLOWED_COMMANDS, true)) {
throw new \RuntimeException("Command '$first' is not in the allowed list: " . implode(', ', self::$ALLOWED_COMMANDS)); throw new \RuntimeException("Command '$first' is not allowed. Allowed: " . implode(', ', self::$ALLOWED_COMMANDS));
} }
$wp_cli = self::findWpCli(); $wp_cli = self::findWpCli();
$wp_path = ABSPATH; $wp_path = ABSPATH;
$full_cmd = escapeshellarg($wp_cli) . ' ' . $command . ' --path=' . escapeshellarg($wp_path) . ' --no-color 2>&1'; $full_cmd = escapeshellarg($wp_cli)
. ' ' . $command
. ' --path=' . escapeshellarg($wp_path)
. ' --allow-root --no-color 2>&1';
$output = []; $output = [];
$exit_code = 0; $exit_code = 0;
@@ -56,11 +57,16 @@ class CliTools {
public static function isAvailable(array $args): array { public static function isAvailable(array $args): array {
$available = self::isWpCliAvailable(); $available = self::isWpCliAvailable();
$version = null;
if ($available) {
$wp_cli = self::findWpCli();
$version = trim((string) shell_exec(escapeshellarg($wp_cli) . ' --version --allow-root --no-color 2>&1'));
}
return [ return [
'available' => $available, 'available' => $available,
'path' => $available ? self::findWpCli() : null, 'path' => $available ? self::findWpCli() : null,
'version' => $available ? shell_exec(self::findWpCli() . ' --version --no-color 2>&1') : null, 'version' => $version,
'allowed_cmds'=> self::$ALLOWED_COMMANDS, 'allowed_cmds' => self::$ALLOWED_COMMANDS,
]; ];
} }
@@ -69,16 +75,12 @@ class CliTools {
} }
private static function findWpCli(): string { private static function findWpCli(): string {
foreach (['/usr/local/bin/wp', '/usr/bin/wp', '/home/*/bin/wp'] as $path) { foreach (['/usr/local/bin/wp', '/usr/bin/wp'] as $path) {
if (strpos($path, '*') !== false) { if (file_exists($path) && is_executable($path)) return $path;
$matches = glob($path); }
$matches = glob('/home/*/bin/wp');
if ($matches) return $matches[0]; if ($matches) return $matches[0];
} elseif (file_exists($path) && is_executable($path)) { $which = trim((string) shell_exec('which wp 2>/dev/null'));
return $path;
}
}
// Try which
$which = trim(shell_exec('which wp 2>/dev/null') ?: '');
return $which; return $which;
} }
} }