SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,267
Folders
403
Scanned Size
3.84 GB
PHP Files
863
Editable Text Files
12,530
File viewer
guarded to /htdocs
/investigator.php
<?php declare(strict_types=1); /** * SIMON Security Investigator & Guided Remediation Console * Version 2.1.0 * * Upload to the active website document root, change SIMON_INSTALL_TOKEN, * open in a browser, investigate, apply only the fixes you approve, then delete. * * Designed for Apache 2.4 / IONOS / PHP 8.1+. * This tool is intentionally approval-based. It never edits PHP source code, * credentials, database schema, OAuth secrets, or payment logic. */ const SIMON_VERSION = '2.1.0'; const SIMON_INSTALL_TOKEN = '123'; const SIMON_TARGET_ORIGIN = 'https://www.gaylordsinclair.com'; const SIMON_MAX_BODY_BYTES = 900000; const SIMON_HTTP_TIMEOUT = 15; const SIMON_BACKUP_RETENTION = 25; header('Content-Type: text/html; charset=UTF-8'); header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: DENY'); header('Referrer-Policy: no-referrer'); header("Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'; base-uri 'none'"); if (session_status() !== PHP_SESSION_ACTIVE) { session_name('SIMON_SECURITY_INVESTIGATOR'); session_start(); } function h(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function nowIso(): string { return gmdate('c'); } function randomId(string $prefix): string { return $prefix . '-' . gmdate('YmdHis') . '-' . bin2hex(random_bytes(4)); } function startsWithPath(string $path, string $root): bool { $path = rtrim(str_replace('\\', '/', $path), '/') . '/'; $root = rtrim(str_replace('\\', '/', $root), '/') . '/'; return str_starts_with($path, $root); } function documentRoot(): string { $candidate = (string)($_SERVER['DOCUMENT_ROOT'] ?? __DIR__); return realpath($candidate) ?: __DIR__; } function storageRoot(): string { $root = documentRoot() . '/_data/simon/investigations'; if (!is_dir($root)) { @mkdir($root, 0755, true); } $guard = $root . '/.htaccess'; if (is_dir($root) && !is_file($guard)) { @file_put_contents($guard, "Options -Indexes\n<IfModule mod_authz_core.c>\nRequire all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\nOrder allow,deny\nDeny from all\n</IfModule>\n", LOCK_EX); } return $root; } function csrfToken(): string { if (empty($_SESSION['csrf'])) { $_SESSION['csrf'] = bin2hex(random_bytes(24)); } return (string)$_SESSION['csrf']; } function validCsrf(): bool { return isset($_POST['csrf']) && hash_equals(csrfToken(), (string)$_POST['csrf']); } function authorized(): bool { if (!empty($_SESSION['simon_authorized'])) { return true; } $token = (string)($_POST['install_token'] ?? ''); if ($token !== '' && hash_equals(SIMON_INSTALL_TOKEN, $token)) { $_SESSION['simon_authorized'] = true; return true; } return false; } function httpFetch(string $url, bool $headOnly = false): array { if (!function_exists('curl_init')) { return ['ok' => false, 'error' => 'cURL is not available.']; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 8, CURLOPT_TIMEOUT => SIMON_HTTP_TIMEOUT, CURLOPT_CONNECTTIMEOUT => 6, CURLOPT_HEADER => true, CURLOPT_NOBODY => $headOnly, CURLOPT_ENCODING => '', CURLOPT_USERAGENT => 'SIMON-Security-Investigator/' . SIMON_VERSION, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, ]); $started = microtime(true); $raw = curl_exec($ch); $elapsed = (microtime(true) - $started) * 1000; if ($raw === false) { $error = curl_error($ch); $errno = curl_errno($ch); curl_close($ch); return ['ok' => false, 'error' => "cURL {$errno}: {$error}", 'ms' => $elapsed]; } $info = curl_getinfo($ch); curl_close($ch); $headerSize = (int)($info['header_size'] ?? 0); $headersRaw = substr((string)$raw, 0, $headerSize); $body = $headOnly ? '' : substr((string)$raw, $headerSize, SIMON_MAX_BODY_BYTES); return [ 'ok' => true, 'status' => (int)($info['http_code'] ?? 0), 'final_url' => (string)($info['url'] ?? $url), 'redirects' => (int)($info['redirect_count'] ?? 0), 'content_type' => (string)($info['content_type'] ?? ''), 'ms' => round($elapsed, 1), 'headers_raw' => $headersRaw, 'body' => $body, ]; } function parseHeaders(string $raw): array { $blocks = preg_split("~\R\R~", trim($raw)); $last = trim((string)end($blocks)); $map = []; foreach (preg_split("~\R~", $last) ?: [] as $line) { if (str_contains($line, ':')) { [$name, $value] = explode(':', $line, 2); $map[strtolower(trim($name))] = trim($value); } } return $map; } function securityHeaderFindings(array $headers): array { $required = [ 'content-security-policy' => 'Content-Security-Policy', 'strict-transport-security' => 'Strict-Transport-Security', 'x-content-type-options' => 'X-Content-Type-Options', 'referrer-policy' => 'Referrer-Policy', 'permissions-policy' => 'Permissions-Policy', ]; $missing = []; foreach ($required as $key => $label) { if (!isset($headers[$key]) || trim((string)$headers[$key]) === '') { $missing[] = $label; } } return $missing; } function normalizeUrlPath(string $url): string { $path = (string)(parse_url($url, PHP_URL_PATH) ?: '/'); return '/' . ltrim(rawurldecode($path), '/'); } function localPathForUrl(string $url): ?string { $path = normalizeUrlPath($url); $candidate = documentRoot() . $path; $candidate = str_replace('//', '/', $candidate); $realParent = realpath(dirname($candidate)); if ($realParent === false || !startsWithPath($realParent, documentRoot())) { return null; } return $realParent . '/' . basename($candidate); } function lineEvidence(string $file, int $context = 2): array { if (!is_file($file) || !is_readable($file)) { return []; } $lines = @file($file, FILE_IGNORE_NEW_LINES); if (!is_array($lines)) { return []; } $rules = [ 'eval-base64' => '~\beval\s*\(\s*base64_decode\s*\(~i', 'dynamic-eval' => '~\beval\s*\(|new\s+Function\s*\(~i', 'system-exec' => '~\b(shell_exec|system|passthru|proc_open|popen|exec)\s*\(~i', 'encoded-payload' => '~[A-Za-z0-9+\/]{900,}={0,2}~', 'base64-decode' => '~\b(base64_decode|atob)\s*\(~i', 'gzinflate' => '~\bgzinflate\s*\(~i', 'rot13' => '~\bstr_rot13\s*\(~i', 'assert-exec' => '~\bassert\s*\(~i', 'dynamic-include' => '~\b(include|include_once|require|require_once)\s*\(?\s*\$~i', 'webshell-keyword' => '~\b(c99|r57|filesman|b374k|wso)\b~i', ]; $hits = []; foreach ($lines as $index => $line) { foreach ($rules as $rule => $regex) { if (preg_match($regex, $line)) { $start = max(0, $index - $context); $end = min(count($lines) - 1, $index + $context); $snippet = []; for ($i = $start; $i <= $end; $i++) { $snippet[] = ['line' => $i + 1, 'text' => mb_substr((string)$lines[$i], 0, 500)]; } $hits[] = [ 'rule' => $rule, 'line' => $index + 1, 'snippet' => $snippet, 'severity' => in_array($rule, ['eval-base64', 'system-exec', 'webshell-keyword'], true) ? 'critical' : (in_array($rule, ['dynamic-eval', 'encoded-payload', 'gzinflate', 'assert-exec'], true) ? 'high' : 'review'), ]; } } } return array_slice($hits, 0, 100); } function phpLint(string $file): array { if (!is_file($file) || strtolower(pathinfo($file, PATHINFO_EXTENSION)) !== 'php') { return ['available' => false, 'output' => 'Not a PHP file.']; } if (!function_exists('proc_open')) { return ['available' => false, 'output' => 'proc_open is disabled.']; } $command = escapeshellarg(PHP_BINARY) . ' -l ' . escapeshellarg($file); $descriptors = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $process = @proc_open($command, $descriptors, $pipes); if (!is_resource($process)) { return ['available' => false, 'output' => 'Could not start PHP lint.']; } $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); $code = proc_close($process); return ['available' => true, 'ok' => $code === 0, 'output' => trim((string)$stdout . "\n" . (string)$stderr)]; } function candidateErrorLogs(): array { $root = documentRoot(); $candidates = [ ini_get('error_log') ?: '', $root . '/error_log', $root . '/error.log', dirname($root) . '/logs/error.log', dirname($root) . '/logs/php_errors.log', $root . '/logs/error.log', $root . '/_logs/error.log', $root . '/_logs/php_errors.log', ]; $out = []; foreach (array_unique(array_filter($candidates)) as $path) { $real = realpath((string)$path); if ($real && is_file($real) && is_readable($real)) { $out[] = $real; } } return $out; } function tailFile(string $file, int $maxLines = 180): array { if (!is_file($file) || !is_readable($file)) { return []; } $lines = @file($file, FILE_IGNORE_NEW_LINES); if (!is_array($lines)) { return []; } return array_slice($lines, -$maxLines); } function backupFile(string $file): ?string { if (!is_file($file)) { return null; } $backupDir = storageRoot() . '/backups'; @mkdir($backupDir, 0755, true); $name = str_replace(['/', '\\', ':'], '_', ltrim($file, '/')); $backup = $backupDir . '/' . gmdate('Ymd-His') . '-' . $name; return @copy($file, $backup) ? $backup : null; } function atomicWrite(string $file, string $content): bool { $dir = dirname($file); if (!is_dir($dir) || !is_writable($dir)) { return false; } $temp = $file . '.simon-tmp-' . bin2hex(random_bytes(3)); if (@file_put_contents($temp, rtrim($content) . PHP_EOL, LOCK_EX) === false) { return false; } @chmod($temp, 0644); if (!@rename($temp, $file)) { @unlink($temp); return false; } return true; } function appendManagedBlock(string $file, string $marker, string $block): array { $existing = is_file($file) ? (string)@file_get_contents($file) : ''; $start = "# BEGIN {$marker}"; $end = "# END {$marker}"; $pattern = '~\Q' . $start . '\E.*?\Q' . $end . '\E~s'; $managed = $start . "\n" . trim($block) . "\n" . $end; $new = preg_match($pattern, $existing) ? (string)preg_replace($pattern, $managed, $existing) : rtrim($existing) . ($existing !== '' ? "\n\n" : '') . $managed . "\n"; $backup = is_file($file) ? backupFile($file) : null; if (!atomicWrite($file, $new)) { return ['ok' => false, 'message' => 'Write failed.', 'backup' => $backup]; } return ['ok' => true, 'message' => 'Managed block installed.', 'backup' => $backup]; } function headersBlock(): string { return <<<'HTACCESS' <IfModule mod_headers.c> Header always set X-Content-Type-Options "nosniff" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set X-Frame-Options "SAMEORIGIN" Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(self), payment=(), usb=()" Header always set Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'; img-src 'self' data: blob: https:; media-src 'self' blob: https:; font-src 'self' data: https:; style-src 'self' 'unsafe-inline' https:; script-src 'self' 'unsafe-inline' https:; connect-src 'self' https: wss:; frame-src 'self' https:" </IfModule> HTACCESS; } function sensitiveProtectionBlock(): string { return <<<'HTACCESS' Options -Indexes <IfModule mod_authz_core.c> Require all denied </IfModule> <IfModule !mod_authz_core.c> Order allow,deny Deny from all </IfModule> HTACCESS; } function generateSitemap(array $successfulUrls): array { $urls = []; foreach ($successfulUrls as $url) { $path = normalizeUrlPath($url); if (preg_match('~(?:/admin/|/private/|/_|/config/|/logs?/|/cache/|/api/auth/core/)~i', $path)) { continue; } if (preg_match('~\.(?:css|js|png|jpe?g|gif|webp|svg|ico|zip|pdf|json|xml)$~i', $path)) { continue; } $urls[] = rtrim(SIMON_TARGET_ORIGIN, '/') . $path; } $urls[] = rtrim(SIMON_TARGET_ORIGIN, '/') . '/'; $urls = array_values(array_unique($urls)); sort($urls); $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; $xml .= "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"; foreach ($urls as $url) { $xml .= " <url><loc>" . htmlspecialchars($url, ENT_XML1 | ENT_QUOTES, 'UTF-8') . "</loc></url>\n"; } $xml .= "</urlset>\n"; return ['xml' => $xml, 'count' => count($urls)]; } function knownTargets(): array { return [ '/500.php', '/admin/simon_intelligence_master_console.php', '/admin/simon_web_360_ui.php', '/api/auth/callback/google_callback.php', '/api/auth/core/auth_config.php', '/api/auth/core/session_bootstrap.php', '/api/talk.php', '/arcade/games/decoder.html', '/comics/telemetry_collect.php', '/config/secrets.php', '/connlink/index.php', '/connlink/INDEX2.PHP', '/connlink/', '/connlink/api/ai.php', '/connlink/api/system_validate.php', '/connlink/api/template_preview.php', '/connlink/api/voice_pipeline_test.php', '/downloads/', '/downloads/error.php', '/downloads/protected.php', '/downloads/uap.php', '/infinity/restore.php', '/sitemap.xml', '/robots.txt', '/books/index.php', '/web360/ui/simon_feed_helix.html', ]; } function investigate(array $paths): array { $results = []; $successful = []; foreach ($paths as $path) { $path = '/' . ltrim(trim((string)$path), '/'); if ($path === '/') { $url = rtrim(SIMON_TARGET_ORIGIN, '/') . '/'; } else { $url = rtrim(SIMON_TARGET_ORIGIN, '/') . $path; } $http = httpFetch($url, false); $headers = ($http['ok'] ?? false) ? parseHeaders((string)($http['headers_raw'] ?? '')) : []; $local = localPathForUrl($url); $exists = $local !== null && is_file($local); $evidence = $exists ? lineEvidence($local) : []; $lint = $exists ? phpLint($local) : ['available' => false, 'output' => 'No matching local file.']; $status = (int)($http['status'] ?? 0); if ($status >= 200 && $status < 400) { $successful[] = $url; } $expectedPrivate = (bool)preg_match('~^/(?:config/|api/auth/core/|_data/|_logs/|private/)~i', $path); $classification = 'review'; if ($expectedPrivate && in_array($status, [401, 403, 404], true)) { $classification = 'expected-protection'; } elseif ($status >= 500) { $classification = 'server-error'; } elseif ($status === 404) { $classification = 'missing'; } elseif ($status >= 200 && $status < 400 && $expectedPrivate) { $classification = 'exposed-sensitive'; } elseif ($status >= 200 && $status < 400) { $classification = 'reachable'; } $results[] = [ 'path' => $path, 'url' => $url, 'http' => $http, 'headers' => $headers, 'missing_headers' => securityHeaderFindings($headers), 'local_path' => $local, 'exists' => $exists, 'readable' => $exists && is_readable($local), 'writable' => $exists && is_writable($local), 'evidence' => $evidence, 'lint' => $lint, 'classification' => $classification, ]; } return ['generated_at' => nowIso(), 'results' => $results, 'successful_urls' => $successful]; } function saveReport(array $report): string { $file = storageRoot() . '/' . randomId('investigation') . '.json'; @file_put_contents($file, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX); return $file; } function summarize(array $report): array { $summary = ['server-error' => 0, 'missing' => 0, 'exposed-sensitive' => 0, 'expected-protection' => 0, 'reachable' => 0, 'review' => 0, 'evidence_hits' => 0, 'missing_headers' => 0]; foreach ($report['results'] ?? [] as $row) { $key = (string)($row['classification'] ?? 'review'); $summary[$key] = ($summary[$key] ?? 0) + 1; $summary['evidence_hits'] += count($row['evidence'] ?? []); $summary['missing_headers'] += count($row['missing_headers'] ?? []); } return $summary; } $messages = []; $report = $_SESSION['last_report'] ?? null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!authorized()) { $messages[] = ['type' => 'error', 'text' => 'Invalid installer token.']; } elseif (!validCsrf()) { $messages[] = ['type' => 'error', 'text' => 'Security token expired. Reload and try again.']; } else { $action = (string)($_POST['action'] ?? ''); try { if ($action === 'investigate') { $custom = preg_split('~\R+~', trim((string)($_POST['custom_paths'] ?? ''))) ?: []; $paths = array_values(array_unique(array_filter(array_merge(knownTargets(), $custom)))); $report = investigate($paths); $report['report_file'] = saveReport($report); $_SESSION['last_report'] = $report; $messages[] = ['type' => 'success', 'text' => 'Investigation completed and saved privately.']; } elseif ($action === 'apply_headers') { $target = documentRoot() . '/.htaccess'; $result = appendManagedBlock($target, 'SIMON SECURITY HEADERS', headersBlock()); $messages[] = ['type' => $result['ok'] ? 'success' : 'error', 'text' => $result['message'] . ($result['backup'] ? ' Backup: ' . $result['backup'] : '')]; } elseif ($action === 'protect_sensitive') { $dirs = ['/config', '/api/auth/core']; foreach ($dirs as $dir) { $full = documentRoot() . $dir; if (!is_dir($full)) { $messages[] = ['type' => 'warning', 'text' => "Skipped {$dir}; directory does not exist."]; continue; } $result = appendManagedBlock($full . '/.htaccess', 'SIMON PRIVATE DIRECTORY', sensitiveProtectionBlock()); $messages[] = ['type' => $result['ok'] ? 'success' : 'error', 'text' => "{$dir}: {$result['message']}" . ($result['backup'] ? ' Backup: ' . $result['backup'] : '')]; } } elseif ($action === 'generate_sitemap') { if (!is_array($report)) { throw new RuntimeException('Run an investigation first.'); } $generated = generateSitemap($report['successful_urls'] ?? []); $target = documentRoot() . '/sitemap.xml'; $backup = is_file($target) ? backupFile($target) : null; if (!atomicWrite($target, $generated['xml'])) { throw new RuntimeException('Could not write sitemap.xml.'); } $messages[] = ['type' => 'success', 'text' => "Generated sitemap.xml with {$generated['count']} public URLs." . ($backup ? " Backup: {$backup}" : '')]; } elseif ($action === 'create_robots') { $target = documentRoot() . '/robots.txt'; $content = "User-agent: *\nAllow: /\nDisallow: /admin/\nDisallow: /config/\nDisallow: /private/\nDisallow: /_data/\nDisallow: /_logs/\nSitemap: " . rtrim(SIMON_TARGET_ORIGIN, '/') . "/sitemap.xml\n"; $backup = is_file($target) ? backupFile($target) : null; if (!atomicWrite($target, $content)) { throw new RuntimeException('Could not write robots.txt.'); } $messages[] = ['type' => 'success', 'text' => 'robots.txt created or updated.' . ($backup ? " Backup: {$backup}" : '')]; } elseif ($action === 'logout') { $_SESSION = []; session_destroy(); header('Location: ' . strtok((string)$_SERVER['REQUEST_URI'], '?')); exit; } } catch (Throwable $e) { $messages[] = ['type' => 'error', 'text' => $e->getMessage()]; } } } $isAuthorized = authorized(); $summary = is_array($report) ? summarize($report) : []; $errorLogs = $isAuthorized ? candidateErrorLogs() : []; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <title>SIMON Security Investigator</title> <style> :root{color-scheme:dark;--bg:#050914;--panel:#0d1728;--panel2:#111f35;--line:#28405f;--text:#edf6ff;--muted:#9fb4cc;--cyan:#67e8f9;--good:#53d58a;--warn:#ffd166;--bad:#ff6878;--purple:#c084fc} *{box-sizing:border-box}body{margin:0;background:radial-gradient(1000px 700px at 50% -15%,#15335c 0,transparent 60%),linear-gradient(160deg,#03060d,var(--bg));color:var(--text);font:14px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}.wrap{width:min(1500px,96vw);margin:24px auto}.top{display:flex;gap:14px;align-items:center;position:sticky;top:0;z-index:10;background:rgba(5,9,20,.93);backdrop-filter:blur(14px);border:1px solid var(--line);border-radius:16px;padding:12px 16px;margin-bottom:14px}.orb{width:22px;height:22px;border-radius:50%;background:radial-gradient(circle at 30% 25%,#fff,var(--cyan) 20%,#7c3aed 60%,#091123 100%);box-shadow:0 0 22px var(--cyan)}.brand{font-weight:900;letter-spacing:.18em}.spacer{flex:1}.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px}.card{grid-column:span 12;background:linear-gradient(180deg,rgba(17,31,53,.96),rgba(8,16,29,.96));border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:0 18px 70px rgba(0,0,0,.28)}.half{grid-column:span 6}.third{grid-column:span 4}.head{padding:14px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px}.body{padding:16px}.title{font-size:16px;font-weight:900}.muted{color:var(--muted)}.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}.metric{padding:14px;border:1px solid var(--line);border-radius:13px;background:rgba(0,0,0,.22)}.metric b{font-size:25px;display:block}.btn{appearance:none;border:1px solid var(--line);background:#152945;color:var(--text);padding:10px 14px;border-radius:11px;font-weight:850;cursor:pointer}.btn:hover{border-color:var(--cyan)}.btn.danger{background:#4a1d2a}.btn.good{background:#123d2b}.btn.warn{background:#493b16}.actions{display:flex;flex-wrap:wrap;gap:9px}.notice{padding:11px 13px;border-radius:10px;margin-bottom:10px;border:1px solid var(--line)}.notice.success{border-color:#236f49;background:#0e2d20}.notice.error{border-color:#8b2d3b;background:#35131b}.notice.warning{border-color:#81681d;background:#332a0e}input,textarea{width:100%;border:1px solid var(--line);background:#07111f;color:var(--text);padding:11px;border-radius:10px}textarea{min-height:130px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.table-wrap{overflow:auto;max-height:680px}table{width:100%;border-collapse:collapse;min-width:1100px}th,td{text-align:left;padding:10px;border-bottom:1px solid var(--line);vertical-align:top}th{position:sticky;top:0;background:#101d31;color:var(--muted);font-size:12px}.pill{display:inline-block;padding:3px 8px;border-radius:999px;border:1px solid var(--line);font-size:11px;font-weight:900}.server-error,.exposed-sensitive,.critical,.error{color:var(--bad)}.expected-protection,.reachable,.success{color:var(--good)}.missing,.warning,.review{color:var(--warn)}code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}code{background:#050b14;border:1px solid #1c3049;border-radius:6px;padding:2px 5px}pre{white-space:pre-wrap;word-break:break-word;background:#050b14;border:1px solid #1c3049;border-radius:10px;padding:10px;max-height:330px;overflow:auto}.details summary{cursor:pointer;font-weight:800}.section-note{border-left:3px solid var(--cyan);padding-left:12px}.tiny{font-size:11px}.footer{color:var(--muted);text-align:center;padding:24px}@media(max-width:980px){.half,.third{grid-column:span 12}.top{position:static}.grid{display:block}.card{margin-bottom:12px}} </style> </head> <body> <div class="wrap"> <div class="top"><div class="orb"></div><div class="brand">SIMON SECURITY INVESTIGATOR</div><div class="spacer"></div><span class="pill">v<?= h(SIMON_VERSION) ?></span><?php if ($isAuthorized): ?><form method="post"><input type="hidden" name="csrf" value="<?= h(csrfToken()) ?>"><button class="btn" name="action" value="logout">Logout</button></form><?php endif; ?></div> <?php foreach ($messages as $message): ?><div class="notice <?= h($message['type']) ?>"><?= h($message['text']) ?></div><?php endforeach; ?> <?php if (!$isAuthorized): ?> <div class="card"><div class="head"><div class="title">Authorize Investigator</div></div><div class="body"><p class="section-note">Change <code>SIMON_INSTALL_TOKEN</code> inside this file before uploading. Delete this investigator after remediation. Apparently leaving an administrative repair console online forever is frowned upon.</p><form method="post"><input type="hidden" name="csrf" value="<?= h(csrfToken()) ?>"><label>Installer token</label><input type="password" name="install_token" required><div style="height:10px"></div><button class="btn good" name="action" value="authorize">Authorize</button></form></div></div> <?php else: ?> <div class="grid"> <div class="card half"><div class="head"><div class="title">1. Investigate</div></div><div class="body"><p>Tests the URLs from the July 12 security report, maps them to local files, captures exact suspicious lines, performs PHP syntax checks, inspects headers, and classifies expected protection separately from genuine failure.</p><form method="post"><input type="hidden" name="csrf" value="<?= h(csrfToken()) ?>"><label>Additional paths, one per line</label><textarea name="custom_paths" placeholder="/another/problem.php /another/path/"></textarea><div style="height:10px"></div><button class="btn good" name="action" value="investigate">Run Investigation</button></form></div></div> <div class="card half"><div class="head"><div class="title">2. Guided Remediation</div></div><div class="body"><p>Each operation creates a backup when replacing an existing file. These actions do not edit PHP source or secrets.</p><div class="actions"><form method="post"><input type="hidden" name="csrf" value="<?= h(csrfToken()) ?>"><button class="btn warn" name="action" value="apply_headers">Install Security Headers</button></form><form method="post"><input type="hidden" name="csrf" value="<?= h(csrfToken()) ?>"><button class="btn danger" name="action" value="protect_sensitive">Protect Config/Auth Core</button></form><form method="post"><input type="hidden" name="csrf" value="<?= h(csrfToken()) ?>"><button class="btn" name="action" value="generate_sitemap">Generate sitemap.xml</button></form><form method="post"><input type="hidden" name="csrf" value="<?= h(csrfToken()) ?>"><button class="btn" name="action" value="create_robots">Create robots.txt</button></form></div><p class="muted tiny">Security headers use a compatibility-first CSP. Test Web360, maps, chat, trackers, and authentication after applying it. HSTS is deliberately omitted until TLS is confirmed stable.</p></div></div> <?php if (is_array($report)): ?> <div class="card"><div class="head"><div class="title">Investigation Summary</div><div class="spacer"></div><span class="muted"><?= h((string)($report['generated_at'] ?? '')) ?></span></div><div class="body"><div class="metrics"> <?php foreach ([ 'server-error' => 'HTTP 500', 'exposed-sensitive' => 'Sensitive Exposed', 'expected-protection' => 'Expected Protection', 'missing' => 'Missing', 'evidence_hits' => 'Code Evidence', 'missing_headers' => 'Missing Headers', ] as $key => $label): ?><div class="metric"><span class="muted"><?= h($label) ?></span><b class="<?= h($key) ?>"><?= (int)($summary[$key] ?? 0) ?></b></div><?php endforeach; ?> </div><p class="muted">Private report: <code><?= h((string)($report['report_file'] ?? '')) ?></code></p></div></div> <div class="card"><div class="head"><div class="title">URL and Source Evidence</div></div><div class="body table-wrap"><table><thead><tr><th>Path</th><th>HTTP</th><th>Classification</th><th>Local File</th><th>PHP Lint</th><th>Missing Headers</th><th>Evidence</th></tr></thead><tbody> <?php foreach ($report['results'] ?? [] as $row): $status=(int)($row['http']['status']??0); ?> <tr><td><code><?= h((string)$row['path']) ?></code><div class="tiny muted"><?= h((string)($row['http']['error'] ?? $row['http']['final_url'] ?? '')) ?></div></td><td><b><?= $status ?></b><div class="tiny muted"><?= h((string)($row['http']['ms'] ?? '')) ?> ms</div></td><td><span class="pill <?= h((string)$row['classification']) ?>"><?= h((string)$row['classification']) ?></span></td><td><?= $row['exists'] ? '<code>'.h((string)$row['local_path']).'</code>' : '<span class="muted">No matching local file</span>' ?></td><td><details class="details"><summary class="<?= !empty($row['lint']['ok']) ? 'success' : 'warning' ?>"><?= !empty($row['lint']['available']) ? (!empty($row['lint']['ok']) ? 'PASS' : 'FAIL') : 'N/A' ?></summary><pre><?= h((string)($row['lint']['output'] ?? '')) ?></pre></details></td><td><?= empty($row['missing_headers']) ? '<span class="success">Complete</span>' : h(implode(', ', $row['missing_headers'])) ?></td><td><?php if (empty($row['evidence'])): ?><span class="muted">None detected</span><?php else: ?><details class="details"><summary><?= count($row['evidence']) ?> hit(s)</summary><?php foreach ($row['evidence'] as $hit): ?><p><b class="<?= h((string)$hit['severity']) ?>"><?= h((string)$hit['rule']) ?></b> line <?= (int)$hit['line'] ?></p><pre><?php foreach ($hit['snippet'] as $snippet) echo h(str_pad((string)$snippet['line'], 5, ' ', STR_PAD_LEFT).' '.$snippet['text'])."\n"; ?></pre><?php endforeach; ?></details><?php endif; ?></td></tr> <?php endforeach; ?> </tbody></table></div></div> <?php endif; ?> <div class="card half"><div class="head"><div class="title">PHP / Apache Error Logs</div></div><div class="body"><?php if (!$errorLogs): ?><p class="muted">No readable candidate error log was found. Use the IONOS error-log viewer immediately after loading a failing URL.</p><?php else: ?><?php foreach ($errorLogs as $log): ?><details class="details"><summary><?= h($log) ?></summary><pre><?= h(implode("\n", tailFile($log))) ?></pre></details><?php endforeach; ?><?php endif; ?></div></div> <div class="card half"><div class="head"><div class="title">Interpretation Rules</div></div><div class="body"><ul><li><b>Expected protection:</b> private config/auth paths returning 401, 403, or 404.</li><li><b>Server error:</b> any route returning 500. Review PHP lint, include paths, and error logs.</li><li><b>Exposed sensitive:</b> private config/auth paths returning 2xx or 3xx.</li><li><b>Code evidence:</b> exact source lines, not vague “dynamic execution” theater.</li><li><b>Missing:</b> expected public resources such as sitemap.xml returning 404.</li></ul></div></div> </div> <?php endif; ?> <div class="footer">Built with Pride. Inspired by Nature. Guided by Intelligence. Connected Through Knowledge. Evolving Together.</div> </div> </body> </html>
Save file
Quick jump
open a path
Open