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,279
Folders
408
Scanned Size
3.84 GB
PHP Files
891
Editable Text Files
12,540
File viewer
guarded to /htdocs
/sitelogs/security.php
<?php declare(strict_types=1); require_once __DIR__ . '/_bootstrap.php'; /************************************************* * SIMON Guardian — Security & Health * Advanced, “for dummies” view * Depends on guardian_lib.php (page shell + helpers) *************************************************/ declare(strict_types=1); date_default_timezone_set('America/Chicago'); require_once __DIR__ . '/guardian_lib.php'; // uses page_start()/page_end() /* ---------- small utils ---------- */ function short_path(string $p): string { return preg_replace('#^.+/htdocs/#', 'htdocs/', $p); } function mono(string $s): string { return '<span class="mono">'.h($s).'</span>'; } /* ---------- Panel: HTTP Security Headers ---------- */ function probe_headers(string $url): array { $ctx = stream_context_create(['http'=>[ 'method'=>'GET','timeout'=>6,'ignore_errors'=>true, 'header'=>"User-Agent: SIMON-Guardian/1\r\n" ]]); $h = @get_headers($url, true, $ctx); if (!$h) return []; $out = []; foreach ($h as $k=>$v) if (is_string($k)) $out[strtolower($k)] = is_array($v) ? end($v) : $v; return $out; } function grade_headers(array $h): array { $pick = fn($k)=>$h[strtolower($k)] ?? ''; return [ 'Strict-Transport-Security' => str_contains($pick('strict-transport-security'), 'max-age=') ? 'ok' : 'miss', 'Content-Security-Policy' => $pick('content-security-policy') ? 'ok' : 'miss', 'Referrer-Policy' => $pick('referrer-policy') ? 'ok' : 'miss', 'X-Frame-Options' => $pick('x-frame-options') ? 'ok' : 'miss', 'X-Content-Type-Options' => strtolower($pick('x-content-type-options'))==='nosniff' ? 'ok' : 'miss', 'Permissions-Policy' => $pick('permissions-policy') ? 'ok' : 'miss', 'Cache-Control' => (str_contains(strtolower($pick('cache-control')), 'no-store') || str_contains(strtolower($pick('cache-control')), 'private')) ? 'ok' : 'warn', ]; } /* ---------- Panel: .htaccess snippets ---------- */ function ht_snippets(): array { return [ 'deny-raw-logs' => <<<HT # Deny direct access to raw logs <FilesMatch "^(access|error)\\.log(\\.|$)"> Require all denied </FilesMatch> HT, 'mime-guards' => <<<HT # Prevent PHP execution in uploads/misc <FilesMatch "\\.(phps?|phtml)$"> Require all denied </FilesMatch> HT, 'security-headers' => <<<HT # Core security headers <IfModule mod_headers.c> Header set X-Content-Type-Options "nosniff" Header set X-Frame-Options "SAMEORIGIN" Header set Referrer-Policy "strict-origin-when-cross-origin" Header set Permissions-Policy "geolocation=(), microphone=()" Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" env=HTTPS </IfModule> HT, 'hotlink-optional' => <<<HT # Optional: basic image hotlink protection <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTP_REFERER} !^$ RewriteCond %{HTTP_REFERER} !^https?://(www\\.)?gaylordsinclair\\.com/ [NC] RewriteRule \\.(png|jpe?g|gif|webp)$ - [F,NC] </IfModule> HT, ]; } /* ---------- Panel: Permissions scan ---------- */ function scan_perms(string $root, int $max=5000): array { $out=[]; $i=0; $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS)); foreach ($it as $p) { if (++$i > $max) break; /** @var SplFileInfo $p */ $perm = @fileperms($p) ?: 0; $m = substr(sprintf('%o', $perm), -4); $isDir = $p->isDir(); $bad = ($isDir && $m > '0755') || (!$isDir && $m > '0644'); $susp = preg_match('/\\.(env|sql|bak|zip|rar|tar|7z)$/i', $p->getFilename()); if ($bad || $susp) { $out[] = ['flag'=>$bad?'perm':'susp', 'perm'=>$m, 'path'=>$p->getPathname()]; } } return $out; } /* ---------- Panel: Integrity baseline ---------- */ function hash_set(array $files): array { $o=[]; foreach ($files as $f) { if (is_file($f)) $o[$f] = hash_file('sha256', $f); } ksort($o); return $o; } /* ---------- Panel: TLS expiry ---------- */ function tls_days_left(string $host, int $port=443): int { $ctx = stream_context_create(['ssl'=>['capture_peer_cert'=>true,'verify_peer'=>false,'verify_peer_name'=>false]]); $client = @stream_socket_client("ssl://{$host}:{$port}", $errno, $errstr, 6, STREAM_CLIENT_CONNECT, $ctx); if (!$client) return -1; $params = stream_context_get_params($client); $cert = openssl_x509_parse($params['options']['ssl']['peer_certificate'] ?? null); if (!$cert || empty($cert['validTo_time_t'])) return -1; return (int)floor(($cert['validTo_time_t'] - time())/86400); } /* ---------- Render ---------- */ page_start('Security & Health'); /* local CSS to widen main grid and push “Environment” to footer */ echo <<<CSS <style> .grid-main{display:grid;grid-template-columns:repeat(3, minmax(260px, 1fr));gap:14px} .footer-env{margin-top:20px} @media (max-width:1100px){ .grid-main{grid-template-columns:1fr 1fr} } @media (max-width:800px){ .grid-main{grid-template-columns:1fr} } .note{color:#9db3e6;font-size:.92rem} textarea.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace} </style> CSS; /* ---------- MAIN GRID ---------- */ echo "<div class='grid-main'>"; /* A) HTTP headers */ $siteUrl = (isset($_SERVER['HTTPS']) ? 'https':'http').'://'.($_SERVER['HTTP_HOST'] ?? 'localhost').'/'; $hdrs = probe_headers($siteUrl); $grades = grade_headers($hdrs); echo "<div class='card'><h3>HTTP Security Headers</h3> <table><tr><th>Header</th><th>Status</th><th>Value</th></tr>"; foreach ($grades as $k=>$s) { $val = $hdrs[strtolower($k)] ?? '(none)'; $badge = $s==='ok'?'✅':'⚠️'; echo "<tr><td>".h($k)."</td><td>{$badge} ".strtoupper(h($s))."</td><td style='word-break:break-word'>".mono((string)$val)."</td></tr>"; } echo "</table> <p class='note'>Probe: ".h($siteUrl)." — Add missing headers in <code>.htaccess</code> (see right panel).</p> </div>"; /* B) .htaccess snippets */ $snips = ht_snippets(); echo "<div class='card'><h3>.htaccess Hardening Generator</h3> <p class='note'>Copy & paste the blocks you need into your <code>.htaccess</code>. Safe defaults.</p>"; foreach ($snips as $label => $block) { echo "<details style='margin:8px 0'><summary class='btn'>".h($label)."</summary> <textarea class='mono' style='width:100%;height:160px'>".h($block)."</textarea></details>"; } echo "</div>"; /* C) Permissions scan (sitelogs only) */ $permFind = scan_perms(__DIR__, 5000); echo "<div class='card'><h3>Permissions Scan — /sitelogs</h3>"; if (!$permFind) { echo "<p class='note'>All good. No overly permissive files or suspicious extensions (first 5,000 entries).</p>"; } else { echo "<table><tr><th>Flag</th><th>Perm</th><th>Path</th></tr>"; foreach ($permFind as $r) { echo "<tr><td>".h($r['flag'])."</td><td>".h($r['perm'])."</td><td>".mono(short_path($r['path']))."</td></tr>"; } echo "</table><p class='note'>Aim for <b>0644</b> files / <b>0755</b> directories. <b>perm</b>=too open, <b>susp</b>=worth a look.</p>"; } echo "</div>"; /* D) Integrity baseline */ $baselinePath = __DIR__.'/baseline.json'; $keyFiles = [ __DIR__.'/index.php', __DIR__.'/guardian.php', __DIR__.'/guardian_lib.php', __DIR__.'/console.php', __DIR__.'/summary.php', __FILE__, ]; $curHashes = hash_set($keyFiles); if (($_POST['mkbaseline'] ?? '') === '1') { @file_put_contents($baselinePath, json_encode($curHashes, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); echo "<div class='card'><h3>Integrity Baseline</h3><p class='note'>Baseline saved.</p></div>"; } else { $old = is_file($baselinePath) ? (json_decode((string)@file_get_contents($baselinePath), true) ?: []) : []; echo "<div class='card'><h3>Integrity Baseline</h3>"; if (!$old) { echo "<p class='note'>No baseline yet. Create one to detect silent edits.</p> <form method='post'><button class='btn' name='mkbaseline' value='1'>Save baseline</button></form>"; } else { $diffNew=$diffChanged=$diffGone=[]; foreach ($curHashes as $f=>$h) { if (!isset($old[$f])) $diffNew[]=$f; elseif ($old[$f]!==$h) $diffChanged[]=$f; } foreach ($old as $f=>$h) if (!isset($curHashes[$f])) $diffGone[]=$f; echo "<table><tr><th>Status</th><th>File</th></tr>"; foreach ($diffNew as $f) echo "<tr><td>➕ new</td><td>".mono(basename($f))."</td></tr>"; foreach ($diffChanged as $f) echo "<tr><td>✏️ changed</td><td>".mono(basename($f))."</td></tr>"; foreach ($diffGone as $f) echo "<tr><td>🗑️ removed</td><td>".mono(basename($f))."</td></tr>"; if (!$diffNew && !$diffChanged && !$diffGone) echo "<tr><td colspan='2'>✅ Matches baseline</td></tr>"; echo "</table> <form method='post'><button class='btn' name='mkbaseline' value='1'>Update baseline</button></form>"; } echo "</div>"; } /* E) TLS / certificate */ $days = tls_days_left($_SERVER['HTTP_HOST'] ?? 'localhost'); echo "<div class='card'><h3>TLS / Certificate</h3>"; echo ($days>=0) ? "<p class='note'>Certificate for <b>".h($_SERVER['HTTP_HOST'] ?? '')."</b> expires in <b>{$days} days</b>.</p>" : "<p class='note'>Could not read certificate (port 443 probe failed).</p>"; echo "</div>"; echo "</div>"; // grid-main /* ---------- FOOTER: Environment (moved here) ---------- */ echo "<div class='footer-env card'> <h3>Environment</h3> <table> <tr><th>App</th><td>".h($CFG['APP_NAME'])."</td></tr> <tr><th>Time</th><td>".h(date('Y-m-d H:i:s T'))."</td></tr> <tr><th>HTTPS</th><td>".(isset($_SERVER['HTTPS'])?'on':'off')."</td></tr> <tr><th>Server</th><td>".h($_SERVER['SERVER_SOFTWARE'] ?? 'Apache')."</td></tr> <tr><th>PHP</th><td>".PHP_VERSION."</td></tr> <tr><th>DocRoot</th><td>".mono(short_path($_SERVER['DOCUMENT_ROOT'] ?? '(n/a)'))."</td></tr> <tr><th>Script</th><td>".mono(short_path(__FILE__))."</td></tr> <tr><th>Remote IP</th><td>".h($_SERVER['REMOTE_ADDR'] ?? '')."</td></tr> <tr><th>User-Agent</th><td>".h($_SERVER['HTTP_USER_AGENT'] ?? '')."</td></tr> </table> <p class='note'>Paths shown are abbreviated after <code>htdocs/</code>. Full paths are still used internally.</p> </div>"; page_end();
Save file
Quick jump
open a path
Open