VYPR
High severity8.2NVD Advisory· Published Jun 16, 2026· Updated Jun 16, 2026

CVE-2026-48780

CVE-2026-48780

Description

A maliciously crafted email address using RFC2047 encoded-words can bypass domain allowlist/denylist restrictions in Forem, granting unauthorized access to invite-only deployments.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A maliciously crafted email address using RFC2047 encoded-words can bypass domain allowlist/denylist restrictions in Forem, granting unauthorized access to invite-only deployments.

Vulnerability

Forem, an open-source community-building platform, is vulnerable to a domain restriction bypass prior to commit a2ab6d4. A maliciously crafted email address containing RFC2047 encoded-words (e.g., =?utf-8?q?test=40attacker.com=3e?=@company.com) can be accepted as valid by the email validation logic, allowing an attacker to bypass allowlist or denylist domain checks. The vulnerability is present in all versions before the patched commit [1].

Exploitation

An attacker needs no authentication or special privileges, only the ability to submit a registration or invitation form with a crafted email address. The attacker constructs an email that includes an RFC2047 encoded-word in the local part or domain part, such that the application's email validator considers the address syntactically valid while the underlying email delivery system may interpret the decoded address differently. This allows the attacker to bypass domain-based restrictions that would otherwise block or allow certain email domains [1][2].

Impact

Successful exploitation enables an attacker to gain unauthorized access to invite-only Forem deployments or communities that rely on domain allowlists or denylists. The attacker can register an account using an email address that appears to originate from an allowed domain, potentially leading to unauthorized disclosure of community content, ability to interact with other members, and further compromise of community integrity [2].

Mitigation

The issue is patched in commit a2ab6d4. Users should update Forem to a version that includes this commit. As a workaround, some SMTP servers and email delivery providers may drop or refuse to send maliciously crafted email addresses, but this is not a reliable mitigation. No CVE has been assigned (this is the CVE being described).

AI Insight generated on Jun 16, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Forem/Foremreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)

Patches

1
a2ab6d409d26

Merge commit from fork

