fix: server crash when LDAP search filter is invalid - #41373
Conversation
ldapjs parses the search filter synchronously and throws before the callback runs (e.g. 'invalid attribute name' when the User Search Field is empty, producing a filter like '(&(=*))'). The throw escaped doAsyncSearch/doPagedSearch as an unhandled rejection and terminated the process during LDAP sync. Route synchronous client.search throws to the existing callback error path via a small clientSearch wrapper used by all three call sites.
|
Looks like this PR is ready to merge! 🎉 |
🦋 Changeset detectedLatest commit: 9b0873c The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📜 Recent review details⏰ Context from checks skipped due to timeout. (3)
🧰 Additional context used📓 Path-based instructions (2)**/*.{ts,tsx,js}📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
Files:
**/*.spec.ts📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
Files:
🧠 Learnings (6)📚 Learning: 2026-03-16T21:50:37.589ZApplied to files:
📚 Learning: 2026-02-24T19:22:48.358ZApplied to files:
📚 Learning: 2026-02-26T19:25:44.063ZApplied to files:
📚 Learning: 2026-02-26T19:25:44.063ZApplied to files:
📚 Learning: 2026-03-06T18:10:15.268ZApplied to files:
📚 Learning: 2026-05-06T12:21:44.083ZApplied to files:
🔇 Additional comments (4)
WalkthroughLDAP search calls now handle synchronous client errors, invalid user-search-field configuration is rejected explicitly, and user import propagates search failures instead of leaving them unhandled. Unit tests cover search errors, filter composition, validation, and import-facing rejection behavior. ChangesLDAP error handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
importNewUsers discarded the searchAllUsers promise with void, so any rejection not routed through endCallback became an unhandled rejection. Chain it to the surrounding promise's reject instead.
getUserFilter's empty-field guard was dead code: ''.split(',') returns
[''], so the length === 0 branch was unreachable and an empty field
composed the invalid filter '(=*)'. Trim segments, drop empty ones
(also covers trailing commas), and throw a clear configuration error
instead of handing a malformed filter to ldapjs.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #41373 +/- ##
===========================================
- Coverage 68.27% 68.24% -0.03%
===========================================
Files 3968 3969 +1
Lines 154740 154824 +84
Branches 27847 27798 -49
===========================================
+ Hits 105652 105665 +13
- Misses 44294 44416 +122
+ Partials 4794 4743 -51
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Proposed changes (including videos or screenshots)
ldapjs parses the search filter synchronously inside
client.searchand throws before any callback runs — e.g.Error: invalid attribute namewhen the User Search Field is empty, sincegetUserFilterthen composes(&(objectclass=user)(=*)). That throw escapeddoAsyncSearch/doPagedSearchas anUnhandledPromiseRejectionand terminated the whole server process (exit code 1) during LDAP sync or login.Three layered fixes:
client.searchthrows to the callback error path (Connection.ts): a smallclientSearchwrapper (try/catch, mirroring the pattern already used inbindDN) used by all three call sites (doCustomSearch,doAsyncSearch,doPagedSearch). Covers any malformed filter, not just the empty-field case.getUserFilter): the existing guard was dead code —''.split(',')returns[''], so thelength === 0branch was unreachable and an empty field composed the invalid filter(=*). Segments are now trimmed and empty ones dropped (also covers trailing commas likesAMAccountName,), and a fully empty field throwsLDAP User Search Field is not configuredbefore ldapjs is ever called.searchAllUserspromise in the sync job (ee/.../Manager.tsimportNewUsers): it wasvoid-ed, so any rejection not routed throughendCallbackbecame an unhandled rejection; it now chains to the surrounding promise'srejectand lands insync()'s existing try/catch.Unit tests (
Connection.spec.ts, new) pin the behavior: sync-throw routing (verified to fail with the leaked throw when the try/catch is removed) andgetUserFiltercomposition/trimming/validation.Issue(s)
https://rocketchat.atlassian.net/browse/CORE-2407
Steps to test or reproduce
LDAP_AD_User_Search_Field) empty.POST /api/v1/ldap.syncNow).Before: server logs
UnhandledPromiseRejection: Error: invalid attribute nameand exits. After: sync fails with a loggedLDAP User Search Field is not configurederror and the server keeps running.Non-AD equivalent: any non-AD server type with
LDAP_User_Search_Fieldempty or containing a trailing comma.Further comments
An e2e test isn't feasible here: CI has no LDAP server, so
ldap.syncNow/ldap.testSearchfail at connect/bind before ever reaching the filter parse. The unit tests stub the ldapjs client at the exact throw point instead.All
getUserFiltercallers (searchByUsernamelogin/testSearch paths,searchAllUserssync path) already wrap in try/catch or now propagate to one, so the new throw fails only the operation, never the process.Summary by CodeRabbit