fix: sanitize parentheses from email From header to prevent SMTP 553 errors (#14075)

## Description

When an inbox name or business name contains parentheses — e.g. `Giro
Crédito - Soporte (Email` — the resulting From header becomes
unparseable by SMTP servers. The `(` is interpreted as an RFC 5322
comment start, swallowing the actual email address and causing a `553
Invalid email address` rejection.

Closes [CW-6323](https://linear.app/chatwoot/issue/CW-6323)


## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## How to reproduce?

1. Set an inbox's name or business name to include a parenthesis, e.g.
`Support (Email`
2. Send an outgoing email reply from that inbox
3. Observe `Net::SMTPFatalError: 553 ... Invalid email address`

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
This commit is contained in:
Tanmay Deep Sharma 2026-04-21 13:00:20 +07:00 committed by GitHub
parent 437dd9d38c
commit 7d42edd17f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 15 additions and 4 deletions

View File

@ -41,7 +41,7 @@ class Email::BaseBuilder
end
def business_name
inbox.business_name || inbox.sanitized_name
inbox.sanitized_business_name
end
def account_support_email

View File

@ -105,7 +105,7 @@ class ConversationReplyMailer < ApplicationMailer
end
def business_name
@inbox.business_name || @inbox.sanitized_name
@inbox.sanitized_business_name
end
def from_email

View File

@ -102,7 +102,7 @@ class Inbox < ApplicationRecord
# Sanitizes inbox name for balanced email provider compatibility
# ALLOWS: /'._- and Unicode letters/numbers/emojis
# REMOVES: Forbidden chars (\<>@") + spam-trigger symbols (!#$%&*+=?^`{|}~)
# REMOVES: Forbidden chars (\<>@"()) + spam-trigger symbols (!#$%&*+=?^`{|}~)
def sanitized_name
return default_name_for_blank_name if name.blank?
@ -110,6 +110,10 @@ class Inbox < ApplicationRecord
sanitized.blank? && email? ? display_name_from_email : sanitized
end
def sanitized_business_name
sanitize_raw_name(business_name) || sanitized_name
end
def sms?
channel_type == 'Channel::Sms'
end
@ -209,8 +213,15 @@ class Inbox < ApplicationRecord
email? ? display_name_from_email : ''
end
def sanitize_raw_name(raw)
return nil if raw.blank?
result = apply_sanitization_rules(raw)
result.presence
end
def apply_sanitization_rules(name)
name.gsub(/[\\<>@"!#$%&*+=?^`{|}~:;]/, '') # Remove forbidden chars
name.gsub(/[\\<>@"!#$%&*+=?^`{|}~:;()]/, '') # Remove forbidden chars
.gsub(/[\x00-\x1F\x7F]/, ' ') # Replace control chars with spaces
.gsub(/\A[[:punct:]]+|[[:punct:]]+\z/, '') # Remove leading/trailing punctuation
.gsub(/\s+/, ' ') # Normalize spaces