ActiveStorage Direct Uploads Safe by Default/How to make it safe?

I’m looking to use ActiveStorage Direct Uploads in a project and I’m struggling to understand how the default configuration is safe.

As I read it, any user can create a new direct upload by calling DirectUploadController#create. They then receive a URL and/or authentication information for uploading the contents of the blob to your storage service. Couldn’t someone simply keep making calls to DirectUploadController#create and keep uploading more and more blobs to the storage service and never attaching any of those blobs?

Is that safe? Is there a risk of a user getting a super large bill from say S3 because a script kept sending blobs? Or maybe this is a similar level of risk as someone creating new database records on a rails site as well, I’m not sure.

Any thoughts on this? Am I looking at this problem totally wrong or is there something I’m not seeing?

4 Likes

You’re not wrong. Active Storage’s built-in controllers are either unauthenticated or protected only by weak signature-based authentication:

  • ActiveStorage::BlobsController: Signature-based authentication, permanent signatures
  • ActiveStorage::RepresentationsController: Signature-based authentication, permanent signatures
  • ActiveStorage::DirectUploadsController: Unauthenticated (:warning:)
  • ActiveStorage::DiskController: Signature-based authentication, short-lived signatures

In a production app, you’ll likely need to authenticate, validate, rate-limit, and otherwise protect storage access more stringently. That means you’ll need to bring your own controllers.

We have warnings to this effect non-exhaustively peppered throughout the API documentation:

If you need to enforce access protection beyond the security-through-obscurity factor of the signed blob references, you’ll need to implement your own authenticated redirection controller.

We can more consistently/clearly/loudly document this—and PRs are welcome for that—but I don’t feel great about the current state of things regardless. We’re trading production safety for Fisher-Price simplicity. I don’t have a solution in mind at the moment.

/cc @bitsweat @DHH

2 Likes

Thanks for helping me understand it. I don’t think it’s helped by the fact that:

  • You can’t prevent drawing the default routes for ActiveStorage, until 6.1
  • There doesn’t seem to be a well documented way to override some of the default ActiveStorage routes.

For my use-case, I don’t really care that much about the authentication of the other controllers but I don’t like ActiveStorage::DirectUploadsController being unauthenticated.

I say all this without a good answer on how to fix the problem and still make it easy to get started AND not assume a particular authentication system or design.

2 Likes

I was looking into finally implementing AS into one of our apps, decided to read a bit and get familiar with it and this topic blew my mind a bit.

Generally ActiveStorage engine works a lot like a closed black box in most cases. It is convenient and handles complex and cumbersome tasks beautifully but it lacks any documented customization options. And to be honest, lack of structured way to inject any form of authentication into controllers smells like a ticking bomb to me.

I can imagine that there is now a lot of production apps in the world with this controllers endpoints exposed without much concern. Why? Because this matter should be mentioned in huge black letters on top of the README and Guide pages. I know app developers should consider this when developing but people are lazy and I can bet my hair on the fact that huge set of devs assumed that implementing for example devise will cover AS controllers by default as well.

So my suggestion would be to add information about this to documentation ASAP util this matter is resolved in any way. I can open a PR later tomorrow for if you want.

Back to the matter at hand easiest option to solve this I can think of is to add the ability to easy register before_action to those controllers or customize the class ActiveStorage::BaseController inherits from and add authentication in this class.

Either way I strongly believe this should be documented in some way and AS documentation in general is not up to what this engine provides and can do for you now.

2 Likes

Is there a way to make this protected in a roubust way? Or should the ActiveStorage::DirectUploadsController be patched with a before_action method?

Is there any progress on this one? My current attempt has been to override these controllers by copying them into the main app but this doesn’t seem to work. I assume it’s probably because I tweaked the load order. If we could configure where the routes pointed that would make a huge difference. I might look at implementing that as a configuration.

Patching seems only a quick fix, I think it needs a better solution. I have not tried patching yet so from my point no progress.

Yes I think the main problem here is that ActiveStorage and ActionText have wandered into the opinionated app space. In some ways they should probably be external gems with more configurable routing options, or the option to draw routes into the main app manually with arguments.

I guess in my case I’ll probably end up disabling the default drawing of the routes (ActiveStorage.draw_routes = false) and copy these into my app with modifications. That way I can disable the direct upload routes that I’m not using but that I suspect allow the arbitrary creation of empty blob objects (I haven’t looked too deeply). I can then also point the blob and representation urls to my own controller and just mix in the rails concerns.

We alreary enable to create a new controller and using before_action. However, I am unable to use a bearer token authentication due to the limitations of the direct upload behavior, which does not allow customization of headers.

To address this issue, I devoted some time to resolving it through Rails on API-only mode and a completely separate frontend. Through the utilization of custom headers, I was able to create a new pull request that enables other forms of authentication. You can check out the details here: Safe for Direct Uploads in js Libraries or Frameworks by Roriz · Pull Request #47773 · rails/rails · GitHub

As for the default route, we have deactivated it as mentioned in previous responses.