---
url: /guide/security.md
description: >-
  How Media Library Pro protects uploads, URL imports, private files and
  Livewire state, what it leaves to you, and how to report a vulnerability.
---

# Security

This page lists what Media Library Pro checks, so you can judge what to add on top. Each statement matches the shipped code and is covered by the package's test suite.

## Authorization

Every action goes through Laravel policies. `MediaItemPolicy` and `MediaFolderPolicy` are registered unless your app defines its own for the models.

* Shared files are open to every user who can reach the library. Files marked **Only me** are limited to their uploader and to users who pass the `viewPrivate` ability.
* Uploads need `create`. Replacing a file needs `update` on that file.
* The library page and the picker modal need `viewAny`. The panel plugin's `canAccess()` also gates the page and the upload routes.
* Deleting a folder that holds someone else's private content, or moving a folder into one the user cannot see, is refused.
* Folders given to the picker with `folder()` are only resolved when the user can see them.

See [Authorization](/reference/authorization) for every ability and how to override it.

## Uploads

Uploads arrive in chunks, and the server checks each stage.

| Check | What it does |
|---|---|
| Extension | Before any data is stored, the file name must have a plain extension that is not on `upload.blocked_extensions`. Trailing dots and spaces are trimmed, so `shell.php.` is still blocked. Files with no extension are refused. |
| Content sniffing | After assembly, the file's real MIME type is detected from its bytes with `finfo`. It must match `upload.accepted_mime_types`, so a renamed executable does not pass as `photo.jpg`. |
| Size | The declared and assembled size must be within `upload.max_file_size`. |
| Quota | The upload must fit the remaining [quota](/guide/tenancy). Previous versions count towards it. |
| File names | Names are slugged by default. Inner extensions are never kept, so `shell.php.png` is stored as `shell-php.png`. |
| SVG | With `upload.sanitize_svg` on (the default), scripts, event handlers and remote references are removed. An SVG that cannot be cleaned is refused. |

The default `blocked_extensions` list covers server scripts (`php`, `phtml`, `phar`, `py`, `rb`, `jsp`, `asp`), executables and shell files (`exe`, `sh`, `bat`, `ps1`, `jar`), and browser-renderable files (`html`, `htm`, `xhtml`, `js`, `mjs`, `xml`, `xsl`, `svgz`, `swf`). Edit it in `config/filament-media-library.php`.

### Chunk handling

* The first chunk must come first. A chunk that skips it is refused and nothing is written.
* Every later chunk must match the file name, total size and chunk count declared by the first.
* A chunk may not be larger than the configured chunk size, and the parts together may not exceed the declared size.
* Each person can have at most 20 unfinished uploads. Finished uploads and duplicates do not count.
* Chunks are kept in a folder per user. Folders untouched for 24 hours are removed by `media-library:prune`.
* The upload routes require an authenticated user and are throttled to 600 requests a minute.

## URL import

Import from URL downloads a file from a link a user types, so it is guarded against server-side request forgery.

* Only `http` and `https` links are accepted.
* The host is resolved first. Every address it resolves to must be public. Loopback, private ranges, link-local addresses (including cloud metadata addresses), carrier-grade NAT, IPv4-mapped and NAT64 IPv6 addresses, and documentation ranges are refused.
* The request connects to the address that was checked (DNS pinning), so a second DNS answer cannot redirect it.
* Redirects are followed manually, up to three times, and every hop is checked again.
* The download has a 30 second timeout and stops as soon as it passes `upload.max_file_size`.
* The downloaded file then goes through the same checks as any upload.

## Links and storage paths

* Files are stored under a random UUID folder, so URLs cannot be guessed from sequential ids.
* Private files are never given a permanent link. They get short-lived signed links, and the serve route refuses a signed link without an expiry for a private file.
* The serve route refuses trashed files and invalid signatures, and sends `X-Content-Type-Options: nosniff` and a restrictive `Content-Security-Policy`.
* The image editor receives a fresh link, and signed links are never altered after signing.
* Rich text embeds use non-expiring signed links for non-public disks, and never for private files. See [Rich Editor](/guide/rich-editor).

Signed links depend on your `APP_KEY`. Keep it secret.

## Tenants and private files

* Folders, files and tags are filtered by tenant on every query when tenancy is on. Picker validation, attachments and the rich editor reject items from another tenant. See [Tenancy](/guide/tenancy).
* Duplicate detection ignores files from other tenants.
* Private files are filtered out of picker validation for users who cannot see them. See [Private Files](/guide/private-files).

## Livewire state

The library and the picker lock their configuration with Livewire's `#[Locked]` attribute: the folder, page size, active file, accepted types, selection limits, layout and the rest. A visitor who edits that state in the browser gets an exception. Sort order goes through a validated method, and tag names typed by users are treated as names, never as ids.

## Standalone mode

Using the library outside Filament panels is off by default. Until you enable `standalone.enabled`, the components return 403 and the upload and download routes return 404. See [Outside Panels](/guide/outside-panels). Before you enable it, register a policy that fits your users.

## What we do not do

* **No virus scanning.** Files are checked by type and content sniffing, not scanned for malware. If you need it, scan chunks or stored files with your own pipeline, for example on the `MediaItemUploaded` event.
* **No EXIF stripping.** Photos keep the metadata they were uploaded with, including location data. The inspector shows a short list of camera fields, but the file on disk is unchanged.
* **No protection for public disks.** On a public disk the file sits in a web-readable folder. Anyone with the exact URL can open it, even if the file is marked **Only me**. Use a private disk for confidential files. See [Storage](/guide/storage).
* **No content moderation.** The library does not look at what an image shows.
* **No policy for you.** The default policy is permissive for signed-in users. Write your own for any site where users are not all trusted.

## Reporting a vulnerability

Email `security@hoceine.com` with a description, the version you tested and steps to reproduce. Please do not open a public issue or post details until a fix is available.
