Merge: trusted-IP throttle exemption + session hardening

This commit is contained in:
Carlos Arias 2026-07-11 09:16:30 -05:00
commit 41f1b941fa
3 changed files with 43 additions and 0 deletions

View file

@ -24,6 +24,12 @@ class JsonController extends \Controller
protected function throttle(string $key, int $max, int $window): bool protected function throttle(string $key, int $max, int $window): bool
{ {
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// Trusted server-local / whitelisted callers are never throttled: the static-site build
// fetches the read API from the box itself thousands of times per build. The throttle is
// for public abuse (real client IPs), not our own build. See Functions::isTrustedIp.
if (\Functions::isTrustedIp($ip)) {
return false;
}
$count = (int) \Db::getValue( $count = (int) \Db::getValue(
"SELECT COUNT(*) FROM `api_requests` "SELECT COUNT(*) FROM `api_requests`
WHERE `requesting_ip` = ? AND `request` = ? WHERE `requesting_ip` = ? AND `request` = ?

View file

@ -186,6 +186,31 @@ class Functions {
/**
* True for trusted, server-local / whitelisted callers that should bypass public rate
* limits (e.g. the SSG build hammering the read API from the box itself).
* - loopback (127.0.0.1, ::1)
* - the server's own address (same-host request)
* - anything in the optional TRUSTED_IPS config list (comma-separated IPs or CIDRs
* e.g. the server's public IP, an internal build host, or a private range like 10.0.0.0/8)
* Deliberately does NOT blanket-trust all private ranges: behind a private-IP reverse proxy
* that would exempt every visitor. Add specific ranges to TRUSTED_IPS if your setup wants them.
*/
public static function isTrustedIp($ip) {
if ($ip === '127.0.0.1' || $ip === '::1') return true;
$serverAddr = $_SERVER['SERVER_ADDR'] ?? '';
if ($serverAddr !== '' && $ip === $serverAddr) return true;
if (defined('TRUSTED_IPS') && TRUSTED_IPS) {
foreach (array_filter(array_map('trim', explode(',', TRUSTED_IPS))) as $entry) {
if ($ip === $entry) return true;
if (strpos($entry, '/') !== false
&& filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)
&& self::ip_in_range($ip, $entry)) return true;
}
}
return false;
}
public static function ip_in_range($ip, $range) { public static function ip_in_range($ip, $range) {
if (strpos($range, '/') == false) if (strpos($range, '/') == false)
$range .= '/32'; $range .= '/32';

View file

@ -1,4 +1,16 @@
<?php <?php
// Harden the session cookie before it is created (login state rides on it).
if (session_status() !== PHP_SESSION_ACTIVE) {
$https = (($_SERVER['HTTPS'] ?? '') !== '' && $_SERVER['HTTPS'] !== 'off')
|| ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'httponly' => true,
'secure' => $https,
'samesite' => 'Lax',
]);
}
@session_start(); @session_start();
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
require dirname(__DIR__) . '/config.php'; require dirname(__DIR__) . '/config.php';