-
Notifications
You must be signed in to change notification settings - Fork 0
Security Best Practices
File uploads are one of the most common attack surfaces in a web app. The library gives you the tools; this page is the checklist for using them safely.
The browser-supplied MIME type (getMimeType())
and the file name are both attacker-controlled. Always restrict by the
real type, which the library detects with finfo:
$options = [
'allowed_extensions' => ['png', 'jpg', 'jpeg'],
'allowed_mime_types' => ['image/png', 'image/jpeg'], // checked via finfo
'allowed_max_size' => 4 * 1024 * 1024,
];With both set, malware.php renamed to cat.png is rejected: the extension
passes but finfo reports text/x-php, which is not in the allow-list. See
Validation.
If users can upload a file and the web server will execute files in that
directory, an uploaded .php becomes remote code execution. Store uploads in a
directory the web server does not execute:
new LocalAdapter([
'dir' => '/var/app/storage/uploads/', // NOT under /public
'url' => 'https://example.com/files/', // served read-only
]);Serve them through a controller, a read-only location, or a CDN — never from a
directory where the interpreter runs .php/.phar files.
User-controlled names invite path traversal, overwrites and odd characters.
Generate your own name; rename()
keeps the correct extension:
$file->rename(bin2hex(random_bytes(16))); // -> <32hex>.<ext>
$upload->setFile($file)->to('uploads');The library normalizes the $target prefix (stripping and collapsing slashes),
but the safest path is to not let raw user input reach $target or the name at
all.
Use an allow-list, never a deny-list. Allow exactly the extensions you expect:
'allowed_extensions' => ['jpg', 'jpeg', 'png', 'pdf'], // allow-listA deny-list (['php', 'phtml', ...]) is always incomplete — new dangerous
extensions and server quirks (e.g. file.php.jpg on misconfigured servers)
keep finding ways through. The extension is taken from the original name, so an
allow-list of safe types is the reliable control.
Set allowed_max_size (bytes), and keep it at or below your php.ini
limits:
; php.ini
upload_max_filesize = 8M
post_max_size = 10M'allowed_max_size' => 8 * 1024 * 1024, // 8 MBA file larger than the PHP limits never arrives as a valid upload; it comes in with an upload error code, which the library rejects automatically.
-
FTP sends credentials and data in clear text. Set
'ssl' => truefor FTPS, or use SFTP via a different tool. See FTP Adapter. - S3 is HTTPS by default through the SDK.
Read FTP passwords and AWS keys from the environment, not from committed code:
new S3Adapter([
'key' => getenv('AWS_ACCESS_KEY_ID'),
'secret_key' => getenv('AWS_SECRET_ACCESS_KEY'),
'region' => getenv('AWS_REGION'),
'bucket' => getenv('AWS_BUCKET'),
]);For S3, you can also rely on the SDK's default credential chain (IAM roles)
and leave key/secret_key empty.
The default ACL is public-read. For anything sensitive, use 'ACL' => 'private' and serve via pre-signed URLs generated with the AWS SDK directly.
See S3 Adapter.
Even after validation, do not assume a stored image is "safe":
- Serve user content from a separate domain/subdomain where practical.
- Set
Content-Disposition: attachmentand a correctContent-Typewhen serving downloads. - Consider re-encoding images server-side to strip embedded payloads.
-
allowed_mime_typesset (real type viafinfo) -
allowed_extensionsis an allow-list of safe types -
allowed_max_sizeset, ≤php.inilimits -
diris outside the executable web root - file names are generated, not taken from user input
- FTP uses
ssl => true; secrets come from the environment - S3 ACL matches the sensitivity of the data
-
Validation · The
FileObject - Local · FTP · S3 adapters
initphp/upload · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Reference
Adapters
Practical Guides
Other