# How to Fix an Exposed Laravel Storage Directory

> Your Laravel storage directory is publicly accessible, exposing logs, cache files, and uploaded data. Learn how to restrict access.

**Severity:** high | **Category:** Application Security

---

## The Issue

An exposed storage directory allows attackers to access your application logs (containing error details and potentially sensitive data), session files, cache data, and user-uploaded files that should be private. The storage directory contains framework-generated files and user uploads that were never intended to be publicly accessible. Automated scanners regularly check for exposed Laravel storage paths.

## Steps to Fix

### 1. Verify your document root

The most common cause of an exposed storage directory is an incorrect document root. Your web server must point to /public, not the project root:

# Nginx - correct
root /var/www/yourapp/public;

# Nginx - WRONG, exposes storage/
root /var/www/yourapp;

With the correct document root, the storage directory is outside the web-accessible path and cannot be accessed directly.

### 2. Remove or secure the storage symlink

The php artisan storage:link command creates a symlink from public/storage to storage/app/public. Only files in storage/app/public should be web-accessible.

Check what the symlink points to:

ls -la public/storage

It should point to ../storage/app/public, not ../storage. If it points to the wrong location, recreate it:

rm public/storage
php artisan storage:link

Never store private files in storage/app/public. Use storage/app/private/ for sensitive uploads.

### 3. Block direct access to storage paths

Add web server rules to block access to storage directories that should not be public. In Nginx:

location ~* ^/storage/(app|framework|logs) {
    deny all;
    return 404;
}

In Apache .htaccess:

RewriteRule ^storage/(app|framework|logs) - [F,L]

This ensures only public/storage (the symlink to storage/app/public) is accessible.

### 4. Serve private files through a controller

For files that require authentication, serve them through a Laravel controller instead of direct file access:

Route::get('/files/{path}', function (string $path) {
    $fullPath = storage_path('app/private/' . $path);

    if (!file_exists($fullPath)) {
        abort(404);
    }

    // Add authorization check
    // abort_unless(auth()->user()->canAccessFile($path), 403);

    return response()->file($fullPath);
})->middleware('auth')->where('path', '.*');

## Verification

Check that storage directories are not accessible:

curl -I https://yourdomain.com/storage/logs/laravel.log
curl -I https://yourdomain.com/storage/framework/sessions/
curl -I https://yourdomain.com/storage/app/

All should return 403 or 404. Only public/storage files (user-uploaded public assets) should return 200.

## Prevention

Always use the correct document root. Store sensitive files in storage/app/private/ and serve through authenticated controllers. Never store user uploads in a publicly accessible directory without access controls. Use StackShield to monitor for exposed storage paths.

---

## Frequently Asked Questions

### What sensitive data is in the storage directory?

The storage directory contains: application logs with error details and stack traces (storage/logs/), session data for all users (storage/framework/sessions/), compiled Blade templates (storage/framework/views/), cached configuration (storage/framework/cache/), and uploaded files (storage/app/). Logs are especially dangerous as they may contain user data, API responses, and exception details.

### Is the public/storage symlink safe?

The symlink from public/storage to storage/app/public is safe by design. Only files you explicitly place in storage/app/public are accessible. The risk comes from storing sensitive files in the public directory or having the wrong document root that exposes the entire storage directory.

