diff --git a/src/Filesystem.php b/src/Filesystem.php index 2ff64f4..aaae682 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -36,10 +36,24 @@ public function getFileResolvedPath(string $filename): string|bool $paths = (array) $this->config['path']; foreach ($paths as $path) { - $full_filename = realpath($path) . '/' . $filename . '.' . ltrim($this->getExtension(), '.'); + $base = realpath($path); + + if ($base === false) { + continue; + } + + $full_filename = $base . '/' . $filename . '.' . ltrim($this->getExtension(), '.'); $realpath = realpath($full_filename); - if ($realpath !== false && file_exists($realpath)) { + // Containment check: the resolved file must live inside the + // configured view directory. This blocks path traversal from + // resolving an arbitrary file, independent of the '.'-to-'/' + // rewrite above (which a future change could weaken). + if ( + $realpath !== false + && str_starts_with($realpath, $base . DIRECTORY_SEPARATOR) + && file_exists($realpath) + ) { return $realpath; } } diff --git a/src/Lexique/CompileJson.php b/src/Lexique/CompileJson.php index 98e87b6..b953e5b 100644 --- a/src/Lexique/CompileJson.php +++ b/src/Lexique/CompileJson.php @@ -20,7 +20,12 @@ function ($match) { $parts = explode(',', $match[0]); if (! isset($parts[1])) { - return ""; + // Escape HTML-significant characters by default so values + // rendered inside