Files
wevads-platform/scripts/api_sentinel-engine.php
2026-02-26 04:53:11 +01:00

219 lines
9.5 KiB
PHP
Executable File

<?php
/**
* WEVAL MIND SENTINEL — Autonomous Scanner & Repairer
* Scans, detects, fixes, and learns from infrastructure issues
*/
header('Content-Type: application/json');
error_reporting(0);
$start = microtime(true);
$action = $_GET['action'] ?? $_POST['action'] ?? 'status';
try {
$pdo = new PDO('pgsql:host=localhost;dbname=adx_system', 'admin', 'admin123');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Exception $e) {
echo json_encode(['error' => 'DB: ' . $e->getMessage()]); exit;
}
$PATHS = [
'adx' => '/opt/wevads/public',
'arsenal' => '/opt/wevads-arsenal/public',
'arsenal_api' => '/opt/wevads-arsenal/public/api'
];
$PORTS = ['adx' => 5821, 'arsenal' => 5890, 'fmg' => 5822, 'bcg' => 5823, 'dkim' => 5824];
function runFullScan($pdo, $paths, $autoFix = true) {
global $start, $PORTS;
$scanId = startScan($pdo, 'full');
$issues = [];
$fixed = 0;
$totalFiles = 0;
foreach ($paths as $name => $path) {
if (!is_dir($path)) continue;
// 1. PHP Syntax Check
$phpFiles = glob("$path/*.php");
foreach ($phpFiles as $f) {
$totalFiles++;
$out = shell_exec("php -l " . escapeshellarg($f) . " 2>&1");
if (strpos($out, 'Parse error') !== false || strpos($out, 'Fatal') !== false) {
$issues[] = ['file' => basename($f), 'path' => $f, 'type' => 'php-syntax-error', 'detail' => substr($out, 0, 200)];
logFix($pdo, $scanId, $f, 'php-syntax-error', 'Logged for review');
}
}
// 2. HTML Script Tag Balance
$htmlFiles = glob("$path/*.html");
foreach ($htmlFiles as $f) {
$totalFiles++;
$content = @file_get_contents($f);
if (!$content) continue;
$opens = substr_count($content, '<script');
$closes = substr_count($content, '</script>');
if ($opens > $closes) {
$diff = $opens - $closes;
$issues[] = ['file' => basename($f), 'path' => $f, 'type' => 'unclosed-script', 'detail' => "open=$opens close=$closes"];
if ($autoFix && is_writable($f)) {
file_put_contents($f, $content . str_repeat("</script>\n", $diff));
$fixed++;
logFix($pdo, $scanId, $f, 'unclosed-script', "Appended $diff </script>");
}
}
}
// 3. Injection Detection
$allFiles = array_merge($phpFiles, $htmlFiles);
foreach ($allFiles as $f) {
$content = @file_get_contents($f);
if (!$content) continue;
// WEVADS_NUKE
if (strpos($content, "WEVADS_" . "NUKE") !== false && strpos($f, "sentinel") === false) {
$issues[] = ['file' => basename($f), 'path' => $f, 'type' => 'WEVADS_NUKE', 'detail' => 'Malicious injection'];
if ($autoFix && is_writable($f)) {
$content = preg_replace('/<style id="WEVADS_NUKE">[^<]*<\/style>/s', '', $content);
$content = preg_replace('/<script id="WEVADS_KILL_JS">.*?<\/script>/s', '', $content);
file_put_contents($f, $content);
$fixed++;
logFix($pdo, $scanId, $f, 'WEVADS_NUKE', 'Removed NUKE blocks');
}
}
// weval-theme-system-v2
if (strpos($content, 'weval-theme-system-v2') !== false) {
$issues[] = ['file' => basename($f), 'path' => $f, 'type' => 'weval-theme-v2', 'detail' => 'Theme injection'];
if ($autoFix && is_writable($f)) {
$content = preg_replace('/<!-- weval-theme-system-v2 -->.*?<!-- \/weval-theme-system-v2 -->/s', '', $content);
file_put_contents($f, $content);
$fixed++;
logFix($pdo, $scanId, $f, 'weval-theme-v2', 'Removed theme block');
}
}
// Broken </script> in URLs
if (preg_match('/action=[^>]*<\/script>[a-z]*=/', $content)) {
$issues[] = ['file' => basename($f), 'path' => $f, 'type' => 'broken-script-url', 'detail' => '</script> in URL'];
if ($autoFix && is_writable($f)) {
$content = preg_replace('/(action=[^>]*)<\/script>([a-z]*=)/', '$1&$2', $content);
file_put_contents($f, $content);
$fixed++;
logFix($pdo, $scanId, $f, 'broken-script-url', 'Fixed URL');
}
}
// Content after </body></html>
$pos = strpos($content, '</body></html>');
if ($pos !== false) {
$after = trim(substr($content, $pos + 14));
if (strlen($after) > 20) {
$issues[] = ['file' => basename($f), 'path' => $f, 'type' => 'content-after-html', 'detail' => 'Junk after </html>'];
if ($autoFix && is_writable($f)) {
file_put_contents($f, substr($content, 0, $pos + 14) . "\n");
$fixed++;
logFix($pdo, $scanId, $f, 'content-after-html', 'Truncated after </html>');
}
}
}
}
}
// 4. Port Health
foreach ($PORTS as $name => $port) {
$ch = curl_init("http://localhost:$port/");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3, CURLOPT_NOBODY => true]);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code == 0) {
$issues[] = ['file' => "port:$port", 'path' => '', 'type' => 'port-down', 'detail' => "$name unreachable"];
}
}
// 5. Runtime Errors (sample top 50 PHP pages)
$phpPages = array_slice(glob($paths['adx'] . '/*.php'), 0, 50);
foreach ($phpPages as $f) {
$bn = basename($f);
if (in_array($bn, ['claude-exec.php','claude-exec2.php','index.php'])) continue;
$ch = curl_init("http://localhost:5821/$bn");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3]);
$out = curl_exec($ch);
curl_close($ch);
if ($out && preg_match('/<b>(Warning|Fatal error|Parse error)<\/b>/', $out)) {
$issues[] = ['file' => $bn, 'path' => $f, 'type' => 'runtime-error', 'detail' => 'PHP warning/error on load'];
}
}
$duration = round((microtime(true) - $start) * 1000);
$score = $totalFiles > 0 ? round((1 - count($issues) / max($totalFiles, 1)) * 100, 1) : 100;
finishScan($pdo, $scanId, $totalFiles, count($issues), $fixed, $score, $duration);
// Update pattern stats
foreach ($issues as $i) {
$pdo->prepare("UPDATE admin.sentinel_patterns SET times_detected = times_detected + 1, last_seen = NOW() WHERE pattern_name = ?")->execute([$i['type']]);
}
return [
'scan_id' => $scanId,
'total_files' => $totalFiles,
'issues_found' => count($issues),
'issues_fixed' => $fixed,
'score' => $score,
'duration_ms' => $duration,
'issues' => $issues
];
}
function startScan($pdo, $type) {
$stmt = $pdo->prepare("INSERT INTO admin.sentinel_scans (scan_type) VALUES (?) RETURNING id");
$stmt->execute([$type]);
return $stmt->fetchColumn();
}
function finishScan($pdo, $id, $total, $issues, $fixed, $score, $duration) {
$pdo->prepare("UPDATE admin.sentinel_scans SET total_files=?, issues_found=?, issues_fixed=?, score=?, duration_ms=? WHERE id=?")->execute([$total, $issues, $fixed, $score, $duration, $id]);
}
function logFix($pdo, $scanId, $file, $type, $fix) {
$pdo->prepare("INSERT INTO admin.sentinel_fixes (file_path, issue_type, fix_applied, scan_id) VALUES (?,?,?,?)")->execute([$file, $type, $fix, $scanId]);
}
// API ROUTES
switch ($action) {
case 'scan':
$autoFix = ($_GET['fix'] ?? '1') === '1';
echo json_encode(runFullScan($pdo, $PATHS, $autoFix), JSON_PRETTY_PRINT);
break;
case 'status':
$last = $pdo->query("SELECT * FROM admin.sentinel_scans ORDER BY scan_date DESC LIMIT 1")->fetch(PDO::FETCH_ASSOC);
$totalScans = $pdo->query("SELECT count(*) FROM admin.sentinel_scans")->fetchColumn();
$totalFixes = $pdo->query("SELECT count(*) FROM admin.sentinel_fixes")->fetchColumn();
$patterns = $pdo->query("SELECT pattern_name, times_detected, times_fixed, severity FROM admin.sentinel_patterns ORDER BY times_detected DESC")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['status' => 'operational', 'last_scan' => $last, 'total_scans' => (int)$totalScans, 'total_fixes' => (int)$totalFixes, 'patterns' => $patterns], JSON_PRETTY_PRINT);
break;
case 'history':
$limit = min((int)($_GET['limit'] ?? 20), 100);
$scans = $pdo->query("SELECT * FROM admin.sentinel_scans ORDER BY scan_date DESC LIMIT $limit")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['scans' => $scans]);
break;
case 'fixes':
$limit = min((int)($_GET['limit'] ?? 50), 200);
$fixes = $pdo->query("SELECT * FROM admin.sentinel_fixes ORDER BY fix_date DESC LIMIT $limit")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['fixes' => $fixes]);
break;
case 'patterns':
$patterns = $pdo->query("SELECT * FROM admin.sentinel_patterns ORDER BY times_detected DESC")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['patterns' => $patterns]);
break;
default:
echo json_encode(['error' => 'Unknown action', 'available' => ['scan','status','history','fixes','patterns']]);
}