Linux sagir-us1.hostever.us 5.14.0-570.51.1.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Oct 8 09:41:34 EDT 2025 x86_64
LiteSpeed
Server IP : 104.247.108.91 & Your IP : 216.73.216.255
Domains : 74 Domain
User : georgeto
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
usr /
local /
softaculous /
lib /
classes /
ai /
core /
Delete
Unzip
Name
Size
Permission
Date
Action
class_ai_client.php
26.22
KB
-rw-r--r--
2026-05-18 07:05
class_ai_file_handler.php
2.35
KB
-rw-r--r--
2026-05-18 07:05
class_conversation.php
11.96
KB
-rw-r--r--
2026-05-18 07:05
class_file_manager.php
11.42
KB
-rw-r--r--
2026-05-18 07:05
class_project.php
3.4
KB
-rw-r--r--
2026-05-18 07:05
class_project_context.php
5.47
KB
-rw-r--r--
2026-05-18 07:05
class_session.php
2.18
KB
-rw-r--r--
2026-05-18 07:05
class_settings.php
6.13
KB
-rw-r--r--
2026-05-18 07:05
class_snapshot_manager.php
7.34
KB
-rw-r--r--
2026-05-18 07:05
class_tool_definitions.php
10.13
KB
-rw-r--r--
2026-05-18 07:05
class_tool_executor.php
32.77
KB
-rw-r--r--
2026-05-18 07:05
Save
Rename
<?php if(!defined('SOFTACULOUS')){ die('Hacking Attempt'); } class ToolExecutor { private $file_manager; private $project_path; private $user_home_dir; private $mode = 'build'; private $jail_script; private $use_jail = null; public function __construct(AIFileManager $file_manager, $project_path, $user_home_dir = null, $mode = 'build'){ global $globals; if(!empty($globals['path'])){ $this->jail_script = $globals['path'].'/bin/ai_jail.sh'; } $this->file_manager = $file_manager; $this->project_path = rtrim($project_path, '/'); $this->user_home_dir = $user_home_dir ? rtrim($user_home_dir, '/') : $this->project_path; $this->mode = $mode; if($this->use_jail === null){ $this->use_jail = false; $this->check_jail_availability(); } } private function check_jail_availability(){ if(!file_exists($this->jail_script) || !is_executable($this->jail_script)){ return; } $test_cmd = sprintf('%s %s -- echo JAIL_TEST_OK 2>/dev/null', escapeshellarg($this->jail_script), escapeshellarg($this->user_home_dir) ); $output = shell_exec($test_cmd); if(strpos($output, 'JAIL_TEST_OK') !== false){ $this->use_jail = true; } } public function execute($tool_name, array $params){ $read_only_tools = array('read_file', 'glob', 'grep', 'list_directory', 'web_fetch', 'todo_write'); if($this->mode === 'plan' && !in_array($tool_name, $read_only_tools)){ return array('output' => 'Permission denied: write operations are not allowed in Plan mode.', 'is_error' => true); } switch($tool_name){ case 'read_file': return $this->tool_read_file($params); case 'write_file': return $this->tool_write_file($params); case 'edit_file': return $this->tool_edit_file($params); case 'bash': return $this->tool_bash($params); case 'glob': return $this->tool_glob($params); case 'grep': return $this->tool_grep($params); case 'list_directory': return $this->tool_list_directory($params); case 'web_fetch': return $this->tool_web_fetch($params); case 'todo_write': return $this->tool_todo_write($params); case 'file_download': return $this->tool_file_download($params); case 'archive': return $this->tool_archive($params); case 'search_replace': return $this->tool_search_replace($params); case 'php_eval': return $this->tool_php_eval($params); default: return array('output' => "Unknown tool: {$tool_name}", 'is_error' => true); } } private function tool_read_file(array $params){ $path = isset($params['path']) ? $params['path'] : ''; $offset = isset($params['offset']) ? intval($params['offset']) : 1; $limit = isset($params['limit']) ? intval($params['limit']) : 2000; $result = $this->file_manager->read_file($path); if(!empty($result['error'])){ return array('output' => $result['error'], 'is_error' => true); } $content = $result['content']; $lines = explode("\n", $content); if($offset > 1 || $limit < count($lines)){ $lines = array_slice($lines, max(0, $offset - 1), $limit); } $output = ''; $line_no = max(1, $offset); foreach($lines as $line){ $output .= str_pad($line_no, 5, ' ', STR_PAD_LEFT) . ' | ' . $line . "\n"; $line_no++; } return array('output' => rtrim($output), 'is_error' => false); } private function tool_write_file(array $params){ $path = isset($params['path']) ? $params['path'] : ''; $content = isset($params['content']) ? $params['content'] : ''; if(empty($path)){ return array('output' => 'Path is required', 'is_error' => true); } $original = ''; $existing = $this->file_manager->read_file($path); if(!empty($existing['content'])) $original = $existing['content']; $result = $this->file_manager->write_file($path, $content, true); if(!empty($result['error'])){ return array('output' => $result['error'], 'is_error' => true); } $lines = substr_count($content, "\n") + 1; $return = array('output' => "Successfully wrote {$result['size']} bytes ({$lines} lines) to {$path}", 'is_error' => false); if($original !== '' && $original !== $content){ $return['diff'] = $this->compute_unified_diff($original, $content); } return $return; } private function tool_edit_file(array $params){ $path = isset($params['path']) ? $params['path'] : ''; $old_string = isset($params['old_string']) ? $params['old_string'] : ''; $new_string = isset($params['new_string']) ? $params['new_string'] : ''; $replace_all = !empty($params['replace_all']); if(empty($path) || empty($old_string)){ return array('output' => 'Path and old_string are required', 'is_error' => true); } $result = $this->file_manager->read_file($path); if(!empty($result['error'])){ return array('output' => $result['error'], 'is_error' => true); } $content = $result['content']; if(strpos($content, $old_string) === false){ $fuzzy_result = $this->fuzzy_find($content, $old_string); if($fuzzy_result !== false){ $old_string = $fuzzy_result; }else{ $snippet = mb_substr($old_string, 0, 100); return array('output' => "Could not find the specified text in {$path}. Searched for: \"{$snippet}...\"", 'is_error' => true); } } $count = 0; if($replace_all){ $new_content = str_replace($old_string, $new_string, $content, $count); }else{ $pos = strpos($content, $old_string); if($pos !== false){ $new_content = substr($content, 0, $pos) . $new_string . substr($content, $pos + strlen($old_string)); $count = 1; }else{ $new_content = $content; } } if($count === 0){ return array('output' => "No replacements made in {$path}", 'is_error' => true); } $result2 = $this->file_manager->write_file($path, $new_content, true); if(!empty($result2['error'])){ return array('output' => $result2['error'], 'is_error' => true); } $diff = $this->make_mini_diff($old_string, $new_string); $return = array('output' => "Edited {$path} ({$count} replacement".($count > 1 ? 's' : '').")\n{$diff}", 'is_error' => false); $return['diff'] = $this->compute_unified_diff($content, $new_content); return $return; } private function tool_bash(array $params){ $command = isset($params['command']) ? $params['command'] : ''; $timeout = isset($params['timeout']) ? intval($params['timeout']) : 30; $timeout = max(5, min($timeout, 120)); if(empty($command)){ return array('output' => 'Command is required', 'is_error' => true); } if($this->is_dangerous_command($command)){ return array('output' => 'This command is blocked for safety reasons.', 'is_error' => true); } $env = array(); if(!empty($this->user_home_dir)){ $env['HOME'] = $this->user_home_dir; } $env['PATH'] = '/usr/local/bin:/usr/bin:/bin'; $env['TERM'] = 'dumb'; if($this->use_jail){ return $this->execute_via_jail($command, $timeout, $env); } $descriptors = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w') ); $process = proc_open('cd ' . escapeshellarg($this->project_path) . ' && ' . $command, $descriptors, $pipes, null, $env); if(!is_resource($process)){ return array('output' => 'Failed to execute command', 'is_error' => true); } fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $stdout = ''; $stderr = ''; $start = microtime(true); while(true){ $status = proc_get_status($process); if(!$status['running']){ while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; break; } $elapsed = microtime(true) - $start; if($elapsed >= $timeout){ proc_terminate($process, 9); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); $output = $stdout . ($stdout && $stderr ? "\n" : '') . $stderr; return array('output' => "Command timed out after {$timeout}s\n{$output}", 'is_error' => true); } while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; usleep(20000); } fclose($pipes[1]); fclose($pipes[2]); $return_code = proc_close($process); $output = ''; if(!empty($stdout)) $output .= $stdout; if(!empty($stderr)) $output .= ($output ? "\n" : '') . $stderr; if(strlen($output) > 30000){ $output = mb_substr($output, 0, 30000) . "\n... [output truncated]"; } if($return_code !== 0){ $output .= "\n[Exit code: {$return_code}]"; return array('output' => $output, 'is_error' => true); } return array('output' => $output ? $output : "[Command completed successfully with no output]", 'is_error' => false); } private function execute_via_jail($command, $timeout, $env){ $jail_cmd = sprintf('%s %s -- bash -c %s', escapeshellarg($this->jail_script), escapeshellarg($this->user_home_dir), escapeshellarg('cd ' . escapeshellarg($this->project_path) . ' && ' . $command) ); $descriptors = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w') ); $process = proc_open($jail_cmd, $descriptors, $pipes, null, $env); if(!is_resource($process)){ return array('output' => 'Failed to execute jailed command', 'is_error' => true); } fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $stdout = ''; $stderr = ''; $start = microtime(true); while(true){ $status = proc_get_status($process); if(!$status['running']){ while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; break; } $elapsed = microtime(true) - $start; if($elapsed >= $timeout){ proc_terminate($process, 9); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); $output = $stdout . ($stdout && $stderr ? "\n" : '') . $stderr; return array('output' => "Command timed out after {$timeout}s\n{$output}", 'is_error' => true); } while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; usleep(20000); } fclose($pipes[1]); fclose($pipes[2]); $return_code = proc_close($process); $output = ''; if(!empty($stdout)) $output .= $stdout; if(!empty($stderr)) $output .= ($output ? "\n" : '') . $stderr; if(strlen($output) > 30000){ $output = mb_substr($output, 0, 30000) . "\n... [output truncated]"; } if($return_code !== 0){ $output .= "\n[Exit code: {$return_code}]"; return array('output' => $output, 'is_error' => true); } return array('output' => $output ? $output : "[Command completed successfully with no output]", 'is_error' => false); } private function tool_glob(array $params){ $pattern = isset($params['pattern']) ? $params['pattern'] : '**/*'; $base_path = isset($params['path']) ? $params['path'] : ''; $full_base = $this->project_path; if(!empty($base_path)){ $resolved = $this->file_manager->resolve_path($base_path); if(strpos($resolved, $this->user_home_dir) === 0){ $full_base = $resolved; } } $pattern = escapeshellarg($pattern); $cmd = 'cd ' . escapeshellarg($full_base) . ' && find . -path ' . $pattern . ' -type f 2>/dev/null | head -200'; if($this->use_jail){ $cmd = escapeshellarg($this->jail_script) . ' ' . escapeshellarg($this->user_home_dir) . ' -- bash -c ' . escapeshellarg($cmd); } $output = trim(shell_exec($cmd)); if(empty($output)){ $_pattern = isset($params['pattern']) ? $params['pattern'] : $pattern; return array('output' => 'No files found matching pattern: '.$_pattern, 'is_error' => false); } $files = explode("\n", $output); $files = array_map(function($f){ return ltrim($f, './'); }, $files); return array('output' => implode("\n", $files), 'is_error' => false); } private function tool_grep(array $params){ $pattern = isset($params['pattern']) ? $params['pattern'] : ''; $base_path = isset($params['path']) ? $params['path'] : '.'; $include = isset($params['include']) ? $params['include'] : ''; $max_results = isset($params['max_results']) ? intval($params['max_results']) : 50; if(empty($pattern)){ return array('output' => 'Pattern is required', 'is_error' => true); } $full_base = $this->file_manager->resolve_path($base_path); if(strpos($full_base, $this->user_home_dir) !== 0){ return array('output' => 'Path outside allowed directory', 'is_error' => true); } $cmd = 'cd ' . escapeshellarg($this->project_path) . ' && grep -rn --binary-files=without-match'; if(!empty($include)){ $cmd .= ' --include=' . escapeshellarg($include); } $cmd .= ' ' . escapeshellarg($pattern) . ' ' . escapeshellarg($full_base) . ' 2>/dev/null | head -' . $max_results; if($this->use_jail){ $cmd = escapeshellarg($this->jail_script) . ' ' . escapeshellarg($this->user_home_dir) . ' -- bash -c ' . escapeshellarg($cmd); } $output = trim(shell_exec($cmd)); if(empty($output)){ return array('output' => 'No matches found for pattern: '.$pattern, 'is_error' => false); } $output = preg_replace('/^' . preg_quote($this->project_path, '/') . '\//', '', $output); return array('output' => $output, 'is_error' => false); } private function tool_list_directory(array $params){ $path = isset($params['path']) ? $params['path'] : '/'; $depth = isset($params['depth']) ? intval($params['depth']) : 2; $result = $this->file_manager->list_directory($path, $depth); if(!empty($result['error'])){ return array('output' => $result['error'], 'is_error' => true); } return array('output' => json_encode($result, JSON_PRETTY_PRINT), 'is_error' => false); } private function tool_web_fetch(array $params){ $url = isset($params['url']) ? $params['url'] : ''; if(empty($url)){ return array('output' => 'URL is required', 'is_error' => true); } if(!filter_var($url, FILTER_VALIDATE_URL)){ return array('output' => 'Invalid URL', 'is_error' => true); } $ch = curl_init($url); curl_setopt_array($ch, array( CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 15, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; Softaculous AI/1.0)' )); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if($response === false){ return array('output' => 'Failed to fetch URL', 'is_error' => true); } $response = strip_tags($response); $response = preg_replace('/\s+/', ' ', $response); $response = trim($response); if(strlen($response) > 30000){ $response = substr($response, 0, 30000) . '... [truncated]'; } return array('output' => $response, 'is_error' => false); } private function tool_todo_write(array $params){ $todos = isset($params['todos']) ? $params['todos'] : array(); return array('output' => 'Todo list updated with '.count($todos).' items.', 'is_error' => false, 'todos' => $todos); } private function fuzzy_find($content, $search){ $search_trimmed = trim($search); $search_lines = explode("\n", $search_trimmed); if(count($search_lines) < 2) return null; $first_line = trim($search_lines[0]); $last_line = trim($search_lines[count($search_lines) - 1]); $content_lines = explode("\n", $content); $first_positions = array(); foreach($content_lines as $i => $line){ if(trim($line) === $first_line) $first_positions[] = $i; } foreach($first_positions as $start){ $end = $start + count($search_lines) - 1; if($end >= count($content_lines)) continue; if(trim($content_lines[$end]) === $last_line){ $match = implode("\n", array_slice($content_lines, $start, count($search_lines))); return $match; } } foreach($content_lines as $i => $line){ if(strpos(trim($line), $first_line) !== false || strpos($first_line, trim($line)) !== false){ $end = min($i + count($search_lines), count($content_lines)); $candidate = implode("\n", array_slice($content_lines, $i, $end - $i)); similar_text($search_trimmed, $candidate, $percent); if($percent > 70) return $candidate; } } return null; } private function make_mini_diff($old, $new){ $diff = ""; $old_lines = explode("\n", $old); $new_lines = explode("\n", $new); foreach($old_lines as $l) $diff .= "- " . $l . "\n"; foreach($new_lines as $l) $diff .= "+ " . $l . "\n"; return trim($diff); } private function compute_unified_diff($old, $new){ $old_lines = explode("\n", $old); $new_lines = explode("\n", $new); $max = max(count($old_lines), count($new_lines)); $diff = array(); $ctx = 3; $buffer = array(); $in_change = false; for($i = 0; $i < $max || !empty($buffer); $i++){ $o = isset($old_lines[$i]) ? $old_lines[$i] : null; $n = isset($new_lines[$i]) ? $new_lines[$i] : null; if($o !== null && $n !== null && $o === $n){ if($in_change){ $buffer[] = ' ' . $o; if(count($buffer) > $ctx * 2 + 10){ $diff = array_merge($diff, array_slice($buffer, 0, count($buffer) - $ctx)); $buffer = array_slice($buffer, -$ctx); $in_change = false; } }else{ $buffer[] = ' ' . $o; if(count($buffer) > $ctx){ $buffer = array_slice($buffer, -$ctx); } } }else{ if(!$in_change && !empty($buffer)){ $keep = min(count($buffer), $ctx); $diff = array_merge($diff, array_slice($buffer, -$keep)); $buffer = array(); } $in_change = true; if($o !== null) $diff[] = '- ' . $o; if($n !== null) $diff[] = '+ ' . $n; } } if(!empty($buffer)){ $keep = min(count($buffer), $ctx); $diff = array_merge($diff, array_slice($buffer, -$keep)); } return implode("\n", $diff); } private function tool_file_download(array $params){ $url = isset($params['url']) ? $params['url'] : ''; $path = isset($params['path']) ? $params['path'] : ''; $overwrite = !empty($params['overwrite']); if(empty($url) || empty($path)){ return array('output' => 'URL and path are required', 'is_error' => true); } if(!filter_var($url, FILTER_VALIDATE_URL)){ return array('output' => 'Invalid URL: '.$url, 'is_error' => true); } $scheme = parse_url($url, PHP_URL_SCHEME); if(!in_array($scheme, array('http', 'https'))){ return array('output' => 'Only http/https URLs are allowed', 'is_error' => true); } $resolved = $this->file_manager->resolve_path($path); if(strpos($resolved, $this->project_path) !== 0){ return array('output' => 'Destination path is outside the project directory', 'is_error' => true); } if(file_exists($resolved) && !$overwrite){ return array('output' => "File already exists: {$path}. Set overwrite=true to replace.", 'is_error' => true); } $dir = dirname($resolved); if(!is_dir($dir)){ @mkdir($dir, 0755, true); } $fp = @fopen($resolved, 'w'); if(!$fp){ return array('output' => "Cannot write to: {$path}", 'is_error' => true); } $ch = curl_init($url); curl_setopt_array($ch, array( CURLOPT_FILE => $fp, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 120, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; Softaculous AI/1.0)', CURLOPT_MAXFILESIZE => 52428800 )); $result = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $size = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD); $error = curl_error($ch); curl_close($ch); fclose($fp); if(!$result){ @unlink($resolved); return array('output' => 'Download failed: '.($error ?: 'Unknown error'), 'is_error' => true); } if($http_code >= 400){ @unlink($resolved); return array('output' => "Download failed: HTTP {$http_code}", 'is_error' => true); } $size_str = $this->format_bytes($size); return array('output' => "Downloaded {$size_str} to {$path} (HTTP {$http_code})", 'is_error' => false); } private function tool_archive(array $params){ $action = isset($params['action']) ? $params['action'] : ''; $source = isset($params['source']) ? $params['source'] : ''; $destination = isset($params['destination']) ? $params['destination'] : ''; if(empty($action) || empty($source)){ return array('output' => 'action and source are required', 'is_error' => true); } if(!in_array($action, array('zip', 'unzip'))){ return array('output' => 'Action must be "zip" or "unzip"', 'is_error' => true); } if($action === 'zip'){ return $this->archive_zip($source, $destination); }else{ return $this->archive_unzip($source, $destination); } } private function archive_zip($source, $destination){ $src_resolved = $this->file_manager->resolve_path($source); if(strpos($src_resolved, $this->user_home_dir) !== 0){ return array('output' => 'Source path is outside the allowed directory', 'is_error' => true); } if(!file_exists($src_resolved)){ return array('output' => "Source not found: {$source}", 'is_error' => true); } if(empty($destination)){ $destination = $source . '.zip'; } if(substr($destination, -4) !== '.zip') $destination .= '.zip'; $dst_resolved = $this->file_manager->resolve_path($destination); if(strpos($dst_resolved, $this->user_home_dir) !== 0){ return array('output' => 'Destination path is outside the allowed directory', 'is_error' => true); } $zip = new ZipArchive(); if($zip->open($dst_resolved, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true){ return array('output' => 'Failed to create zip archive', 'is_error' => true); } $count = 0; if(is_dir($src_resolved)){ $base = basename($src_resolved); $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($src_resolved, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach($files as $file){ $local = $base . '/' . substr($file->getPathname(), strlen($src_resolved) + 1); if($file->isDir()){ $zip->addEmptyDir($local); }else{ if($file->getSize() > 52428800){ continue; } $zip->addFile($file->getPathname(), $local); $count++; } } }else{ $zip->addFile($src_resolved, basename($src_resolved)); $count = 1; } $zip->close(); $zip_size = $this->format_bytes(filesize($dst_resolved)); return array('output' => "Created archive {$destination} ({$count} files, {$zip_size})", 'is_error' => false); } private function archive_unzip($source, $destination){ $src_resolved = $this->file_manager->resolve_path($source); if(strpos($src_resolved, $this->user_home_dir) !== 0){ return array('output' => 'Source path is outside the allowed directory', 'is_error' => true); } if(!file_exists($src_resolved)){ return array('output' => "Source not found: {$source}", 'is_error' => true); } if(empty($destination)){ $destination = dirname($source) ?: '.'; } $dst_resolved = $this->file_manager->resolve_path($destination); if(strpos($dst_resolved, $this->user_home_dir) !== 0){ return array('output' => 'Destination path is outside the allowed directory', 'is_error' => true); } $zip = new ZipArchive(); if($zip->open($src_resolved) !== true){ return array('output' => 'Failed to open zip archive', 'is_error' => true); } $count = $zip->numFiles; $zip->extractTo($dst_resolved); $zip->close(); return array('output' => "Extracted {$count} files to {$destination}", 'is_error' => false); } private function tool_search_replace(array $params){ $search = isset($params['search']) ? $params['search'] : ''; $replace = isset($params['replace']) ? $params['replace'] : ''; $include = isset($params['include']) ? $params['include'] : ''; $base_path = isset($params['path']) ? $params['path'] : '.'; $max_files = isset($params['max_files']) ? intval($params['max_files']) : 50; $is_regex = !empty($params['regex']); $max_files = max(1, min($max_files, 100)); if(empty($search)){ return array('output' => 'search pattern is required', 'is_error' => true); } $full_base = $this->file_manager->resolve_path($base_path); if(strpos($full_base, $this->user_home_dir) !== 0){ return array('output' => 'Path outside allowed directory', 'is_error' => true); } if($is_regex){ set_error_handler(function(){}); $test = preg_match($search, ''); restore_error_handler(); if($test === false){ return array('output' => 'Invalid regex pattern: '.$search, 'is_error' => true); } } $cmd = 'cd '.escapeshellarg($this->project_path).' && grep -rl --binary-files=without-match'; if(!empty($include)){ $cmd .= ' --include='.escapeshellarg($include); } $cmd .= ' '.escapeshellarg($search).' '.escapeshellarg($full_base).' 2>/dev/null | head -'.($max_files + 1); if($this->use_jail){ $cmd = escapeshellarg($this->jail_script) . ' ' . escapeshellarg($this->user_home_dir) . ' -- bash -c ' . escapeshellarg($cmd); } $output = trim(shell_exec($cmd)); if(empty($output)){ return array('output' => 'No files found matching: '.$search, 'is_error' => false); } $files = explode("\n", $output); $truncated = false; if(count($files) > $max_files){ $files = array_slice($files, 0, $max_files); $truncated = true; } $results = array(); $total_replacements = 0; foreach($files as $file){ $filepath = trim($file); if(!file_exists($filepath) || !is_readable($filepath) || !is_writable($filepath)) continue; $rel_path = str_replace($this->project_path . '/', '', $filepath); $read_result = $this->file_manager->read_file($rel_path); if(!empty($read_result['error'])) continue; $content = $read_result['content']; $count = 0; if($is_regex){ $new_content = preg_replace($search, $replace, $content, -1, $count); }else{ $new_content = str_replace($search, $replace, $content, $count); } if($count > 0 && $new_content !== null){ $write_result = $this->file_manager->write_file($rel_path, $new_content, true); if(empty($write_result['error'])){ $results[] = "{$rel_path}: {$count} replacement(s)"; $total_replacements += $count; } } } $output = "Replaced \"{$search}\" in ".count($results)." file(s) ({$total_replacements} total replacements)\n"; $output .= implode("\n", $results); if($truncated){ $output .= "\n[Only processed first {$max_files} files. Use max_files to increase.]"; } return array('output' => $output, 'is_error' => false); } private function tool_php_eval(array $params){ $code = isset($params['code']) ? $params['code'] : ''; if(empty($code)){ return array('output' => 'code is required', 'is_error' => true); } $forbidden = array('exec(', 'shell_exec(', 'system(', 'passthru(', 'popen(', 'proc_open(', 'pcntl_', 'putenv(', 'apache_', 'ini_set(', 'ini_restore(', 'mail(', 'header(', 'setcookie(', 'move_uploaded_file(', 'chmod(', 'chown(', 'chgrp(', 'unlink(', 'rmdir(', 'file_put_contents(', 'fwrite(', 'fputs(', 'rename(', 'eval(', 'assert(', 'preg_replace(', 'create_function(', 'call_user_func(', 'call_user_func_array('); $code_lower = strtolower($code); foreach($forbidden as $f){ if(strpos($code_lower, strtolower($f)) !== false){ return array('output' => "Function/statement not allowed: {$f}", 'is_error' => true); } } $tmp = tempnam(sys_get_temp_dir(), 'ai_php_'); $wrapped = "<?php\nchdir(".var_export($this->project_path, true).");\n"; if(file_exists($this->project_path.'/wp-load.php')){ $wrapped .= "define('ABSPATH', ".var_export($this->project_path.'/', true).");\n"; $wrapped .= "@include_once(".var_export($this->project_path.'/wp-load.php', true).");\n"; } $wrapped .= "\n".$code."\n"; file_put_contents($tmp, $wrapped); $env = array(); if(!empty($this->user_home_dir)){ $env['HOME'] = $this->user_home_dir; } $env['PATH'] = '/usr/local/bin:/usr/bin:/bin'; $open_basedir = $this->user_home_dir . ':' . sys_get_temp_dir(); $disable_functions = 'exec,shell_exec,system,passthru,popen,proc_open,pcntl_fork,pcntl_exec,putenv,ini_set,ini_restore,dl'; if($this->use_jail){ $jail_cmd = sprintf( '%s %s -- php -d open_basedir=%s -d disable_functions=%s %s', escapeshellarg($this->jail_script), escapeshellarg($this->user_home_dir), escapeshellarg($open_basedir), escapeshellarg($disable_functions), escapeshellarg($tmp) ); $descriptors = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w') ); $process = proc_open($jail_cmd, $descriptors, $pipes, null, $env); if(!is_resource($process)){ @unlink($tmp); return array('output' => 'Failed to execute jailed PHP', 'is_error' => true); } fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $stdout = ''; $stderr = ''; $start = microtime(true); $timeout = 10; while(true){ $status = proc_get_status($process); if(!$status['running']){ while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; break; } if(microtime(true) - $start >= $timeout){ proc_terminate($process, 9); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); @unlink($tmp); return array('output' => "PHP execution timed out after {$timeout}s", 'is_error' => true); } while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; usleep(20000); } fclose($pipes[1]); fclose($pipes[2]); $return_code = proc_close($process); @unlink($tmp); $output = trim($stdout); if(!empty($stderr)){ $output .= ($output ? "\n" : '') . trim($stderr); } if(strlen($output) > 20000){ $output = substr($output, 0, 20000) . "\n... [output truncated]"; } if($return_code !== 0){ return array('output' => $output."\n[Exit code: {$return_code}]", 'is_error' => true); } return array('output' => $output ?: "[PHP executed successfully with no output]", 'is_error' => false); } $php_cmd = sprintf('php -d open_basedir=%s -d disable_functions=%s %s', escapeshellarg($open_basedir), escapeshellarg($disable_functions), escapeshellarg($tmp) ); $descriptors = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w') ); $process = proc_open($php_cmd, $descriptors, $pipes, $this->project_path, $env); if(!is_resource($process)){ @unlink($tmp); return array('output' => 'Failed to execute PHP', 'is_error' => true); } fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $stdout = ''; $stderr = ''; $start = microtime(true); $timeout = 10; while(true){ $status = proc_get_status($process); if(!$status['running']){ while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; break; } if(microtime(true) - $start >= $timeout){ proc_terminate($process, 9); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); @unlink($tmp); return array('output' => "PHP execution timed out after {$timeout}s", 'is_error' => true); } while(($buf = fread($pipes[1], 8192)) !== '') $stdout .= $buf; while(($buf = fread($pipes[2], 8192)) !== '') $stderr .= $buf; usleep(20000); } fclose($pipes[1]); fclose($pipes[2]); $return_code = proc_close($process); @unlink($tmp); $output = trim($stdout); if(!empty($stderr)){ $output .= ($output ? "\n" : '') . trim($stderr); } if(strlen($output) > 20000){ $output = substr($output, 0, 20000) . "\n... [output truncated]"; } if($return_code !== 0){ return array('output' => $output."\n[Exit code: {$return_code}]", 'is_error' => true); } return array('output' => $output ?: "[PHP executed successfully with no output]", 'is_error' => false); } private function format_bytes($bytes){ if($bytes >= 1048576) return round($bytes / 1048576, 1).'MB'; if($bytes >= 1024) return round($bytes / 1024, 1).'KB'; return $bytes.'B'; } private function is_dangerous_command($command){ $protected_dirs = array('.softaculous', '.ssh', '.gnupg'); foreach($protected_dirs as $dir){ if(preg_match('#/(?:' . preg_quote($dir, '#') . ')(?:/|$)#i', $command)){ if(preg_match('/\b(rm|rmdir|mv|chmod|chown|chgrp|ln|unlink)\b/', $command)){ return true; } } } $dangerous = array( '/\brm\s+-[a-zA-Z]*r[a-zA-Z]*f\s+\/\s*$/', '/\brm\s+-[a-zA-Z]*f[a-zA-Z]*r\s+\/\s*$/', '/\brm\s+-rf\s+~\s*$/', '/\brm\s+-fr\s+~\s*$/', '/:\(\)\{.*;\}\s*;&/', '/\bdd\s+if=/', '/\bmkfs\b/', '/\bformat\s+[a-z]:/i', '/\bshutdown\b/', '/\breboot\b/', '/\bhalt\b/', '/\binit\s+[06]\b/', '/>\s*\/dev\/sda/', '/\bchmod\s+-R\s+777\s+\//', '/\bchown\s+-R\s+.*\s+\//', '/\bcurl\b.*\|\s*(ba)?sh/', '/\bwget\b.*\|\s*(ba)?sh/', '/\bsudo\b/', '/\bsu\s+/', '/\bssh\b/', '/\bscp\b/', '/\brsync\b/', '/\bnc\s/', '/\bncat\b/', '/\bsocat\b/', '/\bcrontab\b/', '/\bat\s+[a-z]+\s/', '/\bsystemctl\b/', '/\bservice\b/', '/\biptables\b/', '/\bip\s+route\b/', '/\bmysql\s/', '/\bpsql\s/', '/\bmongosh\b/', '/\bpython\d?\s+-c\s/', '/\bperl\s+-e\s/', '/\bruby\s+-e\s/', '/\bkill\s+-9\s+1\b/', '/\bkillall\b/', '/\bpkill\b/', '/\bshutdown\b/', '/\bpoweroff\b/', ); foreach($dangerous as $pattern){ if(preg_match($pattern, $command)) return true; } return false; } }