hashicorp / hashicorp/vault

Bug: sdk/helper/ldaputil: errwrap `{{err}}` placeholder passed to fmt.Errorf, producing malformed error messages

Open Beginner friendly
#32,063 3 comments 1 reaction 0 assignees View on GitHub
bug error-messages helper/ldaputil
Dominant language
Go
Stars
36.3k
Forks
4.8k
PR merge metrics
PR metrics pending

Description

### Describe the bug

First recorded: https://github.com/hashicorp/vault-plugin-secrets-openldap/pull/183

Six call sites in `sdk/helper/ldaputil/client.go` build a format string containing the `errwrap` placeholder `{{err}}` and pass it to `fmt.Errorf` along with an `error` argument. `fmt.Errorf` does not interpret `{{err}}`, and the format string has no verb to consume the argument, so Go appends `%!(EXTRA ...)`.

The result is a user-facing error containing both an uninterpreted `{{err}}` literal and a Go formatting-error marker:

```
error connecting to host "ldaps://dc1.example.com:636": {{err}}%!(EXTRA *ldap.Error=LDAP Result Code 200 "Network Error": dial tcp 10.0.0.1:636: i/o timeout)
```

Expected:

```
error connecting to host "ldaps://dc1.example.com:636": LDAP Result Code 200 "Network Error": dial tcp 10.0.0.1:636: i/o timeout
```

This looks like leftover from the `errwrap` → `fmt` migration. Elsewhere in the codebase `{{err}}` is used correctly with `errwrap.Wrapf`, which does interpret it; these six use `fmt.Errorf`, which does not.

A secondary consequence: because `%w` is not used, the underlying error is not wrapped, so `errors.Is` / `errors.As` cannot reach it.

### Affected lines

All in `sdk/helper/ldaputil/client.go`, present on `main` at `c6c940dde7e050c9cc5f41cfc7329227a85115ad`:

| Line | Message |
|------|---------|
| 40 | `error parsing url %q` |
| 107 | `error connecting to host %q` |
| 456 | `SID %#v convert failed reading Revision` |
| 460 | `SID %#v convert failed reading SubAuthorityCount` |
| 464 | `SID %#v convert failed reading IdentifierAuthority` |
| 470 | `SID %#v convert failed reading SubAuthority` |

For example, line 107:

```go
retErr = multierror.Append(retErr, fmt.Errorf(fmt.Sprintf("error connecting to host %q: {{err}}", uut), err))
```

### To Reproduce

1. Configure any component that uses `ldaputil` against an unreachable host or port — the LDAP auth method, or the LDAP/AD secrets engine via `vault-plugin-secrets-openldap`.
2. Trigger an operation that must reach the directory. For the secrets engine, issuing or revoking a dynamic credential is enough:

```
vault lease revoke -sync ldap/creds//
```

3. Observe the returned error.

### Expected behavior

The underlying LDAP error is reported without the `{{err}}` literal or the `%!(EXTRA ...)` marker, and is wrapped such that `errors.Is` / `errors.As` work.

### Impact

- User-facing error messages are malformed.
- Log-based alerting that pattern-matches on these error strings will not match, because the literal text differs from what the message is meant to say. This is the practical problem: the connection failure is still legible to a human inside the `EXTRA` block, but not to a monitoring rule.
- Callers cannot unwrap to inspect the underlying `*ldap.Error`.

Affects both the built-in LDAP auth method (`builtin/credential/ldap`) and the LDAP/AD secrets engines, which reach `ldaputil` through `vault-plugin-secrets-openldap`.

### Prior mention

This was noticed and correctly attributed during unrelated work in [vault-plugin-secrets-openldap#183](https://github.com/hashicorp/vault-plugin-secrets-openldap/pull/183), which contains the same output and the note:

> (Note the `{{err}}%!(EXTRA` issue is coming from the `ldaputil` client).

It does not appear to have been filed separately, which is presumably why it is still present.

### Suggested fix

```go
// before
fmt.Errorf(fmt.Sprintf("error connecting to host %q: {{err}}", uut), err)

// after
fmt.Errorf("error connecting to host %q: %w", uut, err)
```

Applied to all six call sites. Using `%w` also restores `errors.Is` / `errors.As` support.

### Environment

- **Vault version:** present on `main` at `c6c940dde7` (2026-07-31)
- **SDK versions checked:** `v0.23.0`, `v0.24.0`, `v0.25.1` — all six occurrences present in each
- **Age:** line 107 last modified in `cc570c11bb` (2022-10-26)
- **Observed on:** Vault 2.0.1+ent with `vault-plugin-secrets-openldap`, against Windows Server 2022 AD over LDAPS

Contributor guide

Open the contributing guide

Research direction

Start in sdk/helper/ldaputil/client.go and inspect the six listed call sites, along with how their errors are consumed by the LDAP auth method and secrets engines. Verify the resulting messages omit the literal {{err}} and %!(EXTRA ...) marker, and that callers can unwrap the underlying LDAP error.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authentication, backend, security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
84/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.