{"record":{"id":"781d9e23e42a1e8b","repo":"goharbor/harbor","slug":"empty-search-dn","errorCode":null,"errorMessage":"empty search dn","messagePattern":"empty search dn","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/pkg/ldap/ldap.go","lineNumber":60,"sourceCode":"var ErrInvalidCredential = errors.New(\"invalid credential\")\n\n// ErrLDAPServerTimeout ...\nvar ErrLDAPServerTimeout = errors.New(\"ldap server network timeout\")\n\n// ErrLDAPPingFail ...\nvar ErrLDAPPingFail = errors.New(\"fail to ping LDAP server\")\n\n// ErrDNSyntax ...\nvar ErrDNSyntax = errors.New(\"invalid DN syntax\")\n\n// ErrInvalidFilter ...\nvar ErrInvalidFilter = errors.New(\"invalid filter syntax\")\n\n// ErrEmptyBaseDN ...\nvar ErrEmptyBaseDN = errors.New(\"empty base dn\")\n\n// ErrEmptySearchDN ...\nvar ErrEmptySearchDN = errors.New(\"empty search dn\")\n\n// Session - define a LDAP session\ntype Session struct {\n\tbasicCfg models.LdapConf\n\tgroupCfg models.GroupConf\n\tldapConn *goldap.Conn\n}\n\n// NewSession create session with configs\nfunc NewSession(basicCfg models.LdapConf, groupCfg models.GroupConf) *Session {\n\treturn &Session{\n\t\tbasicCfg: basicCfg,\n\t\tgroupCfg: groupCfg,\n\t}\n}\n\nfunc formatURL(ldapURL string) (string, error) {\n\tvar protocol, hostport string","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/pkg/ldap/ldap.go#L42-L78","documentation":"Sentinel error returned by ldap.TestConfig (POST /api/v2.0/ldap/ping) when the effective ldap_search_dn is empty — the ping body supplied no search_dn and none is stored in configuration. Unlike the password, there is no fallback: an empty bind DN is rejected outright before attempting the bind.","triggerScenarios":"POST /api/v2.0/ldap/ping with a request body lacking search_dn while the stored configuration's ldap_search_dn is \"\"; or pinging with an explicitly empty string; the check runs right after the connection opens, so network/auth errors appear before it.","commonSituations":"Testing connectivity before finishing the LDAP config; directories that allow anonymous search (admins leave the DN blank) — Harbor still requires a non-empty value; config migration that dropped the DN field; form validation gaps in custom admin tooling.","solutions":["Include search_dn in the ping request: e.g. \"cn=svc-harbor,ou=svc,dc=example,dc=com\".","Or save ldap_search_dn permanently first via PUT /api/v2.0/configurations, then ping without it.","If your directory relies on anonymous bind, configure a low-privilege read-only account DN instead."],"exampleFix":"# before\ncurl -X POST https://harbor/api/v2.0/ldap/ping -d '{\"url\": \"ldap://ad.example.com\"}'\n# after\ncurl -X POST https://harbor/api/v2.0/ldap/ping -d '{\"url\": \"ldap://ad.example.com\", \"search_dn\": \"cn=svc-harbor,ou=svc,dc=example,dc=com\", \"search_password\": \"<secret>\"}'","handlingStrategy":"validation","validationCode":"// Before POST /api/v2.0/ldap/ping:\nif strings.TrimSpace(effectiveSearchDN(cfg, req)) == \"\" {\n    fail(\"supply search_dn in the request or save ldap_search_dn first\")\n}","typeGuard":"func isLdapEmptySearchDN(err error) bool { return errors.Is(err, ldap.ErrEmptySearchDN) }","tryCatchPattern":"if errors.Is(err, ldap.ErrEmptySearchDN) { populate search_dn (request body or stored config) and re-run the ping; connection-level issues are already ruled out at this point }","preventionTips":["Always ping with the full triple: url, search_dn, search_password during setup.","Even for anonymous-search directories, register a read-only bind DN.","Templated configs should assert search_dn is non-empty after rendering."],"tags":["ldap","config","bind-dn","harbor"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}