The package's service provider registers a public route GET /{assets_route}/{file?}
(default /_minify/{file}) with the constraint ->where('file', '(.*)') and no middleware.
It is enabled by default (assets_enabled = true) and auto-registered through package
discovery. HttpConnectionHandler::__invoke() builds the path to read from the {file}
parameter but only strips leading slashes, so ../ segments escape the configured
assets_storage directory. Any file readable by the PHP process is returned with status 200
as text/plain - for example the application's .env (database credentials, APP_KEY,
third-party API keys).
src/Controllers/HttpConnectionHandler.php (v1.2.4, also current main a4fea32):
22 // remove slash or backslash from the beginning of the file path
23 $file = ltrim($file, '/\\');
24
25 // make sure the storage has trailing slash
26 $file = base_path(rtrim($storage, '/').'/'.$file);
27
28 if (!file_exists($file)) {
29 return abort(404);
30 }
31
32 $content = file_get_contents($file);Laravel binds route parameters from the URL-decoded path (Request::decodedPath()), so an
encoded slash (%2F) becomes a real / inside {file}. A request for
/_minify/..%2F.env therefore reaches the controller as {file} = ../.env, and line 26
builds base_path('resources/../.env'), which is the application's .env. Additional ../
segments reach any path on the filesystem.
Releases before 1.2.1 used resource_path($file) plus a ^(css|js)/ prefix check; the prefix
check does not stop traversal (css/../../.env), so those releases are affected as well.
The {file} route parameter is concatenated onto the storage directory without normalising it
or checking that the result is still inside that directory.
Resolve the storage directory and the requested file with realpath() and only serve regular
files inside the storage directory; everything else returns 404 as for missing files:
// resolve "..", "." and symlinks, then only serve regular files that
// are inside the storage directory (prevents path traversal)
$storagePath = realpath(base_path(rtrim($storage, '/')));
$file = realpath($file);
if ($storagePath === false || $file === false || !is_file($file)
|| strpos($file, rtrim($storagePath, '/\\').DIRECTORY_SEPARATOR) !== 0) {
return abort(404);
}Checked with a standalone script against the patched controller (the repository has no test
suite): css/app.css, /css/app.css, css/sub/nested.css, css/sub/../app.css -> 200;
css/missing.css, css (a directory), ../.env, css/../../.env,
../resources-backup/old.css (sibling folder with the same name prefix) and
../../../../etc/hostname -> 404. Before the patch, the last five were all served with 200.
php -l clean; syntax is compatible with PHP 7.2.
Note: the change also refuses files reached through a symlink that points outside the storage
directory. Optional further hardening (not in the patch, a behaviour change): serve only .css
and .js, as releases before 1.2.1 effectively did - today any file under resources/
(e.g. Blade templates) is still readable through the route by design.
Issue found and fixed by Claude, then reviewed and confirmed by Ief (GitHub: iefken).
<details><summary>Plain-language explanation</summary>
Laravel Minify makes a Laravel website's pages, stylesheets (CSS) and scripts (JS) smaller so
they load faster. One of its features is an asset route: a web address such as
https://example.com/_minify/css/app.css. When a browser asks for it, the package looks for the
file css/app.css inside a folder of the website (by default resources/), shrinks it and sends
it back.
The route is switched on by default, needs no login, and is added automatically as soon as the
package is installed.
The part of the address after /_minify/ is the file name, and it comes straight from the
visitor. The package glues it onto the folder name and reads that file. It removes slashes at the
very start of the name, but it never checks for .., which in a file path means "go up one
folder".
So a name like ../.env means: start in resources/, go up one folder (the website's own main
folder), and read .env. That file is where a Laravel site keeps its secrets: database password,
mail password, API keys and APP_KEY, the key that signs and encrypts the site's cookies.
/_minify/ followed by a file name that contains .. (written in its
URL-encoded form, so browsers and most web servers pass it on unchanged).../.env.resources/../.env, which is simply the site's .env... steps reach any file the web server may read: other configuration, source code,
keys, system files.A library lets anyone ask the front desk for a book "from the public shelf". The clerk walks to the public shelf and follows the directions on the request slip from there. Nobody checks the slip, so "from the public shelf, go back out, into the manager's office, top drawer" gets you the manager's private papers.
In an isolated, offline container: the package's own code, a fake website folder with a fake
.env containing only the marker APP_KEY=bughunt-canary-not-a-secret, and the exact address
decoding Laravel uses. A normal stylesheet was served as expected (the control); the .. request
returned the marker. Tested on the latest release 1.2.4 and on 1.1.5 (older releases need the
name to start with css/ or js/, which css/../../.env satisfies).
Anyone can confirm on their own test site that the route is active, without reading anything
sensitive: /_minify/css/app.css (or another real asset) returns the minified file.
| Concern | Real? | Why |
|---|---|---|
Steal the site's database and mail passwords from .env | Yes | One unauthenticated request on a default install |
Forge or decrypt cookies / sessions with APP_KEY | Yes | APP_KEY signs and encrypts Laravel cookies; with it, login sessions can be forged on sites using cookie sessions, and old Laravel versions had remote-code-execution chains built on a leaked key |
| Read source code, private keys, other files | Yes | Anything the web server user can read |
| Change or delete files | No | The route only reads |
| Take the site offline | No | Nothing beyond a normal request |
| Works on every server setup | Not always | Apache with its default AllowEncodedSlashes Off rejects encoded slashes; nginx and PHP's own server pass them on. Not tested against a real nginx/Apache in this check |
| Who is really hurt: every website that installed the package and did not turn the asset route | ||
off - their visitors' data sits behind the credentials in .env. |
Before reading a file, turn both the folder and the requested file into their real, final
locations (resolving ..) and refuse anything that is not a normal file inside the folder.
Missing or refused files get the same "not found" answer as before.
How to verify without reading code: after updating, /_minify/css/app.css still works, and an
address containing ..%2F returns "404 Not Found" instead of a file.
Until a fixed release exists, site owners can switch the route off with
MINIFY_ASSETS_ENABLED=false in .env (this only disables the asset route, not HTML
minification).
</details>
Reproduced on 2026-11-06 in an isolated, offline container (PHP 8.3.36, laravel-minify 1.2.4
with illuminate/http 13.40.0; and 1.1.5 with illuminate/http 11.51.0). The package's real
controller was called with the {file} value produced by Illuminate\Http\Request::decodedPath()
and the route's own (.*) pattern. The Laravel helpers config(), base_path(), abort()
and response() (from laravel/framework, not a dependency of the package) were stubbed. A fake
application root held a .env containing only a marker string.
/_minify/css/app.css -> {file} = css/app.css -> HTTP 200, text/css, minified CSS./_minify/..%2F.env -> {file} = ../.env -> HTTP 200, text/plain, body = contents of .env
(marker returned). On 1.1.5 this returns 404 because of the css|js prefix check./_minify/css%2F..%2F..%2F.env -> {file} = css/../../.env -> HTTP 200, text/plain,
.env contents, on both 1.2.4 and 1.1.5.
Not tested: a full Laravel application behind a real web server. Whether the encoded slash
reaches PHP depends on the front server: nginx (try_files ... /index.php?$query_string) andphp artisan serve forward the original request URI; Apache's default
AllowEncodedSlashes Off rejects %2F in the path with 404.
Unauthenticated arbitrary file read on every application that has the package installed with
the default configuration. Leaking .env exposes database and mail credentials, API keys and
APP_KEY, which allows forging/decrypting Laravel cookies and sessions. Source code and private
keys readable by the web server user are exposed as well. Nothing is written or executed by
this bug itself.
Workaround until a release: set MINIFY_ASSETS_ENABLED=false (the route is then not
registered; HTML minification keeps working), or block /_minify/ requests containing .. or
%2F at the web server.
소스: NVD
무료 취약점 평가
9개 보안 영역에서 클라우드 보안 관행을 평가하여 위험 수준을 벤치마킹하고 방어의 허점을 파악하세요.
맞춤형 데모를 받아보세요
"내가 본 최고의 사용자 경험은 클라우드 워크로드에 대한 완전한 가시성을 제공합니다."
"Wiz는 클라우드 환경에서 무슨 일이 일어나고 있는지 볼 수 있는 단일 창을 제공합니다."
"우리는 Wiz가 무언가를 중요한 것으로 식별하면 실제로 중요하다는 것을 알고 있습니다."