Report privately through GitHub's advisory form:
https://github.com/ctolon/ldap-authenticator/security/advisories/new
Not through an issue, and not through a pull request — both are public from the moment they are opened.
Expect an acknowledgement within three days and an assessment within a week. If a fix is warranted you will be credited in the advisory and the changelog, unless you would rather not be.
The most recent 1.x release. A security fix goes out as a patch release on that line; there are no backports to older 1.x patches.
- A bind is never issued with an empty password, and the request is refused before a connection is opened. RFC 4511 makes a zero-length password an unauthenticated bind, which a conforming server answers with success.
- Every value interpolated into a search filter is escaped for RFC 4515, and every value interpolated into a distinguished name for RFC 4514. There is no exported way to interpolate an unescaped value.
- A wrong password and an unknown user produce the same error, so an application built on this package cannot become an account enumeration oracle by forgetting to collapse them.
- A pooled connection is bound as the service account and is never rebound as a user, so a connection cannot carry one request's identity into the next.
- A rejected credential is never retried, so this package cannot walk an account into a directory's lockout policy.
- A plaintext
ldap://URL is refused unless StartTLS is configured orWithInsecureNoTLSis passed explicitly. - No password reaches a log record, an error message, or the cache. The
cache key is an HMAC derived before any
Cacheimplementation is reached, so a store — including a shared, remote one — never sees a credential. - A password longer than the configured maximum is refused before any network call, as is a username that is empty, overlong, not valid UTF-8, or carrying a control character.
Two things reliably show up in an automated scan of this repository and are worth answering here, so that nobody has to re-derive the answer.
GO-2026-5932 — golang.org/x/crypto/openpgp is unmaintained. The
advisory names golang.org/x/crypto with no fixed version, so a scanner
that works from the module graph alone reports it against anything that
requires that module. This module does require it, transitively:
go-ldap/ldap/v3 imports golang.org/x/crypto/md4 for NTLM binds, which
is the only package of it that reaches the build.
None of the openpgp packages the advisory names is in the build graph —
go list -deps ./... finds none of them — and govulncheck, which
resolves reachability rather than module edges, reports the module and then
says the code does not call it:
Your code is affected by 0 vulnerabilities.
This scan also found 0 vulnerabilities in packages you import and 1
vulnerability in modules you require, but your code doesn't appear to call
these vulnerabilities.
make check runs govulncheck on every commit, so a change that made a
vulnerability reachable would fail the gate rather than reach a release.
go/clear-text-logging in the examples. CodeQL treats an identifier
matching password as a source of sensitive data. The passwordMaxLength
configuration field matches, and it is an int — a length bound, not a
credential. It reaches an error string in two places, and both carry
integers only:
ldapauth: invalid option: password max length 0 is below 1
ldapauth: invalid password: 6000 bytes exceeds the 1024 byte limit
Those errors are returned to the caller, and the example programs print
them, which is what the query sees. The property that matters is tested
directly rather than argued: TestNoPasswordEverReachesALogRecord runs a
full authentication with distinctive passwords and searches everything the
package logged, and TestNoPasswordEverReachesAnError does the same across
every error path a caller can reach, the over-length one included.
If you are evaluating this library and either finding gave you pause, those two tests are the thing to read.
It does not make an insecure directory secure. It cannot tell you whether
your service account has more access than it needs, whether your TLS
certificate is verified by anything, or whether the group whose name you
put in an authorisation rule is the group you meant. Nor is it a
constant-time authenticator: WithMinimumDuration in the httpauth package
pads a response, but anything built on a network round trip leaks some
timing, and this one does not pretend otherwise.
There is one thing worth stating plainly because it is a choice rather than
an oversight: WithCacheSecret is a real secret. Anybody holding it can
test a guessed credential against a shared cache offline, without touching
the directory and without the directory noticing. Keep it where you keep
the service account password, and remember that a cache shared between two
services turns one service's authentication decisions into the other's.
A password appearing in a log line, an error string, a Stats field, or a
cache entry is a vulnerability. Please report it.