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:

  1. Who is allowed to upload the file?
  2. Who is allowed to access the file?
  3. 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:

Code
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:

Code
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:

Code
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:

Code
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:

Code
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:

Code
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:

Code
Signed URL = User is authorized

A better mental model is:

Code
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:

Code
blob = ActiveStorage::Blob.find(params[:id])

use a business-level lookup such as:

Code
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:

Code
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:

Code
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:

Code
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:

Code
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:

Code
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:

Code
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:

YAML
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:

Code
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:

Code
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:

Code
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

MistakeRiskBetter approach
Treating signed URLs as authorizationUnauthorized accessAuthorize before generating access
Exposing blob IDs directlyStorage-level access patternsUse business records
Trusting MIME typeMalicious or unexpected contentValidate and inspect content where required
Allowing unlimited uploadsStorage exhaustionEnforce file-size limits
Making all storage publicSensitive files become accessibleSeparate public and private content
Processing every uploaded fileRisk through processing toolsValidate and scan before processing
Using user filenames in shell commandsCommand injection riskUse safe APIs and avoid shell interpolation
Skipping authorization in download actionsIDOR-style access issuesCheck ownership or permissions
Giving storage credentials excessive permissionsLarger security impactApply least privilege

Security Checklist for Active Storage

Before deploying an application that handles sensitive attachments, verify the following:

  1. Authentication is required for private file access.
  2. Authorization is checked before generating download access.
  3. Users cannot access arbitrary attachment records.
  4. Signed URLs are not treated as a substitute for authorization.
  5. Upload size limits are enforced.
  6. Allowed file types are restricted.
  7. User-controlled filenames are handled safely.
  8. Sensitive uploads are scanned when required.
  9. Public and private files use appropriate storage policies.
  10. Storage credentials follow least-privilege principles.
  11. Image and document processing dependencies are maintained.
  12. Existing attachments are reviewed after a security change.
  13. Download and upload actions are monitored where appropriate.
  14. 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:

JavaScript
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:

JavaScript
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.