semaphoreui/semaphore · error
unencrypted connection
Error message
unencrypted connection
What it means
The PLAIN SMTP auth implementation refuses to send credentials unless the connection is TLS-secured or the server is localhost. Per RFC, ServerInfo cannot be trusted on a plaintext connection (an attacker could advertise PLAIN), so Start rejects it outright with this error.
Solutions
- Enable TLS in the mailer configuration (STARTTLS or implicit TLS on port 465) so server.TLS is true.
- If this is genuinely a local relay, connect via localhost so isLocalhost passes.
- Configure the SMTP server to require and support STARTTLS; do not weaken the client to send credentials unencrypted.
Example fix
// before
smtpConfig := mailer.SMTPConfig{Host: "smtp.example.com", Port: 25, TLS: false}
// after
smtpConfig := mailer.SMTPConfig{Host: "smtp.example.com", Port: 587, TLS: true} // STARTTLS enforced Defensive patterns
Strategy: validation
Validate before calling
if !smtpConfig.TLS && !isLocalhost(smtpConfig.Host) {
return errors.New("SMTP config must enable TLS (or use localhost) before PLAIN auth")
} Try / catch
ok, enc, err := auth.Start(&smtp.ServerInfo{Name: host, TLS: tlsOn, Auth: mechs})
if err != nil && err.Error() == "unencrypted connection" {
return fmt.Errorf("enable STARTTLS/TLS on the SMTP connection (or use localhost); credentials are refused in plaintext: %w", err)
} Prevention
- Always configure SMTP with TLS/STARTTLS for remote hosts (port 587 STARTTLS or 465 implicit TLS).
- Use localhost only for genuinely local relays.
- Confirm the TLS handshake succeeded before authentication (do not swallow handshake errors).
- Never work around this by sending PLAIN credentials over an unencrypted remote connection.
When it happens
Trigger: Using mailer plainAuth with an smtp.Client whose ServerInfo.TLS is false and whose server name is not localhost - i.e. SMTP over a non-TLS connection to a remote host, typically on port 25 without STARTTLS.
Common situations: SMTP config without TLS/STARTTLS enabled pointing at a remote relay; implicit-TLS port (465) misconfigured as plaintext; TLS handshake silently failed but auth still attempted.
Related errors
- wrong host name
- unsupported TLS version
- You can't use both HTTP redirect address and port at the…
- http requests forbidden
- unexpected server challenge
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/394c2d82961b49e0.
Report an issue: GitHub.
Appendix: source
Thrown at util/mailer/auth.go:33
func isLocalhost(name string) bool {
return name == "localhost" || name == "127.0.0.1" || name == "::1"
}
type plainOrLoginAuth struct {
username string
password string
host string
authMethod string
}
func (a *plainOrLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
// Must have TLS, or else localhost server.
// Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.
// In particular, it doesn't matter if the server advertises PLAIN auth.
// That might just be the attacker saying
// "it's ok, you can trust me with your password."
if !server.TLS && !isLocalhost(server.Name) {
return "", nil, errors.New("unencrypted connection")
}
if server.Name != a.host {
return "", nil, errors.New("wrong host name")
}
if !slices.Contains(server.Auth, "PLAIN") {
a.authMethod = "LOGIN"
return a.authMethod, nil, nil
} else {
a.authMethod = "PLAIN"
resp := []byte("\x00" + a.username + "\x00" + a.password)
return a.authMethod, resp, nil
}
}
func (a *plainOrLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if !more {
return nil, nil
}View on GitHub (pinned to 1774ccb71a)