Merge pull request #259 from copenhagentruckwash/fix-redis-autoload-cache-vulnerability

Harden Redis-backed autoloader against poisoned path inclusion
This commit is contained in:
Jeppe B
2026-06-02 00:37:33 +02:00
committed by GitHub
+30
View File
@@ -60,19 +60,37 @@ try {
*/
spl_autoload_register(function (string $class): void {
$class = ltrim($class, '\\');
<<<<<<< HEAD
$wdReal = rtrim((string) realpath(WD), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$modulesRoot = $wdReal . 'modules' . DIRECTORY_SEPARATOR;
$isPathInside = static function (string $path, string $root): bool {
$resolved = realpath($path);
if ($resolved === false) {
return false;
}
$resolved = rtrim($resolved, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
return str_starts_with($resolved, $root);
=======
$cache_key = 'autoload:' . $class;
$is_loaded = static function (string $candidate): bool {
return class_exists($candidate, false)
|| interface_exists($candidate, false)
|| trait_exists($candidate, false)
|| (function_exists('enum_exists') && enum_exists($candidate, false));
>>>>>>> refs/remotes/origin/master
};
// Check Redis cache first
if (defined('redis')) {
try {
<<<<<<< HEAD
$cached = redis->get('autoload:' . $class);
if ($cached && is_file($cached) && $isPathInside($cached, $wdReal)) {
=======
$cached = redis->get($cache_key);
if (is_string($cached) && $cached !== '' && is_file($cached)) {
>>>>>>> refs/remotes/origin/master
require_once $cached;
if ($is_loaded($class)) {
return;
@@ -120,6 +138,18 @@ spl_autoload_register(function (string $class): void {
$module_dirs = redis->get_array('autoload:module_dirs');
} catch (\Throwable $e) {}
}
if (is_array($module_dirs)) {
$module_dirs = array_values(array_filter($module_dirs, static function ($item) use ($base, $modulesRoot, $isPathInside): bool {
if (!is_string($item) || $item === '' || str_contains($item, DIRECTORY_SEPARATOR) || str_contains($item, '..')) {
return false;
}
$candidate = $base . 'modules' . DIRECTORY_SEPARATOR . $item;
return is_dir($candidate) && $isPathInside($candidate, $modulesRoot);
}));
}
if ($module_dirs === null) {
$module_dirs = array_filter(scandir($base . 'modules'), function($item) use ($base) {
return $item !== '.' && $item !== '..' && is_dir($base . 'modules' . DIRECTORY_SEPARATOR . $item);