FIX: strip unsubscribe links in incoming emails (#30695)

When we send an email notification to a user, we always include a link
that will allow them unsubscribe to these emails.

If the user reply to the email notification, the link to unsubscribe
might still be present in the final post (often in the elided part).

Since those links do not require authentication to unsubscribe a user
(this is a feature, not a bug), we would like to avoid showing them to
other users on Discourse.

(If such an email is forwarded elsewhere, then it's totally out of our
control.)

This commmit ensures we always strip those unsubscribe links from any
incoming email to avoid making it easier to unsubscribe another user.

Since the format we use for those links might be similar to the ones
used by other applications, the regular expression used to match those
links uses the absolute URL of the Discourse (aka.
`Discourse.base_url`).
This commit is contained in:
Régis Hanol 2025-01-13 11:33:46 +01:00 committed by GitHub
parent 03119312b5
commit d7aa13328d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 1 deletions

View File

@ -517,7 +517,12 @@ module Email
.join .join
end end
[text, elided_text, text_format] [strip_unsubscribe_links(text), strip_unsubscribe_links(elided_text), text_format]
end
def strip_unsubscribe_links(text)
@unsubscribe_regex ||= %r|#{Discourse.base_url}/email/unsubscribe/\h{64}|
(text.presence || "").gsub(@unsubscribe_regex, "")
end end
def to_markdown(html, elided_html) def to_markdown(html, elided_html)

View File

@ -2268,6 +2268,41 @@ RSpec.describe Email::Receiver do
text, _elided, _format = receiver.select_body text, _elided, _format = receiver.select_body
expect(text).to be_blank expect(text).to be_blank
end end
it "strip unsubscribe links" do
keep_relative = "/email/unsubscribe/#{SecureRandom.hex(32)}"
keep_other_instance = "http://other.discourse.org/email/unsubscribe/#{SecureRandom.hex(32)}"
strip_in_text = "#{Discourse.base_url}/email/unsubscribe/#{SecureRandom.hex(32)}"
strip_in_elided = "#{Discourse.base_url}/email/unsubscribe/#{SecureRandom.hex(32)}"
email = <<~EMAIL
Date: Fri, 10 Jan 2024 13:25:42 +0100
Subject: Will this be stripped?
From: Foo <foo@discourse.org>
To: bar@discourse.org
Content-Type: text/plain; charset="UTF-8"
This is a line that will not be touched.
This is a [relative](#{keep_relative}) link.
This one is from <a href="#{keep_other_instance}">another instance</a>
Here's my unsubscribe link: #{strip_in_text}
XoXo
---
To unsubscribe from these emails, [click here](#{strip_in_elided}).
EMAIL
text, elided, _ = Email::Receiver.new(email).select_body
expect(text).to_not include(strip_in_text)
expect(text).to include(keep_relative, keep_other_instance)
expect(elided).to_not include(strip_in_elided)
end
end end
describe "replying to digest" do describe "replying to digest" do