Skip to content

Commit 798a669

Browse files
committed
Improve docs around when to use Media Library vs File Attachments
1 parent c2734e2 commit 798a669

4 files changed

Lines changed: 93 additions & 5 deletions

File tree

backend/forms.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,8 @@ Option | Description
10111011

10121012
> **NOTE:** Unlike the [Media Finder FormWidget](#media-finder), the File Upload FormWidget uses [database file attachments](../database/attachments); so the field name must match a valid `attachOne` or `attachMany` relationship on the Model associated with the Form. **IMPORTANT:** Having a database column with the name used by this field type (i.e. a database column with the name of an existing `attachOne` or `attachMany` relationship) **will** cause this FormWidget to break. Use database columns with the Media Finder FormWidget and file attachment relationships with the File Upload FormWidget.
10131013

1014+
> **Security:** File attachments are public by default. For sensitive files that require access control, set `'public' => false` in your model's attachment relationship configuration. See [Protected Attachments](../database/attachments#protected-attachments) for more information.
1015+
10141016
By default, the File Upload FormWidget only allows a limited set of file extensions. You can extend this list by adding a `fileDefinitions` config in `config/cms.php` file.
10151017
See [Allowed file types](../setup/configuration#allowed-file-types) for more information.
10161018

@@ -1086,6 +1088,8 @@ Option | Description
10861088

10871089
> **NOTE:** Unlike the [File Upload FormWidget](#file-upload), the Media Finder FormWidget stores its data as a string representing the path to the image selected within the Media Library.
10881090

1091+
> **Warning:** Files selected from the Media Library are **publicly accessible via direct URL** without any authentication or access control. Do not use the Media Finder for sensitive or private files. For files requiring access control, use the [File Upload FormWidget](#file-upload) with [Protected Attachments](../database/attachments#protected-attachments) instead.
1092+
10891093
### Nested Form
10901094

10911095
`nestedform` - renders a nested form as the contents of this field and returns the form data as an array.

cms/mediamanager.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ By default Media Manager works with the storage/app/media subdirectory of the in
66
77
Please note that after you change Media Manager configuration, you should reset its cache. You can do that with pressing the **Refresh** button in the Media Manager toolbar.
88

9+
> **Warning:** All files uploaded to the Media Library are **publicly accessible** via direct URL without any authentication. The Media Manager is designed for public assets like theme images, videos, and content that should be accessible to all website visitors. **Do not use the Media Library for sensitive files, user documents, or private content.** For files that require access control, use [File Attachments](../database/attachments) with the `public => false` option instead.
10+
911
## Configuring Amazon S3 access
1012

1113
To use Amazon S3 with Winter CMS, you should create S3 bucket, folder in the bucket and API user.
@@ -243,7 +245,7 @@ Parameter | Value
243245

244246
### Allowing more specific file extensions
245247

246-
By default, the Media Manager only allows a limited set of file extensions. You can extend this list by adding a `fileDefinitions` config in `config/cms.php` file.
248+
By default, the Media Manager only allows a limited set of file extensions. You can extend this list by adding a `fileDefinitions` config in `config/cms.php` file.
247249
See [Allowed file types](../setup/configuration#allowed-file-types) for more information.
248250

249251
## Events
@@ -279,6 +281,40 @@ Event::listen('media.file.rename', function ($widget, $originalPath, $newPath) {
279281
});
280282
```
281283

284+
## Security considerations
285+
286+
### Public access by default
287+
288+
All files stored in the Media Library are publicly accessible via direct URL. This means:
289+
290+
- **No authentication required**: Anyone who knows or can guess the file URL can access it
291+
- **No permission checks**: Backend user permissions do not affect access to media files
292+
- **Direct browser access**: Files can be accessed directly without going through Winter CMS
293+
294+
For local storage, media files are served from `/storage/app/media/` and configured in your web server to be publicly accessible (see [Server Configuration](../setup/configuration) for details).
295+
296+
### When to use Media Manager
297+
298+
The Media Manager is appropriate for:
299+
300+
- Theme assets (images, videos, fonts)
301+
- Public website content (blog images, product photos, downloadable brochures)
302+
- Files that are meant to be embedded in CMS pages or layouts
303+
- Content that should be accessible to all website visitors
304+
- Files that may be referenced from multiple places in your application
305+
306+
### When NOT to use Media Manager
307+
308+
**Do not use the Media Manager for:**
309+
310+
- User-uploaded documents that should be private
311+
- Files containing sensitive or confidential information
312+
- Content that requires authentication or authorization
313+
- User-specific files (profile pictures, personal documents)
314+
- Files subject to privacy regulations (GDPR, CCPA, etc.)
315+
316+
For these scenarios, use [File Attachments](../database/attachments) with the `public => false` option instead.
317+
282318
## Troubleshooting
283319

284320
The most common issue with using remote services is the SSL connection problem. If you get SSL errors, make sure that your server has fresh SSL certificates of public Certificate Authorities (CA).

database/attachments.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Winter CMS provides a few different ways to manage files depending on your needs
66

77
When using the `System\Models\File` model, you are able to configure the disk and paths that are used to store and retrieve the files managed by that model by modifying the `storage.uploads` setting in the [`config/cms.php` file](https://github.com/wintercms/winter/blob/develop/config/cms.php#L317).
88

9+
> **Warning:** File attachments are **public by default** and accessible via direct URL without authentication. To protect sensitive files, you **must explicitly set** `'public' => false` in your attachment configuration. For truly public content that doesn't need database tracking (like theme assets), consider using the [Media Manager](../cms/mediamanager) instead.
10+
911
## File attachments
1012

1113
Models can support file attachments using a subset of the [polymorphic relationship](../database/relations#polymorphic-relations). The `$attachOne` or `$attachMany` relations are designed for linking a file to a database record called "attachments". In almost all cases the `System\Models\File` model is used to safekeep this relationship where reference to the files are stored as records in the `system_files` table and have a polymorphic relation to the parent model.
@@ -30,17 +32,40 @@ public $attachMany = [
3032

3133
> **NOTE:** If you have a column in your model's table with the same name as the attachment relationship it will not work. Attachments and the FileUpload FormWidget work using relationships, so if there is a column with the same name present in the table itself it will cause issues.
3234
33-
Protected attachments are uploaded to the File Upload disk's **uploads/protected** directory which is not accessible for the direct access from the Web. A protected file attachment is defined by setting the *public* argument to `false`:
35+
### Protected attachments
36+
37+
Protected attachments provide access control for sensitive files. Unlike public attachments, protected files cannot be accessed via direct URL and require backend authentication.
38+
39+
**Key differences between public and protected attachments:**
40+
41+
- **Storage location**: Protected files are stored in `storage/app/uploads/protected/` instead of `storage/app/uploads/public/`
42+
- **Web server access**: The protected directory is not configured for direct web access in server configuration
43+
- **Authentication**: Protected files can only be accessed by authenticated backend users with appropriate permissions
44+
- **URL access**: Protected files are served through Winter CMS's protected file route, not directly from the filesystem
45+
46+
**When to use protected attachments:**
47+
48+
- User-uploaded documents containing sensitive information
49+
- Files subject to privacy regulations (GDPR, CCPA, etc.)
50+
- Content that requires authentication or authorization
51+
- User-specific files (contracts, invoices, medical records)
52+
- Any file that should not be publicly accessible
53+
54+
**Defining a protected attachment:**
55+
56+
Set the `public` argument to `false` in your attachment configuration:
3457

3558
```php
3659
public $attachOne = [
37-
'avatar' => [
60+
'confidential_document' => [
3861
\System\Models\File::class,
3962
'public' => false,
4063
],
4164
];
4265
```
4366

67+
For comparison with other file management approaches, see the [Media Manager security considerations](../cms/mediamanager#security-considerations).
68+
4469
### Creating new attachments
4570

4671
For singular attach relations (`$attachOne`), you may create an attachment directly via the model relationship, by setting its value using the `Input::file` method, which reads the file data from an input upload.
@@ -74,17 +99,19 @@ foreach ($files as $file) {
7499
}
75100
```
76101

77-
Alternatively, you can prepare a File model before hand, then manually associate the relationship later. Notice the `is_public` attribute must be set explicitly using this approach.
102+
Alternatively, you can prepare a File model before hand, then manually associate the relationship later. When creating files this way, the `is_public` attribute must be set explicitly, as it won't automatically inherit from the attachment relationship configuration.
78103

79104
```php
80105
$file = new System\Models\File;
81106
$file->data = Input::file('file_input');
82-
$file->is_public = true;
107+
$file->is_public = true; // Set to false for protected files
83108
$file->save();
84109

85110
$model->avatar()->add($file);
86111
```
87112

113+
> **Note:** Set `is_public` to `true` for publicly accessible files or `false` for protected files that require authentication. When using the relationship's `create()` method (shown earlier), the `is_public` value is automatically determined from the attachment configuration's `public` option.
114+
88115
You can also add a file from a URL. To work this method, you need install cURL PHP Extension.
89116

90117
```php

setup/configuration.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ location ~ ^/humans\.txt { try_files $uri /index.php; }
6868
location ~ /\.(?!well-known).* { deny all; }
6969
7070
## Let nginx return 404 if static file not exists
71+
## These paths expose PUBLIC file storage directories:
72+
## - /storage/app/uploads/public: Public file attachments (attachments with 'public' => true)
73+
## - /storage/app/media: Media Manager files (always public, no access control)
74+
## - /storage/app/resized: Dynamically resized images (public)
75+
## - /storage/temp/public: Public temporary files
76+
## NOTE: /storage/app/uploads/protected is intentionally NOT listed here - those files
77+
## require authentication and are served through Winter CMS, not directly by nginx
7178
location ~ ^/storage/app/uploads/public { try_files $uri 404; }
7279
location ~ ^/storage/app/media { try_files $uri 404; }
7380
location ~ ^/storage/app/resized { try_files $uri 404; }
@@ -123,6 +130,12 @@ Paste the following code in the editor and change the **host address** and `ser
123130
$HTTP["host"] =~ "domain.example.com" {
124131
server.document-root = "/var/www/example/"
125132
133+
# Public file storage paths (publicly accessible without authentication):
134+
# - /storage/app/uploads/public: Public file attachments
135+
# - /storage/app/media: Media Manager files (always public)
136+
# - /storage/app/resized: Dynamically resized images
137+
# - /storage/temp/public: Public temporary files
138+
# Note: /storage/app/uploads/protected is NOT exposed here
126139
url.rewrite-once = (
127140
"^/(plugins|modules/(system|backend|cms))/(([\w-]+/)+|/|)assets/([\w-]+/)+[-\w^&'@{}[\],$=!#().%+~/ ]+\.(jpg|jpeg|gif|png|svg|swf|avi|mpg|mpeg|mp3|flv|ico|css|js|woff|ttf)(\?.*|)$" => "$0",
128141
"^/(system|themes/[\w-]+)/assets/([\w-]+/)+[-\w^&'@{}[\],$=!#().%+~/ ]+\.(jpg|jpeg|gif|png|svg|swf|avi|mpg|mpeg|mp3|flv|ico|css|js|woff|ttf)(\?.*|)$" => "$0",
@@ -146,6 +159,14 @@ If your webserver is running Internet Information Services (IIS) you can use the
146159
<system.webServer>
147160
<rewrite>
148161
<rules>
162+
<!--
163+
Public file storage paths (publicly accessible without authentication):
164+
- /storage/app/uploads/public: Public file attachments
165+
- /storage/app/media: Media Manager files (always public)
166+
- /storage/app/resized: Dynamically resized images
167+
- /storage/temp/public: Public temporary files
168+
Note: /storage/app/uploads/protected is NOT exposed here
169+
-->
149170
<rule name="Winter CMS to handle all non-whitelisted URLs" stopProcessing="true">
150171
<match url="^index.php" negate="true" />
151172
<conditions logicalGrouping="MatchAll">

0 commit comments

Comments
 (0)