Secure Media Uploads

The secure media implementation is not compatible with Backblaze. Do they support pre-signed URLs?

1 Like


Yes, pre-signed URLs are supported: Does the B2 S3 Compatible API support Pre-Signed URLs? – Backblaze Help


Yes, changing the bucket from public to private does this. I did this and it allowed me to upload, but as an anon I couldn’t view topics.


@martin thanks for the clarification. And I can confirm that with leaving the setting on the S3 as in my screenshot as Public and everything unchecked (and suggested by you) that the custom avatars / profile images are finally working while the topic secured uploads remain working as well. Thank you!


I merged this PR this week and I am adding these details to the OP:

If you want to allow embedding secure images in emails you can configure these site settings:

  • secure_media_allow_embed_images_in_emails : If enabled we will embed secure images in emails instead of redacting them.
  • secure_media_max_email_embed_image_size_kb : The cap to the size of the secure image we will embed, defaulting to 1mb, so the email does not become too big. Max is 10mb. Works in tandem with email_total_attachment_size_limit_kb .

The secure images will be added as email attachments and embedded using the cid: url format because base64 URL support in email clients is still flaky.

If you don’t have secure_media_allow_embed_images_in_emails enabled, or if the images start to exceed the size limits, then this is what you will see in place of secure images (also secure audio and video which is not embedded):


Further addendum to my previous post; from this PR:

We now enable secure media image embedding by default.


After setting up secure media uploads (following the OP guide) everything works well (attachments, images…) except uploads that are not topic attachments (like site logo, profile avatar) those raise “Access Denied” pop up in Discourse.
Did i mess up some setting?

Please read the topic, your issue is addressed a few posts above this one.


I read everything carefully. Please let me know what exactly you mean by few posts above. I don’t see this issue.
Trying to read between the lines here, i see that this thread used to be longer but some key replies were deleted from it, i see references to replies that don’t exist and mentions of screenshots that don’t appear in the thread.
If I understand correctly though, the recommendation is to set S3 bucket to NOT block any public access at all. I just want to confirm that, and ask if it is safe?

1 Like


The original post is indeed gone but the problem and solution are still there


I noticed a bug with using secure media and Knowledge Base Plugin
Links to attachments in the knowledge base fail to open (redirect to 404 page) unless forced to be opened in a new window.
The extra weird thing is that the very same attachment can be opened without issue from the Discourse topic associated with the knowledge base item.

The same bug happens when someone is copying a link to an attachment from one reply to another. the link is the exact same one naturally, but it only works from the original reply, unless forced to be opened in a new window.

1 Like

What about secure media if s3 is not used?
Currently it looks like that, if you have the direct link to the uploaded file, you can download that file without login. Which is like a security issue then…

Secure media requires s3.
This entire feature was developed to make it impossible to share and access direct links.


This is an excellent feature. Thank you for developing it. Minio, a free drop-in replacement for S3, has no support for ACLs and never will. Their argument, which is a good one, is that ACL are not a useful feature and negatively affect performance because they require a second write operation. Secure media uploads will work with Discourse - the uploads:secure_upload_analyse_and_update task will error out on the final step but it seems you can ignore it. That said, is there any reason for Discourse to make ACL calls at all?


Yes, because the S3 bucket is private based on the setup in the OP, any non-secure uploads need to have their ACL set to public, and secure uploads will have a private ACL so the S3 URL can be accessed directly. I do not think we have plans at this time to modify how this works; I think there would be quite a bit of work involved to avoid using ACLs altogether for S3 replacements.


For a completely private forum (no anonymous access), the lack of a secure media option in the default local storage configuration seems like a huge oversight to me. I wouldn’t be surprised if a lot of folks running private forums don’t realize that all of their post attachments are publicly accessible (if you know the URL). If my post’s text can’t be accessed anonymously, intuitively I would think the post’s attachments can’t be either.

Is it worth submitting an issue requesting this to see what sort of support it would get? Does one already exist? This one issue is currently a deal breaker for my small, poor group who is trying to spin up Discourse in an environment where we own the hosting hardware already and don’t want to pay for cloud storage (particularly at AWS S3 prices) just to make sure our media isn’t viewable anonymously.

Also, forgive the potential naivete of this question. But there is already support for distinct buckets for uploads and backups, right? Would it be difficult to support a third bucket for secure uploads? Public uploads go in the public bucket, secure uploads go in the secure bucket, and no individual object ACLS are needed. And if the security status of an object changes, it could just be moved between buckets?


I think it’s quite a bit of work. At least a day’s work, but maybe a week?

Cdck hosting, which pays for development, is run with uploads on S3, so it’s only login required self hosted sites without budget for S3 that’s interested.

If your users see sharing links to your assets they could just as well be downloading them to share them. It doesn’t seem like a big problem.


I’m not crazy pants for thinking it’s weird that post text is protected but attachments are not though, right?

There’s a big difference between intentional and accidental sharing though. Obviously, there’s no real way to prevent the former. But the latter can happen right now simply because people don’t understand the underlying tech. Consider email notifications for posts that include links to attached images getting innocently forwarded to non-members. An option to redact attachments in emails that’s not tied to the secure media functionality might be a good alternative there.

Even when people do intentionally share attachment links, they might have simply forgotten that it was from a protected category. But in an ideal world, the attachment link should be worthless to anyone without access to that category.

A current or future bug in one of the S3 implementations could also potentially allow for anonymous enumeration of the resources in a bucket or the ability to guess and brute force object URLs. In an ideal world, I’d want my on-prem S3 interface to be firewalled off from the internet at large so only the Discourse server can reach it directly.


Not crazy, implementing secure uploads for non s3 setups is certainly not something I am against building. It is just that we have no urgency or push to build the feature and the change is non trivial.

We occasionally have interns and audition projects, this is certainly a type of project that could fit into one of those buckets.


Any thoughts on the feasibility of this as an easier path for secure media on not-AWS-S3 storage?

1 Like

I’ve got a quick hack that I think would provide secure uploads for login-required sites.

Basically, you set up NGINX Docs | Authentication Based on Subrequest Result for uploads. The only issue is that I can’t find a URL that returns a 403/401 when login-required is turned on, so accessing an upload when you’re not logged in gives a 500 error. This would only happen if someone had an upload URL and tried to access it when they weren’t logged in, so it doesn’t seem that bad.

It’s something like this:

# JP
    location = /auth {
        proxy_pass http://discourse/categories;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    # END JP
    location ~ ^/uploads/ {

      auth_request /auth; #$JP
      # NOTE: it is really annoying that we can't just define headers
      # at the top level and inherit.