File uploads are common in Rails applications. Users may upload profile pictures, invoices, documents, reports, videos, or other business files. Rails Active Storage makes this workflow easier by handling file attachments, storage services, and download URLs.
However, adding Active Storage does not automatically make uploaded files secure.
A common mistake is to focus only on whether a file is stored safely while ignoring who can access it, how download URLs are generated, and whether uploaded content is trusted by the application.
A secure Active Storage implementation should answer three questions:
- Who is allowed to upload the file?
- Who is allowed to access the file?
- What should happen when the uploaded file is processed or downloaded?
This article explains practical security issues in Rails Active Storage applications and how to address them.
Understanding How Active Storage Handles Files
Active Storage separates application records from the actual uploaded files.
A typical model might look like this:
class User < ApplicationRecord
has_one_attached :avatar
end
The database stores attachment metadata while the actual file is stored using a configured service such as local storage or an object storage provider.
A simplified flow looks like this:
User Upload
|
v
Rails Application
|
v
Active Storage Attachment
|
v
Storage Service
|
v
Blob / File
Active Storage also creates signed identifiers and signed URLs for accessing blobs.
The important security point is that signing a URL or identifier protects against arbitrary modification of that value, but it does not replace application-level authorization.
Your application still needs to decide whether the current user should receive access to a particular file.
Keep Private Files Private
One of the most common security problems is accidentally treating private business files as public assets.
For example, an application may store employee documents, invoices, identity documents, or customer reports. These files should generally not be accessible simply because someone knows or obtains a file URL.
Do not assume that an Active Storage URL means the file is automatically protected according to your application's authorization rules.
A safer design is:
User requests file
|
v
Check authentication
|
v
Check authorization
|
v
Generate access URL / redirect
|
v
Download file
The authorization decision should happen before the application provides access to the file.
Add Authorization Before Serving Attachments
Consider an application where users can upload documents.
A model might contain:
class Document < ApplicationRecord
belongs_to :user
has_one_attached :file
end
A dangerous controller pattern is to locate the attachment without checking ownership or permissions.
Instead, check the relationship first:
class DocumentsController < ApplicationController
before_action :authenticate_user!
def download
document = current_user.documents.find(params[:id])
redirect_to document.file.url
end
end
The important part is not the redirect itself. It is the authorization boundary before the URL is generated.
For applications using an authorization library, the same principle applies:
def download
document = Document.find(params[:id])
authorize! :read, document
redirect_to document.file.url
end
The exact authorization mechanism depends on the application, but the security boundary should remain the same.
Do Not Treat Signed URLs as Authorization
Active Storage uses signed values to prevent users from arbitrarily changing references.
That solves a different problem from authorization.
For example, suppose a user receives a valid signed URL for a private document. The URL may remain usable according to the storage and routing configuration until it expires or becomes invalid.
Therefore, this assumption is unsafe:
Signed URL = User is authorized
A better mental model is:
Signed URL
|
+-- Protects the integrity of the reference
|
+-- Does not decide business authorization
Your Rails application should determine whether the user is allowed to access the attachment before providing the access mechanism.
Avoid Exposing Blob Identifiers Directly
Active Storage blob identifiers should not be treated as a replacement for authorization.
For example, an application should avoid building a download endpoint where a user can provide an arbitrary blob identifier and receive the corresponding file without checking ownership.
Prefer going through the application's business record.
Instead of:
blob = ActiveStorage::Blob.find(params[:id])
use a business-level lookup such as:
document = current_user.documents.find(params[:id])
blob = document.file.blob
This makes the authorization relationship much clearer.
The user is requesting a document they are allowed to access, rather than directly requesting an arbitrary storage object.
Validate Uploaded Files
File extensions and browser-provided content types should not be considered sufficient security controls.
For example, a request might claim that a file is:
image/jpeg
while the actual content is something completely different.
Applications should validate uploaded files according to their business requirements.
A basic Active Storage validation can look like this:
class Document < ApplicationRecord
has_one_attached :file
validates :file,
content_type: [
"application/pdf",
"image/png",
"image/jpeg"
],
size: { less_than: 10.megabytes }
end
Validation should cover at least:
- Allowed content types
- Maximum file size
- Required attachments
- Business-specific file restrictions
However, validation alone should not be considered malware protection.
Remember That Active Storage Is Not an Antivirus
Active Storage manages file storage and attachments. It is not an antivirus engine or a complete file sanitization system.
If users can upload untrusted documents, consider adding malware scanning before the file is made available for sensitive workflows.
A production upload flow might look like this:
Upload
|
v
Validate size/type
|
v
Store safely
|
v
Security scan
|
v
Mark as approved
|
v
Allow normal application processing
For applications handling sensitive files, the scan result should become part of the application's state.
For example:
enum :scan_status, {
pending: 0,
clean: 1,
rejected: 2
}
The application can then prevent processing or downloading files that have not passed the required security checks.
Be Careful With Content-Type and Filenames
A filename is user-controlled data.
Never use the uploaded filename as though it were trusted application data.
For example, do not build operating-system commands directly from a filename.
Avoid patterns like:
system("convert #{attachment.filename}")
A filename may contain spaces, shell characters, or unexpected values.
Use APIs that accept file paths safely instead of constructing shell commands from user input.
Also remember that content type metadata can be manipulated. If your application performs sensitive processing based on file type, use appropriate content inspection and security controls rather than trusting metadata alone.
Secure Direct Uploads
Active Storage supports direct uploads where the browser uploads the file to the storage service without sending the entire file through the Rails application.
This can reduce application-server load, but it does not remove the need for validation and authorization.
A secure direct-upload flow should still consider:
Authenticated User
|
v
Application authorizes upload
|
v
Upload request is created
|
v
Browser uploads file
|
v
Application validates attachment
|
v
File becomes usable
Do not assume that because the file goes directly to object storage, the upload is trusted.
The application still needs to control who can create attachments and what those attachments are allowed to contain.
Review Storage Service Configuration
Your storage configuration should match the sensitivity of the files.
For example, development might use local storage:
local:
service: Disk
root: <%= Rails.root.join("storage") %>
Production may use an object storage service.
The important question is not simply which provider is being used. The important question is whether the storage configuration matches the application's access requirements.
For sensitive files, review:
- Public versus private storage
- Bucket or container permissions
- Access policies
- Encryption settings
- Credential permissions
- URL expiration behavior
- Logging and monitoring
Do not give the Rails application broader storage permissions than it actually needs.
Do Not Make Sensitive Attachments Public
Public storage can be appropriate for genuinely public content such as published images or downloadable assets.
It becomes dangerous when the same configuration is used for private business documents.
Separate public and private use cases when necessary.
For example:
Public images
-> Public delivery model
Private invoices
-> Authorized application access
Employee documents
-> Authorized application access
Customer reports
-> Authorized application access
The security model should be based on the business sensitivity of the data.
Protect Attachment Download Controllers
A dedicated controller is often useful when the application needs detailed authorization rules.
For example:
class DocumentsController < ApplicationController
before_action :authenticate_user!
def download
document = current_user.documents.find(params[:id])
return head :not_found unless document.file.attached?
redirect_to document.file.url
end
end
This approach gives the application a clear place to perform:
- Authentication
- Authorization
- Attachment existence checks
- Audit logging
- Business rules
It also avoids exposing a generic endpoint that accepts arbitrary storage references.
Be Careful With Image Processing
Image attachments may be processed into thumbnails or other variants.
Processing untrusted files increases the importance of keeping image-processing dependencies updated and limiting the types of files that can reach those processors.
A safer workflow is:
Upload
|
v
Validate
|
v
Security scan
|
v
Process
|
v
Serve
Do not process every uploaded file simply because the browser says it is an image.
Audit Existing Attachments
When fixing an existing Active Storage security issue, do not only update the code.
Review the existing data.
Look for:
- Attachments belonging to deleted records
- Unexpected public files
- Files accessible without the expected authorization
- Old temporary files
- Oversized uploads
- Unsupported file types
- Storage objects that are no longer referenced
- Application logs containing sensitive download information
Security improvements are incomplete if historical data remains exposed.
Common Active Storage Security Mistakes
| Mistake | Risk | Better approach |
|---|
| Treating signed URLs as authorization | Unauthorized access | Authorize before generating access |
| Exposing blob IDs directly | Storage-level access patterns | Use business records |
| Trusting MIME type | Malicious or unexpected content | Validate and inspect content where required |
| Allowing unlimited uploads | Storage exhaustion | Enforce file-size limits |
| Making all storage public | Sensitive files become accessible | Separate public and private content |
| Processing every uploaded file | Risk through processing tools | Validate and scan before processing |
| Using user filenames in shell commands | Command injection risk | Use safe APIs and avoid shell interpolation |
| Skipping authorization in download actions | IDOR-style access issues | Check ownership or permissions |
| Giving storage credentials excessive permissions | Larger security impact | Apply least privilege |
Security Checklist for Active Storage
Before deploying an application that handles sensitive attachments, verify the following:
- Authentication is required for private file access.
- Authorization is checked before generating download access.
- Users cannot access arbitrary attachment records.
- Signed URLs are not treated as a substitute for authorization.
- Upload size limits are enforced.
- Allowed file types are restricted.
- User-controlled filenames are handled safely.
- Sensitive uploads are scanned when required.
- Public and private files use appropriate storage policies.
- Storage credentials follow least-privilege principles.
- Image and document processing dependencies are maintained.
- Existing attachments are reviewed after a security change.
- Download and upload actions are monitored where appropriate.
- Temporary and abandoned files have a retention strategy.
Testing Active Storage Security
Security testing should verify both allowed and denied access.
For example, a request test can confirm that an authenticated user cannot download another user's document:
test "cannot download another user's document" do
other_user = users(:two)
document = documents(:private_document)
sign_in users(:one)
get download_document_path(document)
assert_response :not_found
end
Also test valid access:
test "can download own document" do
document = documents(:private_document)
sign_in document.user
get download_document_path(document)
assert_response :redirect
end
The exact testing approach depends on the authentication and authorization stack, but both positive and negative authorization cases should be covered.
Troubleshooting Common Problems
Users Receive Unauthorized File Access
Check whether the download endpoint performs an authorization check before generating the Active Storage URL.
Do not rely only on the fact that the URL is signed.
Valid Users Cannot Download Files
Check:
- The attachment exists
- The storage service is reachable
- The user's authorization rules allow access
- The storage credentials are valid
- The URL has not expired
- The storage service permissions are correct
Uploaded Files Are Rejected
Review:
- Content-type validation
- File-size limits
- Required attachment rules
- Client-side upload behavior
- Direct-upload configuration
Do not remove security validation simply because some legitimate files are being rejected. Adjust the allowed policy based on the application's actual requirements.
Old Files Are Still Accessible
Changing application authorization does not automatically mean every previously generated access URL has disappeared.
Review existing storage objects, previously exposed URLs, and the application's access paths as part of the remediation process.
Best Practices
Use Business-Level Authorization
Authorize the resource that owns the attachment instead of treating the blob as the primary application resource.
Keep Upload Rules Explicit
Define supported file types and maximum sizes clearly.
Separate Public and Private Content
Do not use one broad storage policy for every attachment in the application.
Scan Sensitive Uploads
If your application accepts untrusted documents or files, integrate appropriate malware scanning before sensitive processing or distribution.
Use Least Privilege
Storage credentials should have only the permissions required by the application.
Test Denied Access
A security test suite should verify that users cannot access files belonging to other users or tenants.
Review Historical Data
Security fixes should include existing attachments, not just future uploads.
Advantages of a Secure Active Storage Design
A properly secured Active Storage implementation provides:
- Clear ownership and authorization boundaries
- Better protection for private documents
- Controlled upload sizes and file types
- Reduced risk from untrusted files
- Easier security testing
- Better separation between application data and storage
- More predictable file access behavior
Disadvantages and Considerations
Security controls also introduce additional work:
- File validation requires maintenance
- Malware scanning can increase processing time
- Private downloads require authorization logic
- Storage policies need careful configuration
- Large files require additional monitoring
- Security testing must cover multiple access paths
These costs are generally easier to manage when security rules are designed as part of the upload architecture rather than added after an incident.
Summary
Active Storage simplifies file uploads in Rails, but it does not automatically define who is allowed to access those files.
The most important security boundary is application-level authorization. A valid signed identifier or URL should not be confused with permission to access a business resource.
A secure implementation should authorize users before generating download access, validate uploaded files, enforce size limits, handle filenames safely, use appropriate public or private storage policies, and consider malware scanning for untrusted content.
For existing Rails applications, start by reviewing upload endpoints, attachment ownership, download controllers, storage configuration, direct uploads, and previously exposed files.
The goal is not simply to store files securely. The goal is to make sure the right users can upload, access, process, and download the right files at every stage of the application lifecycle.
Join the conversation! Your thoughts help the community grow.