{"record":{"id":"394c2d82961b49e0","repo":"semaphoreui/semaphore","slug":"unencrypted-connection","errorCode":null,"errorMessage":"unencrypted connection","messagePattern":"unencrypted connection","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/mailer/auth.go","lineNumber":33,"sourceCode":"func isLocalhost(name string) bool {\n\treturn name == \"localhost\" || name == \"127.0.0.1\" || name == \"::1\"\n}\n\ntype plainOrLoginAuth struct {\n\tusername   string\n\tpassword   string\n\thost       string\n\tauthMethod string\n}\n\nfunc (a *plainOrLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {\n\t// Must have TLS, or else localhost server.\n\t// Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.\n\t// In particular, it doesn't matter if the server advertises PLAIN auth.\n\t// That might just be the attacker saying\n\t// \"it's ok, you can trust me with your password.\"\n\tif !server.TLS && !isLocalhost(server.Name) {\n\t\treturn \"\", nil, errors.New(\"unencrypted connection\")\n\t}\n\tif server.Name != a.host {\n\t\treturn \"\", nil, errors.New(\"wrong host name\")\n\t}\n\tif !slices.Contains(server.Auth, \"PLAIN\") {\n\t\ta.authMethod = \"LOGIN\"\n\t\treturn a.authMethod, nil, nil\n\t} else {\n\t\ta.authMethod = \"PLAIN\"\n\t\tresp := []byte(\"\\x00\" + a.username + \"\\x00\" + a.password)\n\t\treturn a.authMethod, resp, nil\n\t}\n}\n\nfunc (a *plainOrLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {\n\tif !more {\n\t\treturn nil, nil\n\t}","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/mailer/auth.go#L15-L51","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nsmtpConfig := mailer.SMTPConfig{Host: \"smtp.example.com\", Port: 25, TLS: false}\n// after\nsmtpConfig := mailer.SMTPConfig{Host: \"smtp.example.com\", Port: 587, TLS: true} // STARTTLS enforced","handlingStrategy":"validation","validationCode":"if !smtpConfig.TLS && !isLocalhost(smtpConfig.Host) {\n    return errors.New(\"SMTP config must enable TLS (or use localhost) before PLAIN auth\")\n}","typeGuard":null,"tryCatchPattern":"ok, enc, err := auth.Start(&smtp.ServerInfo{Name: host, TLS: tlsOn, Auth: mechs})\nif err != nil && err.Error() == \"unencrypted connection\" {\n    return fmt.Errorf(\"enable STARTTLS/TLS on the SMTP connection (or use localhost); credentials are refused in plaintext: %w\", err)\n}","preventionTips":["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."],"tags":["smtp","tls","email","security"],"backgroundTag":"authentication-required","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}