404 a null byte or a directory on the showcase's asset route

Plan step 38 review: a %00 in the asset path reached realpath(), which
throws on a null byte, so the route answered 500 instead of 404; a
directory whose name ended in a served extension would have reached
response()->file(). Both 404 now. The tests add what the review
probed: encoded dot segments and slashes, backslashes, absolute paths,
a directory, a very long path and a symbolic link pointing out of a
served folder, all 404.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-15 06:08:39 +02:00
co-authored by Claude Opus 5
parent 2f649983fa
commit 3027f85cef
2 changed files with 33 additions and 4 deletions
@@ -69,12 +69,16 @@ class ShowcaseAssetController
}
/**
* A font or an SVG the stylesheet's `url()`s point at, MIME-typed by extension: `..` and
* anything the two folders above do not contain resolves to nothing and 404s, the same as an
* unlisted extension (the font's own `OFL.txt`, sitting right beside it, included).
* A font or an SVG the stylesheet's `url()`s point at, MIME-typed by extension. The path is
* resolved on disk first, so `..` (however it was encoded), a symbolic link and anything else
* that lands outside the two folders above 404s, as does a directory, a null byte (which
* `realpath()` would throw on) and an unlisted extension (the font's own `OFL.txt`, sitting
* right beside it, included).
*/
public function file(string $path): BinaryFileResponse
{
abort_if(str_contains($path, "\0"), 404);
$segments = explode('/', $path, 2);
$folder = $segments[0] ?? null;
$rest = $segments[1] ?? null;
@@ -84,7 +88,7 @@ class ShowcaseAssetController
$root = realpath(dirname(__DIR__, 3)."/resources/{$folder}");
$real = $root === false ? false : realpath("{$root}/{$rest}");
abort_unless($real !== false && str_starts_with($real, $root.DIRECTORY_SEPARATOR), 404);
abort_unless($real !== false && str_starts_with($real, $root.DIRECTORY_SEPARATOR) && is_file($real), 404);
$mime = self::MIME_TYPES[strtolower(pathinfo($real, PATHINFO_EXTENSION))] ?? null;