laravel-at/laravel-image-sanitize
Laravel package to sanitize images on upload by stripping EXIF/metadata, normalizing orientation, and re-encoding to safer formats. Helps reduce privacy leaks and potential payloads while keeping image quality and integrating cleanly with Laravel apps.
Installation
composer require laravel-at/laravel-image-sanitize
Publish the config file:
php artisan vendor:publish --provider="LaravelAt\ImageSanitize\ImageSanitizeServiceProvider" --tag="config"
Basic Usage Sanitize an uploaded image file before processing:
use LaravelAt\ImageSanitize\Facades\ImageSanitize;
$sanitizedPath = ImageSanitize::sanitize($request->file('image')->getRealPath());
First Use Case
store methods of controllers to sanitize before saving:
public function store(Request $request) {
$request->validate(['image' => 'required|image']);
$sanitizedPath = ImageSanitize::sanitize($request->file('image')->getRealPath());
// Save $sanitizedPath to storage
}
Middleware for Automatic Sanitization Create middleware to sanitize all uploaded images:
namespace App\Http\Middleware;
use LaravelAt\ImageSanitize\Facades\ImageSanitize;
use Closure;
class SanitizeUploads {
public function handle($request, Closure $next) {
if ($request->hasFile('image')) {
$request->merge([
'image' => ImageSanitize::sanitize($request->file('image')->getRealPath())
]);
}
return $next($request);
}
}
Register in app/Http/Kernel.php:
protected $routeMiddleware = [
'sanitize.uploads' => \App\Http\Middleware\SanitizeUploads::class,
];
Apply to routes:
Route::post('/upload', [UploadController::class, 'store'])->middleware('sanitize.uploads');
Event-Based Sanitization
Listen to file.uploaded events (if using Laravel Filesystem events):
use LaravelAt\ImageSanitize\Facades\ImageSanitize;
event(new FileUploaded($path));
// In listener:
$sanitizedPath = ImageSanitize::sanitize($event->path);
Queue-Based Processing For large files, defer sanitization to a queue job:
use LaravelAt\ImageSanitize\Facades\ImageSanitize;
use Illuminate\Support\Facades\Queue;
Queue::push(new SanitizeImageJob($request->file('image')->getRealPath()));
Custom Sanitization Rules
Extend the default rules via config (config/imagesanitize.php):
'rules' => [
'max-width' => 5000,
'max-height' => 5000,
'allowed-extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
'disallowed-mimetypes' => ['image/svg+xml'], // Explicitly block SVG
],
Dynamic Rule Overrides Override rules per request:
$sanitizedPath = ImageSanitize::sanitize($path, [
'allowed-extensions' => ['png', 'webp'],
'max-width' => 3000,
]);
Integration with Storage Disks Chain sanitization with disk operations:
use Illuminate\Support\Facades\Storage;
$sanitizedPath = ImageSanitize::sanitize($path);
Storage::disk('s3')->putFileAs('images', $sanitizedPath, 'sanitized-' . basename($sanitizedPath));
False Positives with SVG
masterminds/html5).'disallowed-mimetypes' => [], // Remove SVG from blocked list
Then sanitize SVG separately:
$svgContent = file_get_contents($path);
$sanitizedSvg = (new Masterminds\HTML5())->loadHTML($svgContent)->saveHTML();
Path Handling in Shared Hosting
$path is an absolute filesystem path, not a URL or relative path.realpath() to verify:
$absolutePath = realpath($request->file('image')->getRealPath());
Memory Limits
memory_limit. Increase it temporarily:
ini_set('memory_limit', '256M');
$sanitizedPath = ImageSanitize::sanitize($path);
Race Conditions
Storage::copy() to duplicate the file first:
$tempPath = tempnam(sys_get_temp_dir(), 'img');
Storage::copy($path, $tempPath);
$sanitizedPath = ImageSanitize::sanitize($tempPath);
Enable Verbose Logging
Set debug to true in config to log skipped rules:
'debug' => env('APP_DEBUG', false),
Check Sanitization Report The method returns an array with metadata:
$report = ImageSanitize::sanitize($path, [], true);
// $report = [
// 'sanitized_path' => '/path/to/sanitized.jpg',
// 'skipped_rules' => ['max-width'], // Rules that didn’t apply
// 'warnings' => ['file_larger_than_allowed'], // Non-fatal issues
// ];
Test with Malicious Files Use test vectors from OWASP to verify:
exiftool -Comment="<script>alert(1)</script>" image.jpg.binwalk to check for hidden files in images.Custom Sanitizers
Add new sanitizers by extending the LaravelAt\ImageSanitize\Sanitizers\Sanitizer class:
namespace App\Sanitizers;
use LaravelAt\ImageSanitize\Sanitizers\Sanitizer;
class CustomSanitizer extends Sanitizer {
public function sanitize($path) {
// Custom logic (e.g., check for specific metadata)
return $path;
}
}
Register in config/imagesanitize.php:
'sanitizers' => [
\App\Sanitizers\CustomSanitizer::class,
],
Pre/Post-Sanitization Hooks
Use Laravel’s events to run logic before/after sanitization:
// In EventServiceProvider:
protected $listen = [
'imagesanitize.before' => [
\App\Listeners\LogSanitization::class,
],
'imagesanitize.after' => [
\App\Listeners\NotifyAdmin::class,
],
];
Bypass for Trusted Sources Skip sanitization for internal files (e.g., admin uploads):
if ($request->user()->isAdmin()) {
$path = $request->file('image')->store('admin');
} else {
$path = ImageSanitize::sanitize($request->file('image')->getRealPath());
}
How can I help you explore Laravel packages today?