CodePress CMS v1.8.0: BotGuard security engine, HAProxy docs & ARIA fix

- Implement BotGuard security engine (Bot, AI, Scraper & Empty UA blocking)
- Add Admin Security page (/admin/security) with toggles, rate limiter & block/allowlists
- Add per-IP RateLimiter handoff in index.php with HTTP 429 response
- Add dynamic /robots.txt generation and noai/noimageai meta tags
- Add RequestLogger status column and blocked badges in admin request logs
- Fix ARIAComponents.php syntax errors on lines 67, 137, 262
- Add HAProxy / PFSense bot blocking & IP forwarding guide (docs/haproxy-bot-blocking.md)
- Update version to 1.8.0 with release notes in version.php and guides
This commit is contained in:
2026-07-29 14:26:48 +02:00
parent 62dd7ddb9c
commit 239762fd3a
18 changed files with 618 additions and 117 deletions
+8
View File
@@ -66,6 +66,11 @@ $layoutSidebarColor = $layoutThemeConfig['header_color'] ?? '#0a369d';
<i class="bi bi-sliders"></i> Configuratie
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= ($route ?? '') === 'security' ? 'active' : '' ?>" href="/admin/security">
<i class="bi bi-shield-check"></i> Beveiliging
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= ($route ?? '') === 'plugins' ? 'active' : '' ?>" href="/admin/plugins">
<i class="bi bi-plug"></i> Plugins
@@ -135,6 +140,9 @@ $layoutSidebarColor = $layoutThemeConfig['header_color'] ?? '#0a369d';
case 'config':
require __DIR__ . '/pages/config.php';
break;
case 'security':
require __DIR__ . '/pages/security.php';
break;
# Media route (removed from menu)
// Uncomment if media functionality is needed elsewhere
// require __DIR__ . '/pages/media.php';
+15 -3
View File
@@ -70,6 +70,7 @@
<th>IP</th>
<th>Pagina</th>
<th>Type / Gebruiker</th>
<th>Status</th>
<th>Taal</th>
<th>User agent</th>
<th>Referrer</th>
@@ -77,7 +78,7 @@
</thead>
<tbody>
<?php foreach ($requestLogs as $log): ?>
<tr>
<tr class="<?= str_starts_with($log['status'] ?? 'ok', 'blocked') ? 'table-danger' : '' ?>">
<td class="text-nowrap small text-muted"><?= htmlspecialchars($log['time']) ?></td>
<td><code class="small"><?= htmlspecialchars($log['ip']) ?></code></td>
<td><?= htmlspecialchars($log['page']) ?></td>
@@ -86,9 +87,20 @@
<i class="bi <?= $log['visitor_info']['icon'] ?>"></i> <?= $log['visitor_info']['label'] ?>
</span>
</td>
<td>
<?php if (str_starts_with($log['status'] ?? 'ok', 'blocked')): ?>
<span class="badge bg-danger" title="<?= htmlspecialchars($log['status']) ?>">
<i class="bi bi-shield-x"></i> Geblokkeerd
</span>
<?php else: ?>
<span class="badge bg-success" title="Toegestaan">
<i class="bi bi-check-circle"></i> OK
</span>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($log['lang']) ?></td>
<td class="small text-muted" title="<?= htmlspecialchars($log['ua']) ?>"><?= htmlspecialchars(substr($log['ua'], 0, 60)) ?><?= strlen($log['ua']) > 60 ? '…' : '' ?></td>
<td class="small text-muted" title="<?= htmlspecialchars($log['referrer']) ?>"><?= htmlspecialchars(substr($log['referrer'], 0, 40)) ?><?= strlen($log['referrer']) > 40 ? '…' : '' ?></td>
<td class="small text-muted" title="<?= htmlspecialchars($log['ua']) ?>"><?= htmlspecialchars(substr($log['ua'], 0, 50)) ?><?= strlen($log['ua']) > 50 ? '…' : '' ?></td>
<td class="small text-muted" title="<?= htmlspecialchars($log['referrer']) ?>"><?= htmlspecialchars(substr($log['referrer'], 0, 30)) ?><?= strlen($log['referrer']) > 30 ? '…' : '' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
+114
View File
@@ -0,0 +1,114 @@
<h2 class="mb-4"><i class="bi bi-shield-check"></i> Beveiliging & Bot Bescherming</h2>
<form method="POST" action="/admin/security">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>">
<div class="card shadow-sm mb-4">
<div class="card-header bg-dark text-white">
<i class="bi bi-robot"></i> Bot, AI & Scraper Blokkering
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="block_ai_bots" name="block_ai_bots" value="1" <?= !empty($sec['block_ai_bots']) ? 'checked' : '' ?>>
<label class="form-check-label fw-bold" for="block_ai_bots">
<i class="bi bi-cpu text-danger"></i> AI Crawlers & Scrapers blokkeren (403 Forbidden)
</label>
<div class="form-text">Blokkeert bekende AI-bots zoals <code>GPTBot</code>, <code>ChatGPT-User</code>, <code>ClaudeBot</code>, <code>PerplexityBot</code>, <code>CCBot</code>, <code>Google-Extended</code>, <code>Bytespider</code>, <code>Applebot-Extended</code>, etc.</div>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="block_scrapers" name="block_scrapers" value="1" <?= !empty($sec['block_scrapers']) ? 'checked' : '' ?>>
<label class="form-check-label fw-bold" for="block_scrapers">
<i class="bi bi-bug text-warning"></i> Geautomatiseerde Scrapers & Tools blokkeren (403 Forbidden)
</label>
<div class="form-text">Blokkeert automatische scraping tools zoals <code>HTTrack</code>, <code>Scrapy</code>, <code>PhantomJS</code>, <code>HeadlessChrome</code>, <code>cURL</code>, <code>Wget</code>, <code>Python-requests</code>, <code>libwww-perl</code>, etc.</div>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="block_empty_user_agent" name="block_empty_user_agent" value="1" <?= !empty($sec['block_empty_user_agent']) ? 'checked' : '' ?>>
<label class="form-check-label fw-bold" for="block_empty_user_agent">
<i class="bi bi-slash-circle text-secondary"></i> Verzoeken met lege User-Agent header blokkeren
</label>
<div class="form-text">Veel eenvoudige bots en aanvalscripts sturen geen User-Agent header mee.</div>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="block_search_engines" name="block_search_engines" value="1" <?= !empty($sec['block_search_engines']) ? 'checked' : '' ?>>
<label class="form-check-label fw-bold text-danger" for="block_search_engines">
<i class="bi bi-search"></i> Legitieme zoekmachines blokkeren (Google, Bing, DuckDuckGo, etc.)
</label>
<div class="form-text text-danger">Let op: Schakel dit alleen in als je wilt dat de hele site niet in zoekmachines (zoals Google en Bing) verschijnt.</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header">
<i class="bi bi-speedometer2"></i> Snelheidsbeperking (Rate Limiting per IP)
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="rate_limit_enabled" name="rate_limit_enabled" value="1" <?= !empty($sec['rate_limit_enabled']) ? 'checked' : '' ?>>
<label class="form-check-label fw-bold" for="rate_limit_enabled">Rate Limiter inschakelen</label>
<div class="form-text">Voorkomt dat scrapers of bots de site overbelasten door tientallen verzoeken per seconde uit te voeren. Overschrijding geeft HTTP 429.</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="rate_limit_max" class="form-label">Maximaal aantal verzoeken per IP</label>
<input type="number" class="form-control" id="rate_limit_max" name="rate_limit_max" min="10" max="1000" value="<?= (int)($sec['rate_limit_max'] ?? 60) ?>">
<small class="form-text text-muted">Aanbevolen: 60 verzoeken.</small>
</div>
<div class="col-md-6 mb-3">
<label for="rate_limit_window" class="form-label">Tijdvenster (in seconden)</label>
<input type="number" class="form-control" id="rate_limit_window" name="rate_limit_window" min="10" max="3600" value="<?= (int)($sec['rate_limit_window'] ?? 60) ?>">
<small class="form-text text-muted">Aanbevolen: 60 seconden (1 minuut).</small>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header">
<i class="bi bi-list-check"></i> Eigen Filters & IP-Lijsten
</div>
<div class="card-body">
<div class="mb-3">
<label for="custom_blocked_agents" class="form-label fw-bold">Aangepaste User-Agent Blocklist</label>
<textarea class="form-control font-monospace" id="custom_blocked_agents" name="custom_blocked_agents" rows="3" placeholder="Typ één User-Agent patroon per regel (bijv. MyCustomBot)"><?= htmlspecialchars(implode("\n", $sec['custom_blocked_agents'] ?? [])) ?></textarea>
<div class="form-text">Verzoeken waarvan de User-Agent dit patroon bevat krijgen een 403 Forbidden.</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="allowed_ips" class="form-label fw-bold text-success"><i class="bi bi-shield-check"></i> IP Whitelist (Altijd Toegang)</label>
<textarea class="form-control font-monospace" id="allowed_ips" name="allowed_ips" rows="3" placeholder="Één IP per regel (bijv. 82.169.10.20)"><?= htmlspecialchars(implode("\n", $sec['allowed_ips'] ?? [])) ?></textarea>
<div class="form-text text-success">IP's op de whitelist worden nooit geblokkeerd door bot-filters of rate limiting.</div>
</div>
<div class="col-md-6 mb-3">
<label for="blocked_ips" class="form-label fw-bold text-danger"><i class="bi bi-shield-x"></i> IP Blocklist (Altijd Geblokkeerd)</label>
<textarea class="form-control font-monospace" id="blocked_ips" name="blocked_ips" rows="3" placeholder="Één IP per regel (bijv. 198.51.100.4)"><?= htmlspecialchars(implode("\n", $sec['blocked_ips'] ?? [])) ?></textarea>
<div class="form-text text-danger">IP's op de blocklist krijgen altijd direct een HTTP 403 Forbidden.</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-lg mb-4">
<i class="bi bi-check-lg"></i> Beveiligingsinstellingen opslaan
</button>
</form>
<div class="card shadow-sm mb-4">
<div class="card-header">
<i class="bi bi-file-earmark-text"></i> Dynamische <code>robots.txt</code> Preview
</div>
<div class="card-body p-0">
<div class="bg-dark text-light p-3 rounded-bottom">
<pre class="m-0 text-light font-monospace small"><?= htmlspecialchars($robotsPreview ?? '') ?></pre>
</div>
</div>
<div class="card-footer text-muted small">
Deze <code>robots.txt</code> wordt automatisch geserveerd op <code>/robots.txt</code> en past zich aan je instellingen aan.
</div>
</div>