October 4, 2017·5 min read·engineeringgosecuritysmtptooling

Go 1.9.1 stops trusting an SMTP advertisement

PlainAuth again requires TLS for remote servers, and go get rejects conflicting nested version-control metadata. Both fixes put checks before operations that depend on untrusted information.

An SMTP server advertising support for password authentication does not prove that the connection is safe for a password. A malicious intermediary can advertise the same capability. Go 1.9.1 repairs a rule in net/smtp that had confused those two questions, restoring a TLS requirement for remote servers before PlainAuth sends credentials.

The October 4 release entry records two security fixes. The release diff contains the explanation of the SMTP change and a separate repair to version-control discovery in go get. Both corrections concern a decision made before a more sensitive operation. One controls when credentials can leave the program, while the other checks which version-control system the tool is about to use.

I like explicit operational policy in Go code. These fixes matter because applications and build environments also rely on policy inside the standard tools. A short library call should enforce the documented prerequisite instead of letting information from an untrusted peer waive it.

An advertised mechanism is not authentication

The SMTP patch explains that PlainAuth originally refused to send passwords to servers without TLS. A change released in Go 1.1 relaxed that rule when the server advertised the PLAIN mechanism. The documented TLS requirement remained, so the implementation and its published contract diverged.

The commit traces the relaxation to a request about authenticating without TLS. It identifies connections to localhost as the plausible motivating case. The adopted approach went further by allowing the server advertisement to determine whether sending credentials was acceptable. That gave the remote peer authority over a condition intended to protect the client from an impostor.

An intermediary does not need to persuade the client that an unusual mechanism is safe. It can advertise the same PLAIN mechanism that a legitimate server supports. If the client treats the advertisement as permission to proceed without transport protection, the check supplies no protection against that intermediary.

Go 1.9.1 removes the loop that searched advertised mechanisms for PLAIN when TLS was absent. The new condition rejects an unencrypted connection unless the name is one of the recognized localhost spellings. The host-name check remains afterward, so permitting a local connection does not remove every other check.

The source comment makes the reasoning explicit. Without TLS, the code cannot trust the contents of ServerInfo as evidence supplied by an authenticated remote server. In particular, a list of supported authentication mechanisms cannot establish that this is the server to which the password should be sent.

The local exception is narrow

The corrected documentation says credentials are sent only on TLS connections or connections to localhost. Otherwise authentication fails without sending them. The implementation defines localhost as exactly localhost, 127.0.0.1, or ::1. This is a specific exception in the helper, not a general classification of private networks as trusted.

That precision matters for configurations using other names. A hostname that happens to resolve to a loopback address is not one of the literal strings checked by this helper. The visible code tests the server name, so its documented exception should be understood at the level the implementation actually enforces.

The regression tests include a successful local case without TLS and a failing remote case that advertises PLAIN. They also preserve a case rejecting the wrong host name. These cases test the boundary that the earlier relaxation crossed. The important negative case is a server that says password authentication is available but has not satisfied the transport requirement.

A test that only checked a server with no advertised mechanisms would have missed that distinction. The old implementation already refused some unencrypted connections. The repair specifically removes the advertisement as an acceptable substitute for TLS, while keeping the explicitly documented local use case.

Source retrieval also interprets metadata

The second security change concerns nested version-control systems. The Go command has to determine which repository owns a package directory before retrieving or updating it. Discovering one metadata directory is insufficient if an enclosing directory belongs to a conflicting version-control system.

The diff changes vcsFromDir to record the first version-control system it discovers and continue looking through parents toward the source root. If it encounters a conflicting enclosing system, it returns an error identifying the inner and parent directories and their systems. It no longer treats the first discovery as a complete answer.

The patch explicitly allows Git inside Git because submodules can produce that arrangement. This is an example of preserving a known legitimate case while rejecting conflicting interpretations. A broad rule that forbade every nesting would change more behavior than the stated repair requires.

A new check also runs in the download path before the repository is processed further. The additional validation reflects the fact that repository selection is an operation with consequences, not merely a label for diagnostic output. The tool needs a consistent interpretation of the directory before continuing with its chosen version-control command.

The regression tests exercise go get -u against test repositories involving Subversion and Git. They require the command to fail and inspect the diagnostic for the conflicting systems. Checking the message as well as failure ensures the test observes the intended refusal, rather than an unrelated network or repository error.

Different checks protect different resources

The two changes do not share an exploit mechanism. SMTP authentication handles a network peer and a password. Repository discovery handles filesystem metadata and external version-control behavior. Their common design issue is that an earlier piece of information was being treated as sufficient authorization for a later operation.

In SMTP, capability discovery cannot establish the security of the connection. In source retrieval, a nested repository marker cannot be interpreted without considering the enclosing repository. Both patches add context to the decision at the point where the tool still has an opportunity to refuse the operation.

The release documentation records Go 1.8.4 on the same date with the same two security fixes. Programs and build systems on either supported line can therefore obtain these corrections through a maintenance release. The SMTP change can expose configurations that relied on the relaxed behavior, because an unencrypted remote authentication attempt now returns an error even when the server advertises PLAIN.

I consider that refusal part of the fix. The implementation again enforces the protection its documentation promised, while preserving the named localhost exception. The version-control change likewise makes an ambiguous directory arrangement fail with an explanation before the tool proceeds under the wrong repository interpretation.