Potential fix for code scanning alert no. 1271: Email content injection

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
Jeremy
2026-03-06 15:30:55 -05:00
committed by GitHub
parent 4ebf8d23fe
commit 801760add1
2 changed files with 43 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"html"
"html/template"
"mime"
"net/mail"
@@ -329,6 +330,9 @@ func (s *MailService) SendEmail(ctx context.Context, to []string, subject, htmlB
auth = smtp.PlainAuth("", config.Username, config.Password, config.Host)
}
// Normalize and sanitize the email body so that any untrusted input is
// treated as plain text and cannot break out of the HTML context.
htmlBody = sanitizeAndNormalizeHTMLBody(htmlBody)
htmlBody = sanitizeEmailContent(htmlBody)
for _, recipient := range to {
@@ -494,6 +498,31 @@ func sanitizeEmailContent(body string) string {
}, body)
}
// sanitizeAndNormalizeHTMLBody converts an arbitrary string (potentially containing
// untrusted input) into a safe HTML fragment. It splits on newlines, escapes each
// line as plain text, and wraps non-empty lines in <p> tags. This ensures that
// user input cannot inject raw HTML into the email body.
func sanitizeAndNormalizeHTMLBody(body string) string {
if body == "" {
return ""
}
lines := strings.Split(body, "\n")
var b strings.Builder
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if b.Len() > 0 {
b.WriteString("\n")
}
b.WriteString("<p>")
b.WriteString(html.EscapeString(line))
b.WriteString("</p>")
}
return b.String()
}
// sanitizeEmailBody performs SMTP dot-stuffing to prevent email injection.
// According to RFC 5321, if a line starts with a period, it must be doubled
// to prevent premature termination of the SMTP DATA command.

View File

@@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"fmt"
"html"
"net"
"net/http"
neturl "net/url"
@@ -284,12 +283,24 @@ func (s *NotificationService) dispatchEmail(ctx context.Context, p models.Notifi
safeTitle := sanitizeForEmail(title)
safeMessage := sanitizeForEmail(message)
subject := fmt.Sprintf("[Charon Alert] %s", safeTitle)
htmlBody := "<p><strong>" + html.EscapeString(safeTitle) + "</strong></p><p>" + html.EscapeString(safeMessage) + "</p>"
// Build a plain-text body; MailService will convert this to safe HTML and
// perform additional sanitization before sending.
var bodyBuilder strings.Builder
if safeTitle != "" {
bodyBuilder.WriteString(safeTitle)
}
if safeMessage != "" {
if bodyBuilder.Len() > 0 {
bodyBuilder.WriteString("\n\n")
}
bodyBuilder.WriteString(safeMessage)
}
plainBody := bodyBuilder.String()
timeoutCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
if err := s.mailService.SendEmail(timeoutCtx, recipients, subject, htmlBody); err != nil {
if err := s.mailService.SendEmail(timeoutCtx, recipients, subject, plainBody); err != nil {
logger.Log().WithError(err).WithField("provider", util.SanitizeForLog(p.Name)).Error("Failed to send email notification")
}
}