https://github.com/forem/foremErin OsherMay 26, 2026via nvd-ref
5 files changed · +108 0
  • app/models/user.rb+7 0 modified
    @@ -150,6 +150,7 @@ def cached_recent_user_ids
       validates :comments_count, presence: true
       validates :credits_count, presence: true
       validates :email, length: { maximum: 254 }, email: true, allow_nil: true
    +  validate :reject_encoded_word_email, if: -> { email.present? }
       validates :email, uniqueness: { allow_nil: true, case_sensitive: false }, if: :email_changed?
       validates :following_orgs_count, presence: true
       validates :following_tags_count, presence: true
    @@ -866,6 +867,12 @@ def downcase_email
         self.email = email.downcase if email
       end
     
    +  def reject_encoded_word_email
    +    encoded_word_regex = /=\?[^?]+\?[BbQq]\?[^?]+\?=/
    +
    +    errors.add(:email, :invalid) if email.match?(encoded_word_regex)
    +  end
    +
       def bust_cache
         Users::BustCacheWorker.perform_async(id)
       end
    
  • spec/models/user_spec.rb+48 0 modified
    @@ -192,6 +192,54 @@ def provider_username(service_name)
           it { is_expected.to validate_length_of(:password).is_at_most(100).is_at_least(8) }
           it { is_expected.to validate_length_of(:username).is_at_most(30).is_at_least(2) }
     
    +      it "rejects RFC2047 encoded-words in email with a generic invalid error" do
    +        invalid_emails = [
    +          "=?utf-8?q?test=40attacker.com=3e?=@company.com",
    +          "=?utf-8?q?test=40attacker.com=3e=00?=test@company.com",
    +          "=?utf-8?q?test=40attacker.com=3e=0A=0D?=test@company.com",
    +          "=?utf-8?q?test=40attacker.com=3e=3c?=test@company.com",
    +          "=?utf-8?q?test=40attacker.com=3e=0A=0D?=RCPTTO:test@company.com",
    +          "user=?utf-8?q?x?=@company.com",
    +          "user@=?utf-8?q?company.com?=",
    +          "=?utf-8?Q?test=40attacker.com=3e?=@company.com",
    +          "=?utf-8?B?dGVzdEBhdHRhY2tlci5jb20+?=@company.com",
    +        ]
    +
    +        invalid_emails.each do |email|
    +          user = build(:user, email: email)
    +
    +          expect(user).not_to be_valid
    +          expect(user.errors[:email]).to include("is invalid")
    +        end
    +      end
    +
    +      it "documents that mail decoding changes the recipient shape locally" do
    +        encoded_word_email = "=?utf-8?q?test=40attacker.com=3e?=@company.com"
    +
    +        expect(Mail::Encodings.value_decode(encoded_word_email)).to eq("test@attacker.com>@company.com")
    +      end
    +
    +      it "preserves nil email behavior" do
    +        user = build(:user, email: nil)
    +
    +        expect(user).to be_valid
    +      end
    +
    +      it "keeps normal and non-encoded-word email addresses valid" do
    +        valid_emails = [
    +          "user@example.com",
    +          "first.last+tag@example.co.uk",
    +          "user=?not-complete@example.com",
    +          "user?utf-8?q?x@example.com",
    +        ]
    +
    +        valid_emails.each do |email|
    +          user = build(:user, email: email)
    +
    +          expect(user).to be_valid
    +        end
    +      end
    +
           it { is_expected.not_to allow_value("  ").for(:name) }
     
           it { is_expected.to validate_presence_of(:articles_count) }
    
  • spec/requests/magic_links_spec.rb+24 0 modified
    @@ -123,6 +123,30 @@
               expect(response).to redirect_to(new_user_session_path)
             end
           end
    +
    +      it "does not create a user or send a magic link when encoded-word email domain is acceptable" do
    +        encoded_word_email = "=?utf-8?q?test=40attacker.com=3e?=@company.com"
    +        built_user = nil
    +
    +        allow(ForemInstance).to receive(:invitation_only?).and_return(false)
    +        allow(Settings::Authentication).to receive(:acceptable_domain?).with(domain: "company.com").and_return(true)
    +        allow(User).to receive(:find_by).and_call_original
    +        allow(User).to receive(:find_by).with(email: encoded_word_email).and_return(nil)
    +        allow(User).to receive(:new).and_wrap_original do |method, *args, **kwargs|
    +          built_user = method.call(*args, **kwargs)
    +          allow(built_user).to receive(:send_magic_link!).and_call_original
    +          built_user
    +        end
    +
    +        expect do
    +          post "/magic_links", params: { email: encoded_word_email }
    +        end.not_to change(User, :count)
    +
    +        expect(built_user).not_to be_persisted
    +        expect(built_user).not_to have_received(:send_magic_link!)
    +        expect(controller.current_user).to be_nil
    +        expect(response).to redirect_to(new_user_session_path)
    +      end
         end
     
         context "when no email is provided" do
    
  • spec/requests/registrations_spec.rb+12 0 modified
    @@ -430,6 +430,18 @@ def mock_recaptcha_verification
             expect(User.all.size).to be 0
           end
     
    +      it "does not create user when encoded-word email passes the raw domain allow list" do
    +        allow(Settings::Authentication).to receive(:allowed_registration_email_domains).and_return(["company.com"])
    +
    +        post "/users", params:
    +        { user: { name: "attacker #{rand(10)}",
    +                  username: "attacker_#{rand(10)}",
    +                  email: "=?utf-8?q?test=40attacker.com=3e?=@company.com",
    +                  password: "PaSSw0rd_yo000",
    +                  password_confirmation: "PaSSw0rd_yo000" } }
    +        expect(User.all.size).to be 0
    +      end
    +
           it "creates user when email in allow list" do
             post "/users", params:
             { user: { name: "royal #{rand(10)}",
    
  • spec/services/authentication/authenticator_spec.rb+17 0 modified
    @@ -340,6 +340,23 @@
             )
           end
     
    +      it "rejects encoded-word provider email through user validation" do
    +        encoded_word_email = "=?utf-8?q?test=40attacker.com=3e?=@company.com"
    +        auth_payload.info.email = encoded_word_email
    +        allow(ForemStatsClient).to receive(:increment)
    +        allow(Settings::Authentication).to receive(:acceptable_domain?).with(domain: "company.com").and_return(true)
    +
    +        expect do
    +          expect { described_class.call(auth_payload) }.to raise_error(ActiveRecord::RecordInvalid, /Email is invalid/)
    +        end.to not_change(User, :count).and not_change(Identity, :count)
    +
    +        expect(Settings::Authentication).to have_received(:acceptable_domain?).with(domain: "company.com")
    +        expect(User.find_by(email: encoded_word_email)).to be_nil
    +        expect(Identity.find_by(provider: "github", uid: auth_payload.uid)).to be_nil
    +        tags = hash_including(tags: array_including("error:ActiveRecord::RecordInvalid"))
    +        expect(ForemStatsClient).to have_received(:increment).with("identity.errors", tags)
    +      end
    +
           it "increments identity.errors if any errors occur in the transaction" do
             # rubocop:disable RSpec/AnyInstance
             allow_any_instance_of(Identity).to receive(:save!).and_raise(StandardError)
    

Vulnerability mechanics

Root cause

"Missing validation of RFC 2047 encoded-words in email addresses allows domain allowlist/denylist bypass."

Attack vector

An attacker submits a registration, magic-link, or OAuth sign-up request with an email address containing an RFC 2047 encoded-word in the local part, such as `=?utf-8?q?test=40attacker.com=3e?=@company.com`. The domain allowlist/denylist checks the raw domain (`company.com`) and approve it, but when the email is later decoded by the mail library the effective recipient becomes `test@attacker.com>@company.com`, routing messages to an attacker-controlled domain. This allows bypassing domain restrictions to gain access to invite-only deployments or to receive authentication emails at an arbitrary address [ref_id=1].

Affected code

The vulnerability exists in the `User` model (`app/models/user.rb`). The domain allowlist/denylist checks operate on the raw email string, but downstream mail libraries decode RFC 2047 encoded-words, changing the effective recipient. The patch adds a `reject_encoded_word_email` validation that detects encoded-word patterns with the regex `/=\?[^?]+\?[BbQq]\?[^?]+\?=/` [patch_id=6189714].

What the fix does

The patch adds a custom validation method `reject_encoded_word_email` to the `User` model that checks the email against the regex `/=\?[^?]+\?[BbQq]\?[^?]+\?=/` and adds an `:invalid` error if matched [patch_id=6189714]. This prevents any email containing an RFC 2047 encoded-word from being accepted, regardless of whether the raw domain passes allowlist checks. The test suite confirms that encoded-word emails are rejected during registration, magic-link requests, and OAuth authentication flows [ref_id=1].

Preconditions

  • configThe Forem deployment must use a domain allowlist or denylist (e.g., Settings::Authentication.allowed_registration_email_domains) or be configured as invitation-only.
  • inputThe attacker must be able to submit a registration, magic-link, or OAuth sign-up form with a crafted email address.
  • authNo authentication is required; the attack is unauthenticated (CVSS PR:N).

Reproduction

No public PoC is included in the bundle.

Generated on Jun 16, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.