diff --git a/6.x/crud-fields.md b/6.x/crud-fields.md
index 09b67aaf..0c4b2bc2 100644
--- a/6.x/crud-fields.md
+++ b/6.x/crud-fields.md
@@ -1025,9 +1025,11 @@ This helps you avoid most quirks when validating file uploads using Laravel's va
use Backpack\CRUD\app\Library\Validation\Rules\ValidUpload;
'image' => ValidUpload::field('required')
- ->file('file|mimes:jpeg,png,jpg,gif,svg|max:2048'),
+ ->file('file|mimes:jpeg,png,jpg,gif|max:2048'),
```
+**NOTE**: When using `withFiles`, files are also checked against a list of [allowed file types](/docs/{{version}}/crud-uploaders#allowed-file-types). File types that browsers render as pages, like `svg` and `html`, are not allowed by default.
+
Input preview:

@@ -1076,9 +1078,11 @@ This will help you avoid most quirks of using Laravel's standard validation rule
use Backpack\CRUD\app\Library\Validation\Rules\ValidUploadMultiple;
'photos' => ValidUploadMultiple::field('required|min:2|max:5')
- ->file('file|mimes:jpeg,png,jpg,gif,svg|max:2048'),
+ ->file('file|mimes:jpeg,png,jpg,gif|max:2048'),
```
+**NOTE**: When using `withFiles`, files are also checked against a list of [allowed file types](/docs/{{version}}/crud-uploaders#allowed-file-types). File types that browsers render as pages, like `svg` and `html`, are not allowed by default.
+
**NOTE**: This field uses a `clear_{fieldName}` input to send the deleted files from the frontend to the backend. In case you are using `$guarded` add it there.
Eg: `protected $guarded = ['id', 'clear_photos'];`
@@ -1518,9 +1522,11 @@ Alternatively, you can manually implement the saving process yourself using mode
use Backpack\Pro\Uploads\Validation\ValidDropzone;
'photos' => ValidDropzone::field('required|min:2|max:5')
- ->file('file|mimes:jpeg,png,jpg,gif,svg|max:2048'),
+ ->file('file|mimes:jpeg,png,jpg,gif|max:2048'),
```
+**NOTE**: When using `withFiles`, files are also checked against a list of [allowed file types](/docs/{{version}}/crud-uploaders#allowed-file-types). File types that browsers render as pages, like `svg` and `html`, are not allowed by default.
+
**Step 5:** (optional) Configure the temp directory. Whenever new files are uploaded using the Dropzone operation, old files are automatically deleted from the temp directory. But you can also manually clean the temp directory. For more info and temp directory configuration options, see [this link](/docs/{{version}}/crud-how-to#configuring-the-temporary-directory).
Input preview:
diff --git a/6.x/crud-uploaders.md b/6.x/crud-uploaders.md
index 2b49ef32..99a42197 100644
--- a/6.x/crud-uploaders.md
+++ b/6.x/crud-uploaders.md
@@ -54,7 +54,68 @@ When `temporaryUrl` is set to `true`, this configures the amount of time in minu
- **`uploader`** - default: **null**
This allows you to overwrite or set the uploader class for this field. You can use any class that implements `UploaderInterface`.
- **`fileNamer`** - default: **null**
-It accepts a `FileNameGeneratorInterface` instance or a closure. As the name implies, this will be used to generate the file name. Read more about in the [Naming uploaded files](#upload-name-files) section.
+It accepts a `FileNameGeneratorInterface` instance or a closure. As the name implies, this will be used to generate the file name. Read more about in the [Naming uploaded files](#naming-files-when-using-uploaders) section.
+- **`allowedExtensions`** - default: **null** (uses the `allowed_upload_extensions` config)
+The extensions files uploaded to this field can be stored with. Read more about it in the [Allowed file types](#allowed-file-types) section.
+
+
+### Allowed file types
+
+On top of your validation rules, Uploaders only store files whose extension is in an allow list (since `backpack/crud` 6.8.17). This protects your admins even when a field has no validation rules: a file that browsers render as a page (like `svg` or `html`) could run scripts in the session of the admin that opens it, when served from your application domain.
+
+With the default [file namer](#naming-files-when-using-uploaders), the extension a file is stored with comes from **its content**, not from the name the user sent. So a `payload.png` file that contains SVG markup is detected as `svg`, and rejected. When the content can't be identified, the file is stored with the `bin` extension.
+
+By default, the following extensions are allowed:
+- **images:** `jpg`, `jpeg`, `png`, `gif`, `webp`, `avif`, `bmp`, `tif`, `tiff`, `ico`, `heic`, `heif`
+- **documents:** `pdf`, `txt`, `csv`, `rtf`, `json`, `epub`, `doc`, `docx`, `xls`, `xlsx`, `ppt`, `pptx`, `odt`, `ods`, `odp`
+- **archives:** `zip`, `rar`, `7z`, `gz`, `tgz`, `tar`, `bz2`
+- **audio:** `mp3`, `wav`, `ogg`, `oga`, `opus`, `m4a`, `aac`, `flac`, `weba`
+- **video:** `mp4`, `m4v`, `webm`, `mov`, `avi`, `mkv`, `mpeg`, `mpg`, `ogv`, `3gp`
+- **unidentified content:** `bin`
+
+Extensions that web servers may execute (`php`, `phtml`, `phar`, `shtml`, `pl`, `py`, `cgi`, `asp`, `jsp`, `sh`, `exe`, `htaccess` and similar) are **always** rejected, even if you add them to the allow list. They are checked in every part of the file name, so a name like `shell.php.jpg` is rejected too.
+
+**Changing the allowed extensions for all fields**
+
+Set the `allowed_upload_extensions` key in your `config/backpack/crud.php` file:
+
+```php
+use Backpack\CRUD\app\Library\Uploaders\Support\FileExtensions;
+
+'allowed_upload_extensions' => [...FileExtensions::DEFAULT_ALLOWED, 'dwg'],
+```
+
+If you published the config file before this option existed, you don't need to add it - Backpack will use the default list.
+
+**Changing the allowed extensions for one field**
+
+Pass `allowedExtensions` to the uploader configuration. It replaces the list from the config for that field, so you can use it both to restrict and to extend what is allowed:
+
+```php
+use Backpack\CRUD\app\Library\Uploaders\Support\FileExtensions;
+
+// only accept pdfs
+CRUD::field('invoice')->type('upload')->withFiles([
+ 'allowedExtensions' => ['pdf'],
+]);
+
+// the default extensions, plus svg
+CRUD::field('logo')->type('upload')->withFiles([
+ 'allowedExtensions' => [...FileExtensions::DEFAULT_ALLOWED, 'svg'],
+]);
+```
+
+> **IMPORTANT**: Only allow `svg`, `html`, `xml` or other file types that browsers can render when you trust everyone who can upload to that field, or when the files are served from a different domain than your admin panel (eg. a cloud disk). Otherwise, a crafted file can run scripts in the session of the admin who opens it.
+
+**What happens when a file is not allowed**
+
+The form is returned with a validation error for that field, just like any other validation rule. For fields inside a `repeatable` or a relationship, the error is reported on the subfield of the row that sent the file (eg. `gallery.2.photos`).
+
+All the files sent in the form are checked **before** any uploader stores or deletes files. So if one file is rejected, nothing is changed: the new files are not stored and the previous files of the entry are kept. This applies to all fields in the form, including fields inside repeatables and relationships, and to the [Spatie MediaLibrary uploaders](#spatie-media-library).
+
+The `dropzone` field (PRO) checks the files when they are sent to the dropzone upload endpoint, so files that are not allowed never reach the temporary folder, and the error is shown in the field. That endpoint uses the options of fields defined with the array syntax (`'withFiles' => [...]`); for fields defined with `->withFiles([...])` it uses the default file namer and the `allowed_upload_extensions` config.
+
+> **NOTE**: The allow list is a safety net, not a replacement for validation. You should still validate each upload field with the file types it expects, using the [Custom Validation Rules](https://backpackforlaravel.com/docs/{{version}}/custom-validation-rules).
### Handling uploads in relationship fields
@@ -116,8 +177,8 @@ public function categories() {
### Naming files when using Uploaders
Backpack provides a naming strategy for uploaded files that works well for most scenarios:
-- For `upload`, `upload_multiple` and `dropzone` fields, the file name will be the original file name slugged and with a random 4 character string appended to it, to avoid name collisions. Eg: `my file.pdf` becomes `my-file-aY5x.pdf`.
-- For `image` it will generate a unique name for the file, and will keep the original extension. Eg: `my file.jpg` becomes `5f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c.jpg`.
+- For `upload`, `upload_multiple` and `dropzone` fields, the file name will be the original file name slugged and with a random 4 character string appended to it, to avoid name collisions. The extension is detected from the file content. Eg: `my file.pdf` becomes `my-file-aY5x.pdf`.
+- For `image` it will generate a random name for the file, with the extension of the image type. Eg: `5f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c.jpeg`. Only `jpeg`, `png`, `gif`, `webp` and `avif` images are accepted, and the image content must match its type.
You can customize the naming strategy by creating a class that implements `FileNameGeneratorInterface` and pass it to the upload configuration (the default used by Backpack).
@@ -131,6 +192,13 @@ CRUD::field('avatar')->type('upload')->withFiles([
'fileNamer' => function($file, $uploader) { return 'the_file_name.png'; },
])
```
+
+The names returned by your file namer must end with an [allowed extension](#allowed-file-types), otherwise the file is rejected. Keep in mind that the original file name (`$file->getClientOriginalName()`) is sent by the user: if you use it, the extension the user picked is the one that gets checked, not the file content. Names without an extension are rejected too.
+
+The default name generator can be changed for all fields in the `file_name_generator` key of your `config/backpack/crud.php` file.
+
+> **NOTE**: If you create your own Uploader class, always name the files you store with `$this->getFileName($file)`: besides calling the configured `fileNamer`, it makes sure the file type is allowed. Call it for all new files **before** deleting or replacing any previous file, so a rejected upload doesn't remove the files the entry already has.
+
### Subfields in Uploaders
diff --git a/6.x/custom-validation-rules.md b/6.x/custom-validation-rules.md
index 86cfd6b9..0667fc6e 100644
--- a/6.x/custom-validation-rules.md
+++ b/6.x/custom-validation-rules.md
@@ -7,6 +7,8 @@
Some Backpack fields are more difficult to validate using standard Laravel validation rules. So we've created a few custom validation rules, that will make validation them dead-simple.
+> **NOTE**: When the field uses `withFiles` or `withMedia`, the uploaded files are also checked against a list of [allowed file types](/docs/{{version}}/crud-uploaders#allowed-file-types), after your validation rules pass. If your rules accept a type that is not in that list (eg. `mimes:svg`), you also need to allow its extension in the uploader configuration.
+
## `ValidUpload` for `upload` field type