The "import accounts" browse was shelling out a single unbounded
ldapsearch call, which failed outright with "Size limit exceeded (4)"
once the directory grew past whatever sizeLimit the LDAP server enforces
for this bind account. Added RFC 2696 paged results (-E pr=500/noprompt)
so ldapsearch transparently walks the whole base DN in pages instead of
one request that trips the limit.
Separately: execFile's rejection .message is "Command failed: <full
argv>\n<stderr>", and the full argv includes the bind password passed via
-w — that was reaching the admin UI verbatim in the error banner shown
after a failed search. Only stderr (ldapsearch's own diagnostic text,
which never echoes its invocation) is now surfaced.
Also fixes an unrelated image-build flake: drizzle-kit's migrate step
leaves db.sqlite in SQLite's default (non-WAL) journal mode, and next
build's parallel page-data-collection workers then race to perform the
first-ever journal_mode=WAL switch — a mode change that, unlike ordinary
statements, doesn't reliably honor busy_timeout, so one worker's pragma
call throws SQLITE_BUSY regardless of the configured timeout. Switching
to WAL once, single-process, right after migration avoids the race
entirely.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gh2UXUQUVBWroWEnn1FLFG
Bisecting through attribute selection and paging kept reproducing the
same failure in different shapes ("Encoding too long" BER parser error,
then ECONNRESET) — all while a plain `ldapsearch` against the exact same
query, run from inside this same container, completed successfully every
single time. That's strong enough evidence of a bug somewhere in
ldapjs/@ldapjs-asn1's BER decoding against this AD's actual response
bytes, not in our query. Rather than keep chasing a third-party parser
bug, searchLdapDirectory() now shells out to the system `ldapsearch`
(added to the image via ldap-utils) and parses its LDIF output directly
— the same tool that's already proven reliable here. authenticateLdapUser
(the per-login lookup) is untouched: it's a narrow single-match query
that has shown no sign of this issue, and isn't worth the added latency
of spawning a process on every login.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012o9j9RezxbZVKQMrB7oRLY
next build's page-data collection runs several parallel workers that each
import the db client at module-eval time. When data/db.sqlite doesn't
exist yet, every worker raced to create it from scratch — a race at the
file-creation level itself, before our code ever gets to run a
busy_timeout pragma, so pragma ordering alone (previous commit) wasn't
enough to fix it. Running db:migrate as its own isolated, single-process
build step first means the workers only ever see an already-existing,
already-stable file. The runtime volume-mounted DB is unaffected — it's
still migrated again (as a no-op once applied) by the existing CMD.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012o9j9RezxbZVKQMrB7oRLY