Add trusted-proxy auth hardening + PHP requirements checks
Lint / PHP (phpcs PSR-12) (push) Successful in 20s
Lint / JS (eslint) (push) Successful in 11s
Lint / PHP requirements (version + extensions) (push) Successful in 52s
Security / PHP Security (semgrep) (push) Successful in 2m6s
Lint / Deploy (push) Successful in 13s
Lint / Notify on failure (push) Has been skipped

Trusted-proxy hardening (defense-in-depth for Authelia forward-auth):
- AuthMiddleware now only honors Remote-* identity headers when REMOTE_ADDR
  is in a configured TRUSTED_PROXIES allowlist; otherwise it refuses with 403
  and logs an 'untrusted_proxy' security event. Previously anything that could
  reach PHP directly could spoof Remote-User/Remote-Groups and log in as admin.
- New config TRUSTED_PROXIES (comma-separated, from .env). Empty = enforcement
  off, so this is backward compatible until the allowlist is set on a host.

Requirements checks (so a PHP upgrade dropping an extension can't silently
break features like avatars again):
- config/requirements.php: single source of truth for min PHP version and
  required extensions (ldap, mysqli, curl, mbstring, fileinfo, json).
- scripts/check_requirements.php: CI script that fails the build if the
  environment doesn't satisfy them.
- New 'requirements' CI job installs those extensions and runs the check;
  deploy now depends on it.
- api/health.php: adds php_extensions + php_version checks so production
  monitoring surfaces the drift (returns 503 if a required extension is gone).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 10:22:53 -04:00
co-authored by Claude Opus 4.8
parent b3bc3ab159
commit b2c19745eb
6 changed files with 168 additions and 2 deletions
+34
View File
@@ -95,6 +95,40 @@ if (is_dir($rateLimitDir) && is_writable($rateLimitDir)) {
];
}
// Check 5: Required PHP extensions (catches e.g. a PHP upgrade silently
// dropping php-ldap, which breaks avatars with no other visible error).
$requirements = require dirname(__DIR__) . '/config/requirements.php';
$missingExt = array_values(array_filter(
$requirements['required_extensions'],
fn($ext) => !extension_loaded($ext)
));
if (empty($missingExt)) {
$checks['php_extensions'] = [
'status' => 'ok',
'message' => 'All required extensions loaded'
];
} else {
$checks['php_extensions'] = [
'status' => 'error',
'message' => 'Missing extensions: ' . implode(', ', $missingExt)
];
$healthy = false;
}
// Check 6: PHP version meets the declared minimum
if (version_compare(PHP_VERSION, $requirements['min_php_version'], '>=')) {
$checks['php_version'] = [
'status' => 'ok',
'message' => PHP_VERSION
];
} else {
$checks['php_version'] = [
'status' => 'error',
'message' => sprintf('PHP %s < required %s', PHP_VERSION, $requirements['min_php_version'])
];
$healthy = false;
}
// Calculate response time
$responseTime = round((microtime(true) - $startTime) * 1000, 2);